[Erp5-report] r34434 ivan - in /erp5/trunk/products/ERP5Security: ./ tests/ www/

nobody at svn.erp5.org nobody at svn.erp5.org
Fri Apr 9 16:31:01 CEST 2010


Author: ivan
Date: Fri Apr  9 16:31:00 2010
New Revision: 34434

URL: http://svn.erp5.org?rev=34434&view=rev
Log:
Add key authentication plugin (preliminary work of FX) and extend tests to basic cover it. This is a work in progress and needs improvements (see XXX) and better test coverage.

Added:
    erp5/trunk/products/ERP5Security/ERP5KeyAuthPlugin.py
    erp5/trunk/products/ERP5Security/www/ERP5Security_addERP5KeyAuthPlugin.zpt
    erp5/trunk/products/ERP5Security/www/ERP5Security_editERP5KeyAuthPlugin.zpt
Modified:
    erp5/trunk/products/ERP5Security/__init__.py
    erp5/trunk/products/ERP5Security/tests/testERP5Security.py

Added: erp5/trunk/products/ERP5Security/ERP5KeyAuthPlugin.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Security/ERP5KeyAuthPlugin.py?rev=34434&view=auto
==============================================================================
--- erp5/trunk/products/ERP5Security/ERP5KeyAuthPlugin.py (added)
+++ erp5/trunk/products/ERP5Security/ERP5KeyAuthPlugin.py [utf8] Fri Apr  9 16:31:00 2010
@@ -1,0 +1,410 @@
+# -*- coding: utf-8 -*-
+##############################################################################                                 
+#                                                                                                              
+# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.                                        
+#                    Francois-Xavier Algrain <fxalgrain at tiolive.com>                                                   
+#                                                                                                              
+# WARNING: This program as such is intended to be used by professional                                         
+# programmers who take the whole responsability of assessing all potential                                     
+# consequences resulting from its eventual inadequacies and bugs                                               
+# End users who are looking for a ready-to-use solution with commercial                                        
+# garantees and support are strongly adviced to contract a Free Software                                       
+# Service Company                                                                                              
+#                                                                                                              
+# This program is Free Software; you can redistribute it and/or                                                
+# modify it under the terms of the GNU General Public License                                                  
+# as published by the Free Software Foundation; either version 2                                               
+# of the License, or (at your option) any later version.                                                       
+#                                                                                                              
+# This program is distributed in the hope that it will be useful,                                              
+# but WITHOUT ANY WARRANTY; without even the implied warranty of                                               
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                                
+# GNU General Public License for more details.                                                                 
+#                                                                                                              
+# You should have received a copy of the GNU General Public License                                            
+# along with this program; if not, write to the Free Software                                                  
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.                                  
+#                                                                                                              
+##############################################################################     
+
+from base64 import encodestring, decodestring
+from urllib import quote, unquote
+from DateTime import DateTime
+from zLOG import LOG, PROBLEM
+from Globals import InitializeClass
+try:
+    from zope.interface import Interface
+except ImportError:
+    from Products.PluggableAuthService.utils import Interface
+
+from AccessControl import ClassSecurityInfo
+from AccessControl.SecurityManagement import getSecurityManager,\
+                                             newSecurityManager,\
+                                             setSecurityManager
+
+from Products.PageTemplates.PageTemplateFile import PageTemplateFile
+
+from Products.PluggableAuthService.interfaces import plugins
+from Products.PluggableAuthService.utils import classImplements
+from Products.PluggableAuthService.permissions import ManageUsers
+from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
+from Products.PluggableAuthService.plugins.CookieAuthHelper import CookieAuthHelper
+
+from Products.ERP5Type.Cache import CachingMethod
+from Products.ERP5Security.ERP5UserManager import ERP5UserManager,\
+                                                  SUPER_USER,\
+                                                  _AuthenticationFailure
+
+class ILoginEncryptionPlugin(Interface):
+  """Contract for possible ERP5 Key Auth Plugin"""
+
+  def encrypt(self,login):
+    """Encrypt the login"""
+
+  def decrypt(self,crypted_login):
+    """Decrypt string and return the login"""
+
+
+#Form for new plugin in ZMI
+manage_addERP5KeyAuthPluginForm = PageTemplateFile(     
+    'www/ERP5Security_addERP5KeyAuthPlugin', globals(),
+    __name__='manage_addERP5KeyAuthPluginForm')
+
+def addERP5KeyAuthPlugin(dispatcher, id,title=None,\
+                         encryption_key='',cookie_name='',\
+                         default_cookie_name='',REQUEST=None):
+    """ Add a ERP5KeyAuthPlugin to a Pluggable Auth Service. """
+
+    plugin = ERP5KeyAuthPlugin( id, title,encryption_key,cookie_name,default_cookie_name)
+    dispatcher._setObject(plugin.getId(), plugin)
+
+    if REQUEST is not None:
+        REQUEST['RESPONSE'].redirect(
+                                '%s/manage_workspace'
+                                '?manage_tabs_message='
+                                'ERP5KeyAuthPlugin+added.'
+                            % dispatcher.absolute_url())
+
+class ERP5KeyAuthPlugin(ERP5UserManager, CookieAuthHelper):
+  """
+    Key authentification PAS plugin which support key authentication in URL.
+
+    <ERP5_Root>/web_page_module/1/view?__ac_key=207221200213146153166
+
+    where value of __ac_key contains (encrypted):
+    - proxied (i.e. granting user) username 
+    - PAS plugin encryption key
+
+  XXX: improve encrypt & decrypt part to use PAS encryption_key with a true 
+  python encryption library (reuse of public / private key architecture)!
+
+  """
+
+  meta_type = "ERP5 Key Authentication"
+  login_path = 'login_form'
+  security = ClassSecurityInfo()  
+  block_length = 3
+  cookie_name = "__ac_key"
+  default_cookie_name = "__ac"
+  encryption_key = ''
+  encrypted_key = ''
+
+  manage_options = ( ( { 'label': 'Edit',
+                          'action': 'manage_editERP5KeyAuthPluginForm', }
+                        ,
+                      )
+                      + BasePlugin.manage_options[:]
+                      #+ CookieAuthHelper.manage_options[:] //don't need folder option today
+                    )
+
+  _properties = ( ( { 'id':'default_cookie_name',
+                      'type':'string',
+                      'mode':'w',
+                      'label':'Default Cookie Name'
+                    },
+                    )
+                    + BasePlugin._properties[:]
+                    + CookieAuthHelper._properties[:]
+                  )
+
+  def __init__(self, id, title=None, encryption_key='', cookie_name='', default_cookie_name=''):
+    #Check parameters
+    if cookie_name is None or cookie_name == '':
+      cookie_name = id      
+    if encryption_key is None or encryption_key == '':
+      encryption_key = id
+    if "__ac_key" in [cookie_name, default_cookie_name]:
+      raise ValueError, "Cookie name must be different of __ac_key"
+
+    #Register value
+    self._setId(id)
+    self.title = title
+    self.cookie_name = cookie_name
+    self.default_cookie_name = default_cookie_name
+    self.encryption_key = encryption_key
+    self.encrypted_key = self.transformKey(self.encryption_key);
+
+
+  ################################
+  #    ILoginEncryptionPlugin    #
+  ################################
+  security.declarePrivate('transformKey')
+  def transformKey(self,key):
+    """Transform the key to number for encryption"""
+    encrypt_key = []
+    for letter in key:
+      encrypt_key.append(ord(letter))
+    return encrypt_key
+
+  security.declarePublic('encrypt')
+  def encrypt(self,login):
+    """Encrypt the login"""
+    crypted_login = ''
+    key_length = len(self.encrypted_key)
+    for i in range(0,len(login)):
+      delta = i % key_length
+      crypted_letter = str(ord(login[i]) + self.encrypted_key[delta])
+      #ord is the inverse of chr() for 8-bit (1111 1111 = 256)
+      #so crypted_letter max id 512
+      #we ajust lenght to be able to decrypt by block
+      crypted_letter = crypted_letter.rjust(self.block_length, '0')
+      crypted_login += crypted_letter
+
+    return crypted_login
+  
+  security.declarePrivate('decrypt')
+  def decrypt(self, crypted_login):
+    """Decrypt string and return the login"""
+    login = ''
+    #check lenght of the string
+    clogin_length = len(crypted_login)
+    if clogin_length % self.block_length != 0:
+      raise ValueError, "Lenght is not good"
+
+    #decrypt block per block
+    position = 0
+    key_length = len(self.encrypted_key)
+    for block in range(0, clogin_length, self.block_length):
+      delta = position % key_length
+      crypted_letter = crypted_login[block:block + self.block_length]
+      crypted_letter = int(crypted_letter) - self.encrypted_key[delta] 
+      letter = chr(crypted_letter)
+      login += letter
+      position += 1
+    return login
+
+  ####################################
+  #ILoginPasswordHostExtractionPlugin#
+  ####################################
+  security.declarePrivate('extractCredentials')
+  def extractCredentials(self, request):
+    """ Extract credentials from cookie or 'request'. """
+    try:
+      creds = {}
+      #Search __ac_key
+      key = request.get('__ac_key', None)
+      if key is not None:     
+        creds['key'] = key
+        #Save this in cookie
+        self.updateCredentials(request,request["RESPONSE"],None,None)
+      else:
+        # Look in the request for the names coming from the login form     
+        #It's default method
+        login_pw = request._authUserPW()
+
+        if login_pw is not None:
+          name, password = login_pw
+          creds[ 'login' ] = name
+          creds[ 'password' ] = password
+          #Save this in cookie
+          self.updateCredentials(request,request["RESPONSE"],name,password)
+
+        else:
+          #search in cookies      
+          cookie = request.get(self.cookie_name, None)
+          if cookie is not None:
+            #Cookie is found
+            cookie_val = unquote(cookie)
+            creds['key'] = cookie_val
+          else:     
+            #Default cookie if needed
+            default_cookie = request.get(self.default_cookie_name, None)
+            if default_cookie is not None:
+              #Cookie is found
+              cookie_val = decodestring(unquote(default_cookie))
+              if cookie_val is not None:
+                login, password = cookie_val.split(':')
+                creds['login'] = login
+                creds['password'] = password
+
+      #Complete credential with some informations
+      if creds:
+        creds['remote_host'] = request.get('REMOTE_HOST', '')
+        try:
+            creds['remote_address'] = request.getClientAddr()
+        except AttributeError:
+            creds['remote_address'] = request.get('REMOTE_ADDR', '')
+    except StandardError,e:
+      #Log standard error to check error
+      LOG('ERP5KeyAuthPlugin.extractCredentials', PROBLEM, str(e))
+
+    return creds
+  
+  ################################
+  #   ICredentialsUpdatePlugin   #
+  ################################
+  security.declarePrivate('updateCredentials')
+  def updateCredentials(self, request, response, login, new_password):
+    """ Respond to change of credentials"""
+
+    #Update credential for key auth or standard of.
+    #Remove conflict between both systems
+    cookie_val = request.get("__ac_key",None)
+    if cookie_val is not None:
+      #expires = (DateTime() + 365).toZone('GMT').rfc822()
+      cookie_val = cookie_val.rstrip()
+      response.setCookie(self.cookie_name, quote(cookie_val), path='/')#,expires=expires)
+      response.expireCookie(self.default_cookie_name, path='/')
+    elif login is not None and new_password is not None:
+      cookie_val = encodestring('%s:%s' % (login, new_password))
+      cookie_val = cookie_val.rstrip()
+      response.setCookie(self.default_cookie_name, quote(cookie_val), path='/')
+      response.expireCookie(self.cookie_name, path='/')
+
+
+  ################################
+  #    ICredentialsResetPlugin   #
+  ################################
+  security.declarePrivate('resetCredentials')
+  def resetCredentials(self, request, response):
+    """Expire cookies of authentification """
+    response.expireCookie(self.cookie_name, path='/')    
+    response.expireCookie(self.default_cookie_name, path='/')
+
+
+  ################################
+  #     IAuthenticationPlugin    #
+  ################################
+  security.declarePrivate('authenticateCredentials')
+  def authenticateCredentials( self, credentials ):
+    """Authentificate with credentials"""
+    key = credentials.get('key', None)
+    if key != None:
+      login = self.decrypt(key)
+      # Forbidden the usage of the super user.
+      if login == SUPER_USER:
+        return None
+      
+      #Function to allow cache
+      def _authenticateCredentials(login):
+        if not login:
+          return None
+
+        #Search the user by his login
+        user_list = self.getUserByLogin(login)
+        if len(user_list) != 1:
+          raise _AuthenticationFailure()
+        user = user_list[0]
+
+        #We need to be super_user
+        sm = getSecurityManager()
+        if sm.getUser().getId() != SUPER_USER:
+          newSecurityManager(self, self.getUser(SUPER_USER))
+          try:
+            portal = self.getPortalObject()
+            # get assignment list
+            assignment_list = [x for x in user.contentValues(portal_type="Assignment") \
+                                 if x.getValidationState() == "open"]
+            valid_assignment_list = []
+            # check dates if exist
+            login_date = DateTime()
+            for assignment in assignment_list:
+              if assignment.getStartDate() is not None and \
+                        assignment.getStartDate() > login_date:
+                continue
+              if assignment.getStopDate() is not None and \
+                  assignment.getStopDate() < login_date:
+                continue
+              valid_assignment_list.append(assignment)
+
+            # validate 
+            if len(valid_assignment_list) > 0:
+              return (login,login)
+          finally:
+            setSecurityManager(sm)
+          
+          raise _AuthenticationFailure()
+
+      #Cache Method for best performance
+      _authenticateCredentials = CachingMethod(_authenticateCredentials,
+                                                id='ERP5KeyAuthPlugin_authenticateCredentials',
+                                                cache_factory='erp5_content_short')
+      try:
+          return _authenticateCredentials(
+                          login=login)
+      except _AuthenticationFailure:            
+            return None     
+      except StandardError,e:
+          #Log standard error
+          LOG('ERP5KeyAuthPlugin.authenticateCredentials', PROBLEM, str(e))
+          return None
+
+  ################################
+  # Properties for ZMI managment #
+  ################################
+
+  #'Edit' option form
+  manage_editERP5KeyAuthPluginForm = PageTemplateFile( 
+      'www/ERP5Security_editERP5KeyAuthPlugin', 
+      globals(),
+      __name__='manage_editERP5KeyAuthPluginForm' )
+  
+  security.declareProtected( ManageUsers, 'manage_editKeyAuthPlugin' )
+  def manage_editKeyAuthPlugin(self, encryption_key,cookie_name,default_cookie_name, RESPONSE=None):
+    """Edit the object"""
+    error_message = ''
+      
+    #Test paramaeters
+    if "__ac_key" in [cookie_name, default_cookie_name]:
+      raise ValueError, "Cookie name must be different of __ac_key"
+
+    #Save key
+    if encryption_key == '' or encryption_key is None:
+      error_message += 'Invalid key value '
+    else:
+      self.encryption_key = encryption_key
+      self.encrypted_key = self.transformKey(self.encryption_key);
+      
+    #Save cookie name
+    if cookie_name == '' or cookie_name is None:
+      error_message += 'Invalid cookie name '
+    else:
+      self.cookie_name = cookie_name
+
+    #Save default_cookie_name
+    if default_cookie_name == '' or default_cookie_name is None:
+      error_message += 'Invalid default cookie name '
+    else:
+      self.default_cookie_name = default_cookie_name
+
+    #Redirect
+    if RESPONSE is not None:
+      if error_message != '':
+          self.REQUEST.form['manage_tabs_message'] = error_message
+          return self.manage_editERP5KeyAuthPluginForm(RESPONSE)
+      else:
+        message = "Updated"
+        RESPONSE.redirect( '%s/manage_editERP5KeyAuthPluginForm'
+                            '?manage_tabs_message=%s'
+                            % ( self.absolute_url(), message )
+                          )
+
+#List implementation of class
+classImplements(ERP5KeyAuthPlugin,
+                ILoginEncryptionPlugin,
+                plugins.IAuthenticationPlugin,
+                plugins.ILoginPasswordHostExtractionPlugin,
+                plugins.ICredentialsResetPlugin,
+                plugins.ICredentialsUpdatePlugin)
+
+InitializeClass(ERP5KeyAuthPlugin)

Modified: erp5/trunk/products/ERP5Security/__init__.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Security/__init__.py?rev=34434&r1=34433&r2=34434&view=diff
==============================================================================
--- erp5/trunk/products/ERP5Security/__init__.py [utf8] (original)
+++ erp5/trunk/products/ERP5Security/__init__.py [utf8] Fri Apr  9 16:31:00 2010
@@ -25,6 +25,7 @@
 import ERP5GroupManager
 import ERP5RoleManager
 import ERP5UserFactory
+import ERP5KeyAuthPlugin
 
 def mergedLocalRoles(object):
   """Returns a merging of object and its ancestors'
@@ -58,6 +59,7 @@
 registerMultiPlugin(ERP5GroupManager.ERP5GroupManager.meta_type)
 registerMultiPlugin(ERP5RoleManager.ERP5RoleManager.meta_type)
 registerMultiPlugin(ERP5UserFactory.ERP5UserFactory.meta_type)
+registerMultiPlugin(ERP5KeyAuthPlugin.ERP5KeyAuthPlugin.meta_type)
 
 def initialize(context):
 
@@ -97,3 +99,12 @@
                          , icon='www/portal.gif'
                          )
 
+    context.registerClass( ERP5KeyAuthPlugin.ERP5KeyAuthPlugin
+                         , permission=ManageUsers
+                         , constructors=(
+                            ERP5KeyAuthPlugin.manage_addERP5KeyAuthPluginForm,
+                            ERP5KeyAuthPlugin.addERP5KeyAuthPlugin, )
+                         , visibility=None
+                         , icon='www/portal.gif'
+                         )
+

Modified: erp5/trunk/products/ERP5Security/tests/testERP5Security.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Security/tests/testERP5Security.py?rev=34434&r1=34433&r2=34434&view=diff
==============================================================================
--- erp5/trunk/products/ERP5Security/tests/testERP5Security.py [utf8] (original)
+++ erp5/trunk/products/ERP5Security/tests/testERP5Security.py [utf8] Fri Apr  9 16:31:00 2010
@@ -619,6 +619,56 @@
                             basic='guest:guest')
     self.assertEqual(response.getStatus(), 401)
 
+  def testKeyAuthentication(self):
+    """
+     Make sure that we can grant security using a key.
+    """
+    # add key authentication PAS plugin
+    portal = self.portal
+    uf = portal.acl_users
+    uf.manage_addProduct['ERP5Security'].addERP5KeyAuthPlugin(
+         id="erp5_auth_key", \
+         title="ERP5 Auth key",\
+         encryption_key='fdgfhkfjhltylutyu',
+         cookie_name='__key',\
+         default_cookie_name='__ac')
+
+    erp5_auth_key_plugin = getattr(uf, "erp5_auth_key")
+    erp5_auth_key_plugin.manage_activateInterfaces(
+       interfaces=['IExtractionPlugin',
+                   'IAuthenticationPlugin',
+                   'ICredentialsUpdatePlugin',
+                   'ICredentialsResetPlugin'])
+    self.stepTic()
+
+    reference = 'UserReferenceTextWhichShouldBeHardToGeneratedInAnyHumanOrComputerLanguage'
+    loginable_person = self.getPersonModule().newContent(portal_type='Person',
+                                                         reference=reference,
+                                                         password='guest')
+    assignment = loginable_person.newContent(portal_type='Assignment',
+                                             function='another_subcat')
+    assignment.open()
+    self.stepTic()
+
+    # encrypt & decrypt works 
+    key = erp5_auth_key_plugin.encrypt(reference)
+    self.assertNotEquals(reference, key)
+    self.assertEquals(reference, erp5_auth_key_plugin.decrypt(key))
+    base_url = '%s/view' %portal.absolute_url(relative=1)
+
+    # without key we are Anonymous User so we should be redirected with proper HTML
+    # status code to login_form
+    response = self.publish(base_url)
+    self.assertEqual(response.getStatus(), 302)
+    self.assertTrue('location' in response.headers.keys())
+    self.assertTrue(response.headers['location'].endswith('login_form'))
+     
+    # view front page we should be logged in if we use authentication key
+    response = self.publish('%s?__ac_key=%s' %(base_url, key))
+    self.assertEqual(response.getStatus(), 200)
+    self.assertTrue(reference in response.getBody())
+
+
 def test_suite():
   suite = unittest.TestSuite()
   suite.addTest(unittest.makeSuite(TestUserManagement))

Added: erp5/trunk/products/ERP5Security/www/ERP5Security_addERP5KeyAuthPlugin.zpt
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Security/www/ERP5Security_addERP5KeyAuthPlugin.zpt?rev=34434&view=auto
==============================================================================
--- erp5/trunk/products/ERP5Security/www/ERP5Security_addERP5KeyAuthPlugin.zpt (added)
+++ erp5/trunk/products/ERP5Security/www/ERP5Security_addERP5KeyAuthPlugin.zpt [utf8] Fri Apr  9 16:31:00 2010
@@ -1,0 +1,68 @@
+<h1 tal:replace="structure context/manage_page_header">PAGE HEADER</h1>
+<h2 tal:define="form_title string:Add ERP5 Key Authentication PAS"
+    tal:replace="structure context/manage_form_title">FORM TITLE</h2>
+
+<p class="form-help">Please input the configuration</p>
+
+<form action="addERP5KeyAuthPlugin" method="POST">
+<table cellspacing="0" cellpadding="2" border="0">
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Id
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="id" size="40" />
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Title
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="title" size="40" />
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Encryption Key
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="encryption_key" size="40" />
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Cookie Name
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="cookie_name"
+           value="__key" size="40" />
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Default Cookie Name
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="default_cookie_name" 
+           value="__ac" size="40" />
+    </td>
+  </tr>
+  <tr>
+    <td colspan="2"> <input type="submit" value="add plugin"/>
+    </td>
+  </tr>
+</table>
+</form>
+
+<h1 tal:replace="structure context/manage_page_footer">PAGE FOOTER</h1>

Added: erp5/trunk/products/ERP5Security/www/ERP5Security_editERP5KeyAuthPlugin.zpt
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Security/www/ERP5Security_editERP5KeyAuthPlugin.zpt?rev=34434&view=auto
==============================================================================
--- erp5/trunk/products/ERP5Security/www/ERP5Security_editERP5KeyAuthPlugin.zpt (added)
+++ erp5/trunk/products/ERP5Security/www/ERP5Security_editERP5KeyAuthPlugin.zpt [utf8] Fri Apr  9 16:31:00 2010
@@ -1,0 +1,45 @@
+<h1 tal:replace="structure context/manage_page_header">PAGE HEADER</h1>
+<h2 tal:replace="structure here/manage_tabs"> TABS </h2>
+<h2 tal:define="form_title string:Edit ERP5 Key Authentification Plugin"
+    tal:replace="structure context/manage_form_title">FORM TITLE</h2>
+
+<p class="form-help">Please input the configuration for the radius host</p>
+
+<form action="manage_editKeyAuthPlugin" method="POST">
+
+<table tal:define="encryption_key request/encryption_key|context/encryption_key|string:;
+                   default_cookie_name request/default_cookie_name|context/default_cookie_name|string:;
+                   cookie_name request/cookie_name|context/cookie_name|string:;">
+
+<tr>
+   <td> Encryption Key </td>
+   <td>
+     <input type="text" name="encryption_key" value=""
+            tal:attributes="value encryption_key;" />
+   </td>
+</tr>
+<tr>
+   <td> Cookie Name </td>
+   <td>
+     <input type="text" name="cookie_name" value=""
+            tal:attributes="value cookie_name;" />
+   </td>
+</tr>
+<tr>
+   <td> Default Cookie Name </td>
+   <td>
+     <input type="text" name="default_cookie_name" value=""
+            tal:attributes="value default_cookie_name;" />
+   </td>
+</tr>
+<tr>
+   <td colspan="2"> 
+    <input type="submit" value="save"/>
+   </td>
+</tr>
+
+</table>
+
+</form>
+
+<h1 tal:replace="structure context/manage_page_footer">PAGE FOOTER</h1>




More information about the Erp5-report mailing list