Issue46 - Refactoring of authentication. default tip
authorMarijn Vriens <marijn@metronomo.cl>
Sun Oct 18 09:56:42 2009 +0200 (2009-10-18)
changeset 131eef880b907f7
parent 130 f624fca07988
Issue46 - Refactoring of authentication.
Service page now only shows services the user has access to.
Service page does not require authentication.
Controller loading now done in serviceregistry
confs/development.ini
docs/configuration.rst
docs/index.rst
docs/install.rst
steward.server.egg-info/paste_deploy_config.ini_tmpl
steward/server/config/authorize.py
steward/server/config/middleware.py
steward/server/config/routing.py
steward/server/controllers/adminselector.py
steward/server/controllers/aliasselector.py
steward/server/controllers/assetselector.py
steward/server/controllers/atomselector.py
steward/server/controllers/reportselector.py
steward/server/controllers/serviceselector.py
steward/server/lib/assetcollection.py
steward/server/lib/base.py
steward/server/lib/eventcollection.py
steward/server/lib/exceptions.py
steward/server/lib/service.py
steward/server/lib/serviceregistry.py
steward/server/lib/users.py
steward/server/public/static/xsl/service.xsl
steward/server/tests/__init__.py
steward/server/tests/fixtures.py
steward/server/tests/functional/test_adminselector.py
steward/server/tests/functional/test_aliasselector.py
steward/server/tests/functional/test_assetselector.py
steward/server/tests/functional/test_atomselector.py
steward/server/tests/functional/test_reportselector.py
steward/server/tests/functional/test_serviceselector.py
steward/server/tests/stubs.py
steward/server/tests/test.ini
steward/server/tests/test_auth_servicepage.ini
steward/server/tests/test_service.py
steward/server/tests/test_serviceregistry.py
steward/server/tests/test_users.py
     1.1 --- a/confs/development.ini	Sun Aug 09 23:52:10 2009 +0200
     1.2 +++ b/confs/development.ini	Sun Oct 18 09:56:42 2009 +0200
     1.3 @@ -25,7 +25,7 @@
     1.4  authkit.setup.method = wsse
     1.5  authkit.wsse.realm = Steward
     1.6  authkit.wsse.options.allow_basic = true
     1.7 -authkit.wsse.authenticate.user.data = tester:123123 publisher
     1.8 +authkit.wsse.authenticate.user.data = tester:123123 publisher admin
     1.9  
    1.10  # If you'd like to fine-tune the individual locations of the cache data dirs
    1.11  # for the Cache data, or the Session saves, un-comment the desired settings
    1.12 @@ -42,6 +42,9 @@
    1.13  store_title = Development store
    1.14  store_description = Project test
    1.15  
    1.16 +## Should the steward service page be password protected [True | False] (default: False)
    1.17 +#protected_service_page = False
    1.18 +
    1.19  # WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*
    1.20  # Debug mode will enable the interactive debugging tool, allowing ANYONE to
    1.21  # execute malicious code after an exception is raised.
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/docs/configuration.rst	Sun Oct 18 09:56:42 2009 +0200
     2.3 @@ -0,0 +1,28 @@
     2.4 +*********************
     2.5 +Steward Configuration
     2.6 +*********************
     2.7 +
     2.8 +Steward's configuration is done in a ini file. This is a simple
     2.9 +Key-value format. 
    2.10 +
    2.11 +------------------
    2.12 +Mandatory Settings
    2.13 +------------------
    2.14 +
    2.15 +The following are obligatory for having a functional Steward
    2.16 +instalation.
    2.17 +
    2.18 + * port (Default: 5000)
    2.19 + * base_url (Default: localhost:
    2.20 + * file_store_base_path (Default: %(here)s/content)
    2.21 + * sqlalchemy.url = (Default: sqlite:///%(here)s/steward.sqlite)
    2.22 +
    2.23 +-----------------
    2.24 +Optional Settings
    2.25 +-----------------
    2.26 +
    2.27 +The following options change the standard behavior of the Steward
    2.28 +Server, but can be left alone. 
    2.29 +
    2.30 + * protected_service_page (default: False)
    2.31 +
     3.1 --- a/docs/index.rst	Sun Aug 09 23:52:10 2009 +0200
     3.2 +++ b/docs/index.rst	Sun Oct 18 09:56:42 2009 +0200
     3.3 @@ -60,6 +60,7 @@
     3.4     :maxdepth: 1
     3.5    
     3.6     install
     3.7 +   configuration
     3.8     api
     3.9     datastructure
    3.10     buildinfrastructure
     4.1 --- a/docs/install.rst	Sun Aug 09 23:52:10 2009 +0200
     4.2 +++ b/docs/install.rst	Sun Oct 18 09:56:42 2009 +0200
     4.3 @@ -29,7 +29,8 @@
     4.4  other configuration files.
     4.5  
     4.6  The configuration file is auto-commented and by default uses sqlite as
     4.7 -a database. Once the file has been edited prepare the environment by
     4.8 +a database. The Configuration document has details on the configuration 
     4.9 +file it self. Once the file has been edited prepare the environment by 
    4.10  doing::
    4.11  
    4.12   $ paster setup-app my_configuration.conf
     5.1 --- a/steward.server.egg-info/paste_deploy_config.ini_tmpl	Sun Aug 09 23:52:10 2009 +0200
     5.2 +++ b/steward.server.egg-info/paste_deploy_config.ini_tmpl	Sun Oct 18 09:56:42 2009 +0200
     5.3 @@ -71,6 +71,9 @@
     5.4  # Where do the files get stored. (/tmp) is NOT a good place
     5.5  file_store_base_path  = %(here)s/content
     5.6  
     5.7 +## Should the steward service page be password protected [True | False] (default: False)
     5.8 +#protected_service_page = False
     5.9 +
    5.10  # Buffer sizes for read and write loops.
    5.11  file_read_buffer_size = 4096
    5.12  file_write_buffer_size= 4096
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/steward/server/config/authorize.py	Sun Oct 18 09:56:42 2009 +0200
     6.3 @@ -0,0 +1,45 @@
     6.4 +# -*- coding: utf-8 -*-
     6.5 +## Copyright (C) 2008, 2009 Marijn Vriens
     6.6 +##
     6.7 +## This file is part of Steward
     6.8 +##
     6.9 +## Steward is free software: you can redistribute it and/or modify
    6.10 +## it under the terms of the GNU General Public License as published by
    6.11 +## the Free Software Foundation, either version 3 of the License, or
    6.12 +## (at your option) any later version.
    6.13 +##
    6.14 +## This program is distributed in the hope that it will be useful,
    6.15 +## but WITHOUT ANY WARRANTY; without even the implied warranty of
    6.16 +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    6.17 +## GNU General Public License for more details.
    6.18 +##
    6.19 +## You should have received a copy of the GNU General Public License
    6.20 +## along with this program.  If not, see <http://www.gnu.org/licenses/>.
    6.21 +##
    6.22 +
    6.23 +import logging
    6.24 +
    6.25 +from decorator import decorator
    6.26 +from pylons import request
    6.27 +
    6.28 +from authkit.permissions import HasAuthKitRole, ValidAuthKitUser
    6.29 +from authkit.authorize.pylons_adaptors import authorized
    6.30 +
    6.31 +log = logging.getLogger(__name__)
    6.32 +
    6.33 +def authorize():
    6.34 +    def called(func, self, environ, start_response, **kwargs):
    6.35 +        routeName = environ['routes.route'].name
    6.36 +        permission = self.DescribeUsages().getByRouteName(routeName).permission
    6.37 +        if permission and not hasattr(self, "checked"):
    6.38 +            self.checked = True;
    6.39 +            return permission.check(self, environ, start_response)
    6.40 +        return func(self, environ, start_response, **kwargs)
    6.41 +    return decorator(called)
    6.42 +
    6.43 +ValidUser = ValidAuthKitUser
    6.44 +'''User is a valid user'''
    6.45 +
    6.46 +HasRole = HasAuthKitRole
    6.47 +'''User has to have these roles to get access to the service.'''
    6.48 +
     7.1 --- a/steward/server/config/middleware.py	Sun Aug 09 23:52:10 2009 +0200
     7.2 +++ b/steward/server/config/middleware.py	Sun Oct 18 09:56:42 2009 +0200
     7.3 @@ -17,9 +17,7 @@
     7.4  from beaker.middleware import CacheMiddleware, SessionMiddleware
     7.5  from routes.middleware import RoutesMiddleware
     7.6  
     7.7 -import authkit.authorize
     7.8 -import authkit.authenticate
     7.9 -from authkit.permissions import ValidAuthKitUser
    7.10 +from authkit import authenticate 
    7.11  
    7.12  from steward.server.config.environment import load_environment
    7.13  
    7.14 @@ -74,9 +72,7 @@
    7.15  
    7.16          app = CacheMiddleware(app, config)
    7.17  
    7.18 -        permission = ValidAuthKitUser()
    7.19 -        app = authkit.authorize.middleware(app, permission)
    7.20 -        app = authkit.authenticate.middleware(app, app_conf)
    7.21 +        app = authenticate.middleware(app, app_conf)
    7.22  
    7.23          # Display error documents for 401, 403, 404 status codes (and
    7.24          # 500 when debug is disabled)
     8.1 --- a/steward/server/config/routing.py	Sun Aug 09 23:52:10 2009 +0200
     8.2 +++ b/steward/server/config/routing.py	Sun Oct 18 09:56:42 2009 +0200
     8.3 @@ -26,6 +26,8 @@
     8.4  from pylons import config
     8.5  from routes import Mapper
     8.6  
     8.7 +from steward.server.lib.serviceregistry import ServiceRegistry
     8.8 +
     8.9  def make_map():
    8.10      """Create, configure and return the routes Mapper"""
    8.11      map = Mapper(directory=config['pylons.paths']['controllers'],
    8.12 @@ -36,49 +38,10 @@
    8.13          if basePrefix != '/':
    8.14              map.prefix = basePrefix
    8.15  
    8.16 -
    8.17 -    # The ErrorController route (handles 404/500 error pages); it should
    8.18 -    # likely stay at the top, ensuring it can always be resolved
    8.19      map.connect('error/:action/:id', controller='error')
    8.20  
    8.21 -    # Report access.
    8.22 -    map.connect('report_events', 'report/events', controller='reportselector', action='getEvents')
    8.23 -    map.connect('report_changes', 'report/changes', controller='reportselector', action='getChanges')
    8.24 -
    8.25 -    # Main service page.
    8.26 -    map.connect('service_page', '', controller='serviceselector', action='index')
    8.27 -    map.connect('admin_purge', 'admin/purge', controller='adminselector', action="purge")
    8.28 -
    8.29 -    # Aliases interface.
    8.30 -    map.connect('alias_get', 'file/*name', controller='aliasselector', action='get', 
    8.31 -                conditions=dict(method=['GET']))
    8.32 -    map.connect('alias_ui', 'ui/file', controller='aliasselector', action='ui', 
    8.33 -                conditions=dict(method=['POST']))
    8.34 -    map.connect('alias_del', 'file/*name', controller='aliasselector', action='delete', 
    8.35 -                conditions=dict(method=['DELETE']))
    8.36 -
    8.37 -    # Atom Interface
    8.38 -    map.connect('atom_service', 'atom', controller='atomselector', action='atomServiceDocument',
    8.39 -                conditions=dict(method=['GET']))
    8.40 -    map.connect('atom_publish', 'atom', controller='atomselector', action='publishEntry',
    8.41 -                conditions=dict(method=['POST']))
    8.42 -    map.connect('atom_feed', 'atom/feed', controller='atomselector', action='getFeed',
    8.43 -                conditions=dict(method=['GET']))
    8.44 -    map.connect('atom_entry', 'atom/:id', controller='atomselector', action='getEntry',
    8.45 -                conditions=dict(method=['GET']))
    8.46 -
    8.47 -    # Main standard Asset interface.
    8.48 -    map.connect('asset_post', 'new', controller='assetselector', action='post', 
    8.49 -                conditions=dict(method=['POST'])) 
    8.50 -    map.connect('asset_ui', 'ui/storage', controller='assetselector', action='ui',
    8.51 -                conditions=dict(method=['POST']))
    8.52 -    map.connect('alias_post', ':id/alias', controller='aliasselector', action='post',
    8.53 -                conditions=dict(method=['POST']))
    8.54 -    map.connect('asset_get', ':id', controller='assetselector', action='get', 
    8.55 -                conditions=dict(method=['GET']))
    8.56 -    map.connect('asset_head', ':id', controller='assetselector', action='head', 
    8.57 -                conditions=dict(method=['HEAD']))
    8.58 -    map.connect('asset_del', ':id', controller='assetselector', action='delete', 
    8.59 -                conditions=dict(method=['DELETE']))
    8.60 +    for service in ServiceRegistry():
    8.61 +        service.addMapConnections(map)
    8.62  
    8.63      return map
    8.64 +    
     9.1 --- a/steward/server/controllers/adminselector.py	Sun Aug 09 23:52:10 2009 +0200
     9.2 +++ b/steward/server/controllers/adminselector.py	Sun Oct 18 09:56:42 2009 +0200
     9.3 @@ -20,13 +20,12 @@
     9.4  import logging
     9.5  from lxml import etree
     9.6  
     9.7 -from authkit.authorize.pylons_adaptors import authorize
     9.8 -from authkit.permissions import RemoteUser, ValidAuthKitUser
     9.9 +from steward.server.lib.base import BaseController, request, meta
    9.10  
    9.11  from steward.server.lib.assetcollection import AssetCollection
    9.12  from steward.server.lib.eventcollection import EventCollection
    9.13  from steward.server.lib.service import Service
    9.14 -from steward.server.lib.base import *
    9.15 +from steward.server.config.authorize import HasRole
    9.16  
    9.17  log = logging.getLogger(__name__)
    9.18  
    9.19 @@ -40,17 +39,15 @@
    9.20          '''
    9.21          Registers the use-cases of this Controller
    9.22          '''
    9.23 -        sla = Service('admin', "Administrative interface")
    9.24 -        sla.addUsage('purge', 'admin_purge', 'get', 
    9.25 -                     "Eliminate assets that are marked deleted.")
    9.26 +        sla = Service('admin', "Administrative interface", controller="adminselector", permission=HasRole("admin"))
    9.27 +        sla.addUsage('purge', "Eliminate assets that are marked deleted.", 'admin/purge', method="post")
    9.28          return sla
    9.29  
    9.30      def __init__(self):
    9.31          self.ecol = AssetCollection()
    9.32          self.events = EventCollection()
    9.33  
    9.34 -#    @authorize(HasRole('admin'))
    9.35 -    def purge(self):
    9.36 +    def purgePost(self):
    9.37          '''
    9.38          Remove the objects that were marked deleted. This will reclame their space in the store.
    9.39          '''
    10.1 --- a/steward/server/controllers/aliasselector.py	Sun Aug 09 23:52:10 2009 +0200
    10.2 +++ b/steward/server/controllers/aliasselector.py	Sun Oct 18 09:56:42 2009 +0200
    10.3 @@ -22,10 +22,12 @@
    10.4  from routes import url_for
    10.5  from sqlalchemy.exceptions import InvalidRequestError
    10.6  
    10.7 -from steward.server.lib.base import *
    10.8 +from steward.server.lib.base import BaseControllerWithUi, response, h, request, abort
    10.9  from steward.server.model import meta, Alias, Registry
   10.10  from steward.server.lib.service import Service
   10.11  
   10.12 +from steward.server.config.authorize import HasRole
   10.13 +
   10.14  log = logging.getLogger(__name__)
   10.15  
   10.16  class AliasselectorController(BaseControllerWithUi):
   10.17 @@ -38,21 +40,22 @@
   10.18          '''
   10.19          Registers the use-cases of this Controller
   10.20          '''
   10.21 -        sl = Service('aliases', "Asociate names with objects in the store.")
   10.22 -        sl.addUsage('save', 'alias_post', 'post', "Create a new alias for an asset")
   10.23 -        sl.addUsage('get', 'alias_get', 'get', "Using the alias get the related asset url")
   10.24 -        sl.addUsage('remove', 'alias_del', 'delete', "Remove the alias")
   10.25 -        sl.addFormInterface("alias_ui")
   10.26 +        sl = Service('alias', "Associate names with objects in the store.", controller="aliasselector")
   10.27 +        sl.addUsage('save', "Create a new alias for an asset", ':id/alias', method='post', permission=HasRole("publisher"))
   10.28 +        sl.addUsage('get', "Using the alias get the related asset url", 'file/*name')
   10.29 +        sl.addUsage('remove', "Remove the alias", 'file/*name', method='delete', permission=HasRole("publisher"))
   10.30 +        sl.addFormInterface("ui/file")
   10.31 +#        import pdb; pdb.set_trace()
   10.32          return sl
   10.33  
   10.34      def __init__(self, *args, **kwargs):
   10.35          super(AliasselectorController, self).__init__(*args, **kwargs)
   10.36          self.uses = {
   10.37              'save': (self.create, 201, "Alias '%(name)s' was associated with content '%(id)s'."),
   10.38 -            'remove': (self.delete, 410, "Alias '%(name)s' was removed.")
   10.39 +            'remove': (self.removeDelete, 410, "Alias '%(name)s' was removed.")
   10.40          }
   10.41  
   10.42 -    def get(self, name):
   10.43 +    def getGet(self, name):
   10.44          '''
   10.45          Given the alias returns a reference to the object associated it.
   10.46          '''
   10.47 @@ -65,8 +68,7 @@
   10.48          response.headers["Content-Length"] = len(asset_url)
   10.49          return asset_url
   10.50  
   10.51 -    @authorize(HasRole('publisher'))
   10.52 -    def post(self, id):
   10.53 +    def savePost(self, id):
   10.54          '''
   10.55          Create a new alias by posting the alias name to the object alias url.
   10.56          '''
   10.57 @@ -97,14 +99,12 @@
   10.58              except InvalidRequestError, e:
   10.59                  abort(404, e.message)
   10.60              r.aliases.append(a)
   10.61 -            meta.Session.save_or_update(r)
   10.62          location = url_for('alias_get', name=name)
   10.63          response.status_int = 201
   10.64          response.headers['Location'] = h.AbsoluteHostUrl(request) + location
   10.65          return None
   10.66  
   10.67 -    @authorize(HasRole('publisher'))
   10.68 -    def delete(self, name):
   10.69 +    def removeDelete(self, name):
   10.70          '''
   10.71          Remove an alias, deleting the association it has with an object.
   10.72          '''
    11.1 --- a/steward/server/controllers/assetselector.py	Sun Aug 09 23:52:10 2009 +0200
    11.2 +++ b/steward/server/controllers/assetselector.py	Sun Oct 18 09:56:42 2009 +0200
    11.3 @@ -42,20 +42,21 @@
    11.4          '''
    11.5          Registers the use-cases of this Controller
    11.6          '''
    11.7 -        s = Service('storage', "Save and retreive and remove assets in storage")
    11.8 -        s.addUsage('save', 'asset_post', 'post', "Add a new asset to the storage")
    11.9 -        s.addUsage('get', 'asset_get', 'get', "Retrieve an asset from storage")
   11.10 -        s.addUsage('remove', 'asset_del', 'delete', "Delete an asset from the storage")
   11.11 -        s.addUsage('check', 'asset_head', 'head', "Check status of asset in storage")
   11.12 -        s.addFormInterface('asset_ui')
   11.13 +        s = Service('asset', "Save and retrieve and remove assets in storage", controller="assetselector")
   11.14 +        s.addUsage('save', "Add a new asset to the storage", 'new', method='post', permission=HasRole("publisher"))
   11.15 +        s.addUsage('get', "Retrieve an asset from storage", '{id}', requirements={'id':R"[A-Z\d]{32}"})
   11.16 +        s.addUsage('remove', "Delete an asset from the storage", '{id}', method='delete', 
   11.17 +                   permission=HasRole("publisher"), requirements={'id':R"[A-Z\d]{32}"})
   11.18 +        s.addUsage('check', "Check status of asset in storage", '{id}', method='head', requirements={'id':R"[A-Z\d]{32}"})
   11.19 +        s.addFormInterface('ui/storage')
   11.20          return s
   11.21  
   11.22      def __init__(self):
   11.23          self.ecol = AssetCollection()
   11.24          self.events = EventCollection()
   11.25 -        self.uses = {'remove': (self.delete, 410, "Asset with id %(id)s was removed from the store.")}
   11.26 +        self.uses = {'remove': (self.removeDelete, 410, "Asset with id %(id)s was removed from the store.")}
   11.27          
   11.28 -    def getHeaders(self, id):
   11.29 +    def _getHeaders(self, id):
   11.30          '''
   11.31          When given the id it returns relevant headers of the asset.
   11.32          '''
   11.33 @@ -68,24 +69,23 @@
   11.34          response.headers['Last-Modified']  = e.date_stored.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
   11.35          return e
   11.36  
   11.37 -    def get(self, id):
   11.38 +    def getGet(self, id):
   11.39          '''
   11.40          Given the Id returns the object 
   11.41          '''
   11.42 -        e = self.getHeaders(id)
   11.43 +        e = self._getHeaders(id)
   11.44          self.events.markGet(id, request.environ.get('REMOTE_USER'))
   11.45          return e.getBinaryStream()
   11.46  
   11.47 -    def head(self, id):
   11.48 +    def checkHead(self, id):
   11.49          '''
   11.50          Returns the headers of the object.
   11.51          '''
   11.52 -        e = self.getHeaders(id)
   11.53 +        e = self._getHeaders(id)
   11.54          self.events.markCheck(id, request.environ.get('REMOTE_USER'))
   11.55          return None
   11.56  
   11.57 -    @authorize(HasRole('publisher'))
   11.58 -    def post(self):
   11.59 +    def savePost(self):
   11.60          '''
   11.61          I am the interface for storing the object.
   11.62          '''
   11.63 @@ -116,8 +116,7 @@
   11.64          else:
   11.65              return str(location)
   11.66  
   11.67 -    @authorize(HasRole('publisher'))
   11.68 -    def delete(self, id):
   11.69 +    def removeDelete(self, id):
   11.70          '''
   11.71          Through me an object with id can be made unavialable.
   11.72          '''
    12.1 --- a/steward/server/controllers/atomselector.py	Sun Aug 09 23:52:10 2009 +0200
    12.2 +++ b/steward/server/controllers/atomselector.py	Sun Oct 18 09:56:42 2009 +0200
    12.3 @@ -49,28 +49,31 @@
    12.4          '''
    12.5          Registers the use-cases of this Controller
    12.6          '''
    12.7 -        atomServ = Service('atom', "Atom protocol support.")
    12.8 -        atomServ.addUsage('service', 'atom_service', 'get', "Atom service document.")
    12.9 -        atomServ.addUsage('save', 'atom_publish', 'post', "Post an asset into the store via the atom protocol.")
   12.10 -        atomServ.addUsage('feed', 'atom_feed', 'get', "Atom feed of elements in the storage.")
   12.11 -        atomServ.addUsage('get', 'atom_entry', 'get', "Get the atom entry of asset {id}")
   12.12 +        atomServ = Service('atom', "Atom protocol support.", controller="atomselector")
   12.13 +        atomServ.addUsage('feed', "Atom feed of elements in the storage.", 'atom/feed')
   12.14 +        atomServ.addUsage('entry', "Get the atom entry of asset {id}", 'atom/{id}', 
   12.15 +                          requirements={'id':R"[A-Z\d]{32}"})
   12.16 +        atomServ.addUsage('service', "Atom service document.", "atom/index")
   12.17 +        atomServ.addUsage('publish', "Post an asset into the store via the atom protocol.", "atom", method='post', 
   12.18 +                          permission=HasRole("publisher"))
   12.19          return atomServ
   12.20      
   12.21      def __init__(self):
   12.22          self.ecol = AssetCollection()
   12.23          self.events = EventCollection()
   12.24  
   12.25 -    def atomServiceDocument(self):
   12.26 +    def serviceGet(self):
   12.27          '''
   12.28          I generate the atom service document.
   12.29          '''
   12.30 +
   12.31          srv = etree.Element(APP+"service", nsmap=NSMAP)
   12.32          workspace = etree.SubElement(srv, APP+"workspace")
   12.33          workspace.append( self._formatCollection() )
   12.34          response.headers["Content-Type"] = "application/atomsvc+xml"
   12.35          return etree.tostring(srv, xml_declaration=True, encoding="UTF-8", pretty_print=config['debug'])
   12.36  
   12.37 -    def getFeed(self):
   12.38 +    def feedGet(self):
   12.39          '''I show the Atom feed of the last documents added to the repository and am also the entry point for adding more documents.'''
   12.40          ec = EventCollection()
   12.41          response.headers['Content-Type'] = 'application/atom+xml;type=feed;charset="utf-8"' # RFC4287 #7
   12.42 @@ -122,7 +125,7 @@
   12.43          etree.SubElement(entry, ATOM+"link", rel="alternate", type=mime, href=url)
   12.44          return entry
   12.45      
   12.46 -    def publishEntry(self):
   12.47 +    def publishPost(self):
   12.48          '''I will store a document for you and return you the resulting entry in a Atom Entry format.'''
   12.49          response.status_int = 201
   12.50          response.headers['Content-Type'] ='application/atom+xml;type=entry;charset="utf-8"'
   12.51 @@ -161,10 +164,12 @@
   12.52          tree = etree.parse(req.body_file)
   12.53          return tree.xpath("/a:entry/a:link[@rel='related']/@href", namespaces=NOKIAMAP)[0]
   12.54  
   12.55 -    def getEntry(self, id):
   12.56 +    def entryGet(self, id):
   12.57          '''
   12.58          I will return you the information about one entry.
   12.59          '''
   12.60 +        if not id:
   12.61 +            abort(404, "No ID given.")
   12.62          try:
   12.63              e = self.events.getCreationEventOfAsset(id)
   12.64          except AssetNotAvailableException, e:
    13.1 --- a/steward/server/controllers/reportselector.py	Sun Aug 09 23:52:10 2009 +0200
    13.2 +++ b/steward/server/controllers/reportselector.py	Sun Oct 18 09:56:42 2009 +0200
    13.3 @@ -23,12 +23,11 @@
    13.4  
    13.5  from routes import url_for
    13.6  
    13.7 -from steward.server.lib.base import *
    13.8 +from steward.server.lib.base import BaseController, request, response, h
    13.9  from steward.server.lib.eventcollection import EventCollection
   13.10  from steward.server.lib.service import Service
   13.11  
   13.12 -from authkit.authorize.pylons_adaptors import authorize
   13.13 -from authkit.permissions import RemoteUser, ValidAuthKitUser
   13.14 +from steward.server.config.authorize import ValidUser
   13.15  
   13.16  log = logging.getLogger(__name__)
   13.17  
   13.18 @@ -42,18 +41,18 @@
   13.19          '''
   13.20          Registers the use-cases of this Controller
   13.21          '''
   13.22 -        sle = Service('reports', "Report events in the store")
   13.23 -        sle.addUsage('events', 'report_events', 'get', "List all events that have occured in the storage")
   13.24 -        sle.addUsage('changes', 'report_changes', 'get', "List all changes that have affected the storage")
   13.25 +        sle = Service('report', "Report events in the store", controller="reportselector", permission=ValidUser())
   13.26 +        sle.addUsage('events',  "List all events that have occured in the storage", 'report/events',  method='get')
   13.27 +        sle.addUsage('changes', "List all changes that have affected the storage",  'report/changes', method='get')
   13.28          return sle
   13.29  
   13.30 -    def getEvents(self):
   13.31 +    def eventsGet(self):
   13.32          '''
   13.33          Get the events that happen to the store.
   13.34          '''
   13.35          return self.genListOfEvents("events")
   13.36  
   13.37 -    def getChanges(self):
   13.38 +    def changesGet(self):
   13.39          '''
   13.40          Get the changes that have occured in the store.
   13.41          '''
    14.1 --- a/steward/server/controllers/serviceselector.py	Sun Aug 09 23:52:10 2009 +0200
    14.2 +++ b/steward/server/controllers/serviceselector.py	Sun Oct 18 09:56:42 2009 +0200
    14.3 @@ -22,7 +22,7 @@
    14.4  from pylons import config
    14.5  
    14.6  from steward.server.lib.base import *
    14.7 -from steward.server.lib.service import Services
    14.8 +from steward.server.lib.service import Services, Service
    14.9  from steward.server.lib.helpers import BaseUrlPath
   14.10  
   14.11  from adminselector import AdminselectorController
   14.12 @@ -37,16 +37,29 @@
   14.13      '''
   14.14      I generate a page that allows clients to detect what services are provided by the Steward instance.
   14.15      '''
   14.16 -    def index(self):
   14.17 +    @staticmethod
   14.18 +    def DescribeUsages():
   14.19 +        permission = ValidAuthKitUser() if ServiceselectorController.IsProtected() else None
   14.20 +        serv = Service('service', "Steward Server support pages", controller="serviceselector", permission=permission)
   14.21 +        serv.addUsage('index', "Index page", '')
   14.22 +        return serv
   14.23 +
   14.24 +    @staticmethod
   14.25 +    def IsProtected():
   14.26 +        return config.get('protected_service_page', 'false').lower() == "true" # default to false.
   14.27 +
   14.28 +    def __init__(self):
   14.29 +        self.protected = ServiceselectorController.IsProtected()
   14.30 +
   14.31 +    def indexGet(self):
   14.32          '''
   14.33          I generate the main index page.
   14.34          '''
   14.35          services = Services(config['store_title'])
   14.36 -        for controller in [AssetselectorController, AliasselectorController, AtomselectorController, 
   14.37 -                           ReportselectorController, AdminselectorController]:
   14.38 +        for controller in [ServiceselectorController, AssetselectorController, AliasselectorController, 
   14.39 +                           AtomselectorController, ReportselectorController, AdminselectorController]:
   14.40              services.addService(controller.DescribeUsages())
   14.41          response.headers['Content-Type'] = 'application/xml'
   14.42          xslUrl = BaseUrlPath() + 'static/xsl/service.xsl'
   14.43 -#        return services.toXml()
   14.44          return services.toXml(xslUrl=xslUrl)
   14.45          
    15.1 --- a/steward/server/lib/assetcollection.py	Sun Aug 09 23:52:10 2009 +0200
    15.2 +++ b/steward/server/lib/assetcollection.py	Sun Oct 18 09:56:42 2009 +0200
    15.3 @@ -67,6 +67,7 @@
    15.4          elif len(res) == 0:
    15.5              r = Registry()
    15.6              r.sha = id
    15.7 +            meta.Session.add(r)
    15.8          else:
    15.9              msg = "%s more then once in Registry!" % id
   15.10              log.error(msg)
   15.11 @@ -74,8 +75,7 @@
   15.12          r.length = length
   15.13          r.mimetype = mimetype
   15.14          r.status = 'active'
   15.15 -        meta.Session.save_or_update(r)
   15.16 -        meta.Session.commit()
   15.17 +        #meta.Session.commit()
   15.18          return r
   15.19  
   15.20      def remove(self, id):
   15.21 @@ -94,8 +94,7 @@
   15.22              log.error(msg)
   15.23              raise InternalInconsistencyException(msg)
   15.24          r.status = 'deleted'
   15.25 -        meta.Session.update(r)
   15.26 -        meta.Session.commit()
   15.27 +        #meta.Session.commit()
   15.28          return r
   15.29  
   15.30      def listDeleted(self):
   15.31 @@ -111,5 +110,4 @@
   15.32          self._store.purge(id)
   15.33          res  = meta.Session.query(Registry).filter(Registry.sha==id).one()
   15.34          res.status = 'purged'
   15.35 -        meta.Session.update(res)
   15.36 -        meta.Session.commit()
   15.37 +        #meta.Session.commit()
    16.1 --- a/steward/server/lib/base.py	Sun Aug 09 23:52:10 2009 +0200
    16.2 +++ b/steward/server/lib/base.py	Sun Oct 18 09:56:42 2009 +0200
    16.3 @@ -21,22 +21,30 @@
    16.4  Provides the BaseController class for subclassing, and other objects
    16.5  utilized by Controllers.
    16.6  """
    16.7 +
    16.8 +import logging
    16.9 +
   16.10  from pylons import c, cache, config, g, request, response, session
   16.11  from pylons.controllers import WSGIController
   16.12  from pylons.controllers.util import abort, etag_cache, redirect_to
   16.13 -from pylons.decorators import jsonify, validate
   16.14 +from pylons.decorators import jsonify
   16.15  from pylons.i18n import _, ungettext, N_
   16.16  from pylons.templating import render
   16.17  
   16.18 -from authkit.authorize.pylons_adaptors import authorize
   16.19 -from authkit.permissions import HasAuthKitRole as HasRole
   16.20 +from authkit.permissions import HasAuthKitRole as HasRole, ValidAuthKitUser
   16.21  
   16.22 +from steward.server.config.authorize import authorize
   16.23  import steward.server.lib.helpers as h
   16.24  import steward.server.model as model
   16.25  from steward.server.model import meta
   16.26  
   16.27 +log = logging.getLogger(__name__)
   16.28 +    
   16.29  class BaseController(WSGIController):
   16.30  
   16.31 +    protected=True # By default all pages are protected.
   16.32 +
   16.33 +    @authorize()
   16.34      def __call__(self, environ, start_response):
   16.35          """Invoke the Controller"""
   16.36          # WSGIController.__call__ dispatches to the Controller method
   16.37 @@ -50,7 +58,7 @@
   16.38              meta.Session.remove()
   16.39  
   16.40  class BaseControllerWithUi(BaseController):
   16.41 -    def ui(self):
   16.42 +    def uiGet(self):
   16.43          '''I parse the form and call it on to the right function in the controller.
   16.44  ``uses`` is a dictionary with the different uses with the name as the key and a tuple (funcion, success status, success message) as value.
   16.45  '''
    17.1 --- a/steward/server/lib/eventcollection.py	Sun Aug 09 23:52:10 2009 +0200
    17.2 +++ b/steward/server/lib/eventcollection.py	Sun Oct 18 09:56:42 2009 +0200
    17.3 @@ -22,10 +22,11 @@
    17.4  from lxml import etree
    17.5  
    17.6  from sqlalchemy.orm import eagerload
    17.7 +from sqlalchemy.orm.exc import NoResultFound
    17.8  from sqlalchemy.exceptions import InvalidRequestError
    17.9  
   17.10  from steward.server.model import meta, Event, Registry
   17.11 -from steward.server.lib.exceptions import InternalInconsistencyException
   17.12 +from steward.server.lib.exceptions import InternalInconsistencyException, AssetNotAvailableException
   17.13  
   17.14  log = logging.getLogger(__name__)
   17.15  
   17.16 @@ -77,14 +78,17 @@
   17.17          except InvalidRequestError, e:
   17.18              raise InternalInconsistencyException("Asset %s not found in registry" % id)
   17.19          r.events.append(e)
   17.20 -        meta.Session.save(e)
   17.21 +        meta.Session.add(e)
   17.22          return e
   17.23  
   17.24      def getCreationEventOfAsset(self, id):
   17.25          '''
   17.26          Return the creation event of a object with id.
   17.27          '''
   17.28 -        return meta.Session.query(Event).filter_by(register_id=id, action='save').order_by(Event.id.desc())[0]
   17.29 +        try:
   17.30 +            return meta.Session.query(Event).filter_by(register_id=id, action='save').order_by(Event.id.desc()).one()
   17.31 +        except NoResultFound, ex:
   17.32 +            raise AssetNotAvailableException(id)
   17.33  
   17.34      def getListOfEvents(self, id):
   17.35          '''
    18.1 --- a/steward/server/lib/exceptions.py	Sun Aug 09 23:52:10 2009 +0200
    18.2 +++ b/steward/server/lib/exceptions.py	Sun Oct 18 09:56:42 2009 +0200
    18.3 @@ -65,3 +65,8 @@
    18.4  class StoreNeedsUpgradeException(UpgradeException):
    18.5      '''I get raised when the store needs updating.'''
    18.6      pass
    18.7 +
    18.8 +class InternalException(Exception):
    18.9 +    '''I Should be raised when the code internals do unexpected things. I should just fail the server.'''
   18.10 +    pass 
   18.11 +
    19.1 --- a/steward/server/lib/service.py	Sun Aug 09 23:52:10 2009 +0200
    19.2 +++ b/steward/server/lib/service.py	Sun Oct 18 09:56:42 2009 +0200
    19.3 @@ -21,84 +21,150 @@
    19.4  from routes import url_for
    19.5  from distutils import version
    19.6  
    19.7 -from pylons import config 
    19.8 +from pylons import config, request
    19.9  
   19.10  from steward.server import __name__ as projName
   19.11  from steward.server import __version__ as projVersion
   19.12  from steward.server.lib.utils import BuildXml
   19.13 +from steward.server.lib.exceptions import InternalException
   19.14 +
   19.15 +from steward.server.config.authorize import authorized
   19.16  
   19.17  class Services(object):
   19.18      '''
   19.19      I maintain the list of services that steward clams to support. All other modules that have external interfaces should notify me so that I can update my service list document.
   19.20      '''
   19.21      def __init__(self, storeTitle):
   19.22 -        self.doc = etree.Element("services")
   19.23 -        self.doc.attrib['name'] = 'steward'
   19.24 -        self.doc.attrib['version'] = projVersion
   19.25 -
   19.26 -        href=config.get('base_url', "/")
   19.27 -        if not href.endswith("/"):
   19.28 -            href += "/"
   19.29 -        self.doc.attrib["base"] = href
   19.30 -        etree.SubElement(self.doc, "store", title=storeTitle)
   19.31 +        self._services = {}
   19.32 +        self.storeTitle = storeTitle
   19.33  
   19.34      def addService(self, serviceList):
   19.35          '''
   19.36          Add a new service to my service page. I take a serviceList as input. This should have all the details of the service. 
   19.37          '''
   19.38 -        if not isinstance(serviceList, tuple):
   19.39 +        if not isinstance(serviceList, (tuple, list)):
   19.40              serviceList = [serviceList]
   19.41          for service in serviceList:
   19.42 -            serv = etree.SubElement(self.doc, "service", name=service.name, desc=service.desc)
   19.43 -            if service.ui:
   19.44 -                serv.attrib['ui'] = self._generateRoute(service.ui)
   19.45 -            for u in service:
   19.46 -                href = self._generateRoute(u.routeName)
   19.47 -                etree.SubElement(serv, "use", name=u.name, method=u.meth.upper(), 
   19.48 -                                 href=href, desc=u.desc)
   19.49 +            self._services[service.name] = service
   19.50 +
   19.51 +    def _baseHref(self):
   19.52 +        href=config.get('base_url', "/")
   19.53 +        if not href.endswith("/"):
   19.54 +            href += "/"
   19.55 +        return href
   19.56      
   19.57 -    def _generateRoute(self, name):
   19.58 -        routes = config['routes.map']
   19.59 -        prefix = '/'
   19.60 -        return routes._routenames[name].routepath
   19.61 +    def _docHeader(self):
   19.62 +        doc = etree.Element("services")
   19.63 +        doc.attrib['name'] = 'steward'
   19.64 +        doc.attrib['version'] = projVersion
   19.65 +
   19.66 +        doc.attrib["base"] = self._baseHref()
   19.67 +        etree.SubElement(doc, "store", title=self.storeTitle)
   19.68 +        return doc
   19.69 +
   19.70 +    def _docService(self, doc, service):
   19.71 +        if service.permission is not None and authorized(service.permission) == False:
   19.72 +            return
   19.73 +        serv = etree.SubElement(doc, "service", name=service.name, desc=service.desc)
   19.74 +        for u in service:
   19.75 +            if u.permission is not None and authorized(u.permission) == False:
   19.76 +                continue
   19.77 +            if u.usageName == 'ui':
   19.78 +                serv.attrib['ui'] = u.url
   19.79 +            else:
   19.80 +                etree.SubElement(serv, "use", name=u.usageName, method=u.method.upper(), 
   19.81 +                                 href=u.url, desc=u.usageDesc)
   19.82  
   19.83      def toXml(self, xslUrl=None):
   19.84          '''
   19.85          Build a XML document for the service.
   19.86          '''
   19.87 -        return BuildXml(self.doc, xslUrl)
   19.88 +        doc = self._docHeader()
   19.89 +        for service in self._services.itervalues():
   19.90 +            self._docService(doc, service)
   19.91 +        return BuildXml(doc, xslUrl)
   19.92  
   19.93 -class Service(list):
   19.94 +class Service(object):
   19.95      '''
   19.96      One service. Each service can have multiple use-cases.
   19.97      '''
   19.98 -    def __init__(self, name, desc="Unknown"):
   19.99 +    def __init__(self, name, desc, controller=None, permission=None):
  19.100 +        self.uses = {}
  19.101 +        self.usesList = []
  19.102 +        self._usesRoute = {}
  19.103 +
  19.104          self.name = name
  19.105          self.desc = desc
  19.106 -        self.ui   = None
  19.107 +        if controller is None:
  19.108 +            raise InternalException
  19.109 +        self.controller = controller
  19.110 +        self.permission = permission
  19.111  
  19.112 -    def addUsage(self, name, routeName, meth, desc):
  19.113 +    def __iter__(self):
  19.114 +        return self.usesList.__iter__()
  19.115 +
  19.116 +    def _routeName(self, usageName):
  19.117 +        return "%s_%s" % (self.name, usageName)
  19.118 +
  19.119 +    def _defaultAction(self, usageName, method):
  19.120 +        return usageName + method.capitalize() 
  19.121 +
  19.122 +    def addUsage(self, usageName, usageDesc, url, method='get',
  19.123 +                 action=None, permission=None, requirements=None):
  19.124          '''
  19.125          Through me you can add a new use case to the service.
  19.126 +        @param usageName: name of the service usage. 
  19.127 +        @param usageDesc: Description of the service usage.
  19.128 +        @param url: The URL that the usage is available under.
  19.129 +        @param method: The HTTP method for this ['get'].
  19.130 +        @param action: The method to be invoked [Null]. 
  19.131 +        @param permission: The method to check that permissions are
  19.132 +                 granted.
  19.133 +        @param requirements: Dictionary of regular expressions that
  19.134 +                 the parameters should comply to to match
  19.135          '''
  19.136 -        self.append(ServiceUsage(name, routeName, meth, desc))
  19.137 +        routeName = self._routeName(usageName)
  19.138 +        if action is None:
  19.139 +            action = self._defaultAction(usageName, method)
  19.140 +        if permission is None:
  19.141 +            # If no usage permission is set, use the controller's permission as the usage permission.
  19.142 +            permission = self.permission
  19.143 +        if requirements is None:
  19.144 +            requirements = {}
  19.145  
  19.146 -    def addFormInterface(self, serviceUrl):
  19.147 +        d = locals()
  19.148 +        del d['self']
  19.149 +        use = ServiceUsage(d)
  19.150 +        self.uses[usageName] = use
  19.151 +        self._usesRoute[routeName] = use
  19.152 +        self.usesList.append(use)
  19.153 +
  19.154 +    def getByRouteName(self, routeName):
  19.155 +        return self._usesRoute[routeName]
  19.156 +
  19.157 +    def addFormInterface(self, interfaceUrl, permission=None):
  19.158          '''
  19.159          If the service has a interface for posting forms, the URL of that service should be added here.
  19.160          '''
  19.161 -        self.ui = serviceUrl
  19.162 +        self.addUsage('ui', 'User Interface', interfaceUrl, method=['get', 'post'], action="uiGet", permission=permission)
  19.163  
  19.164 +    def _buildCondition(self, methods):
  19.165 +        if not isinstance(methods, (list, tuple)):
  19.166 +            methods = [methods]
  19.167 +        methods = [i.upper() for i in methods]
  19.168 +        return {'method':methods}
  19.169  
  19.170 -class ServiceUsage(object):
  19.171 -    '''
  19.172 -    I am a simple container object for a service use case. 
  19.173 -    '''
  19.174 -    def __init__(self, name, routeName, meth, desc):
  19.175 -        self.name = name
  19.176 -        self.routeName = routeName
  19.177 -        self.meth = meth
  19.178 -        self.desc = desc
  19.179 +    def addMapConnections(self, map):
  19.180 +        '''
  19.181 +        I create the connections for the route object
  19.182 +        @param map The Mapper object that maintains the valid routes in the server.
  19.183 +        '''
  19.184 +        for u in self:
  19.185 +            map.connect(u.routeName, u.url, controller=self.controller, action=u.action, 
  19.186 +                        conditions=self._buildCondition(u.method), requirements=u.requirements)
  19.187  
  19.188 -    def __repr__(self):
  19.189 -        return "<%s %s>" % (type(self), self.name)
  19.190 +class ServiceUsage(dict):
  19.191 +    def __getattr__(self, name):
  19.192 +        if name in self:
  19.193 +            return self[name]
  19.194 +        raise AttributeError(name)
    20.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.2 +++ b/steward/server/lib/serviceregistry.py	Sun Oct 18 09:56:42 2009 +0200
    20.3 @@ -0,0 +1,47 @@
    20.4 +# -*- coding: utf-8 -*-
    20.5 +## Copyright (C) 2008, 2009 Marijn Vriens
    20.6 +##
    20.7 +## This file is part of Steward
    20.8 +##
    20.9 +## Steward is free software: you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by
   20.10 +## the Free Software Foundation, either version 3 of the License, or
   20.11 +## (at your option) any later version.
   20.12 +##
   20.13 +## This program is distributed in the hope that it will be useful,
   20.14 +## but WITHOUT ANY WARRANTY; without even the implied warranty of
   20.15 +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   20.16 +## GNU General Public License for more details.
   20.17 +##
   20.18 +## You should have received a copy of the GNU General Public License
   20.19 +## along with this program.  If not, see <http://www.gnu.org/licenses/>.
   20.20 +##
   20.21 +
   20.22 +import logging
   20.23 +
   20.24 +log = logging.getLogger(__name__)
   20.25 +
   20.26 +from steward.server.controllers.reportselector import ReportselectorController
   20.27 +from steward.server.controllers.serviceselector import ServiceselectorController
   20.28 +from steward.server.controllers.assetselector import AssetselectorController
   20.29 +from steward.server.controllers.atomselector import AtomselectorController
   20.30 +from steward.server.controllers.aliasselector import AliasselectorController
   20.31 +from steward.server.controllers.adminselector import AdminselectorController
   20.32 +
   20.33 +
   20.34 +_registry = None
   20.35 +
   20.36 +def ServiceRegistry():
   20.37 +    global _registry
   20.38 +    if not _registry:
   20.39 +        _registry = _ServiceRegistry()
   20.40 +    return _registry
   20.41 +
   20.42 +class _ServiceRegistry(list):
   20.43 +    def __init__(self):
   20.44 +        self.append(ReportselectorController.DescribeUsages())
   20.45 +        self.append(ServiceselectorController.DescribeUsages())
   20.46 +        self.append(AssetselectorController.DescribeUsages())
   20.47 +        self.append(AliasselectorController.DescribeUsages())
   20.48 +        self.append(AdminselectorController.DescribeUsages())
   20.49 +        self.append(AtomselectorController.DescribeUsages())
   20.50 +
    21.1 --- a/steward/server/lib/users.py	Sun Aug 09 23:52:10 2009 +0200
    21.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.3 @@ -1,51 +0,0 @@
    21.4 -# -*- coding: utf-8 -*-
    21.5 -## Copyright (C) 2008, 2009 Marijn Vriens
    21.6 -##
    21.7 -## This file is part of Steward
    21.8 -##
    21.9 -## Steward is free software: you can redistribute it and/or modify
   21.10 -## it under the terms of the GNU General Public License as published by
   21.11 -## the Free Software Foundation, either version 3 of the License, or
   21.12 -## (at your option) any later version.
   21.13 -##
   21.14 -## This program is distributed in the hope that it will be useful,
   21.15 -## but WITHOUT ANY WARRANTY; without even the implied warranty of
   21.16 -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   21.17 -## GNU General Public License for more details.
   21.18 -##
   21.19 -## You should have received a copy of the GNU General Public License
   21.20 -## along with this program.  If not, see <http://www.gnu.org/licenses/>.
   21.21 -##
   21.22 -
   21.23 -import logging
   21.24 -
   21.25 -from pylons import config
   21.26 -
   21.27 -log = logging.getLogger(__name__)
   21.28 -
   21.29 -class StewardUsers(object):
   21.30 -    '''I am the interface between the user-model and authkit'''
   21.31 -    def __init__(self, model, enc):
   21.32 -        if model == 'fake':
   21.33 -            import steward.server.tests.stubs
   21.34 -            modelCls = steward.server.tests.stubs.FakeUserModel
   21.35 -        else:
   21.36 -            raise Exception("Unknown user data model %s" % model)
   21.37 -        log.info("using %s as authentication source." % modelCls)
   21.38 -        modelConfKey = '%s_config' % model
   21.39 -        self._model = modelCls(eval(config[modelConfKey]))
   21.40 -
   21.41 -    def user_exists(self, username):
   21.42 -        return True
   21.43 -
   21.44 -    def user_has_password(self, username, password):
   21.45 -        return self._model.userHasPassword(username, password)
   21.46 -
   21.47 -    def user_password(self, username):
   21.48 -        return self._model.getPassword(username)
   21.49 -
   21.50 -    def role_exists(self, role):
   21.51 -        return True
   21.52 -
   21.53 -    def user_has_role(self, username, role):
   21.54 -        return self._model.userHasRole(username, role)
    22.1 --- a/steward/server/public/static/xsl/service.xsl	Sun Aug 09 23:52:10 2009 +0200
    22.2 +++ b/steward/server/public/static/xsl/service.xsl	Sun Oct 18 09:56:42 2009 +0200
    22.3 @@ -17,7 +17,7 @@
    22.4        </div>
    22.5        <div id="interfaces" class="section">
    22.6  	<div class="title">Simple browser based interfaces</div>
    22.7 -	<xsl:apply-templates select="/services/service[@name='storage']"/>
    22.8 +	<xsl:apply-templates select="/services/service[@name='assets']"/>
    22.9  	<xsl:apply-templates select="/services/service[@name='aliases']"/>
   22.10        </div>
   22.11        <div id="services" class="section">
   22.12 @@ -51,14 +51,14 @@
   22.13  	  </img>
   22.14        </div>
   22.15        <div class="footer">
   22.16 -	<a href="http://protocultura.cl/steward/">Steward <xsl:value-of select="/services/@version"/></a> - Simple storage and retrival of binary blobs.
   22.17 +	<a href="http://protocultura.cl/steward/">Steward <xsl:value-of select="/services/@version"/></a> - Simple storage and retrival of binary assets.
   22.18  	<br/> Copyright 2008, 2009 Marijn Vriens. This software is licensed under <a href="http://www.gnu.org/licenses/gpl-3.0.html">GPLv3</a>.<br/>
   22.19        </div>
   22.20      </body>
   22.21    </html>
   22.22  </xsl:template>
   22.23  
   22.24 -<xsl:template match="/services/service[@name='storage']">
   22.25 +<xsl:template match="/services/service[@name='assets']">
   22.26    <div class="ui">
   22.27        <table>
   22.28  	<thead>
    23.1 --- a/steward/server/tests/__init__.py	Sun Aug 09 23:52:10 2009 +0200
    23.2 +++ b/steward/server/tests/__init__.py	Sun Oct 18 09:56:42 2009 +0200
    23.3 @@ -77,13 +77,12 @@
    23.4  
    23.5      def delete(self, *args, **kwargs):
    23.6          '''Use the DELETE http method'''
    23.7 +        #print locals()
    23.8          return self._customizeRequest(super(CustomTestApp, self).delete, args, kwargs)
    23.9  
   23.10      def head(self, *args, **kwargs):
   23.11          '''Use the HEAD http method'''
   23.12 -        if 'headers' not in kwargs:
   23.13 -            kwargs['headers'] = {}
   23.14 -        AddWsseAuthorization(kwargs['headers'])
   23.15 +        AddWsseAuthorization(kwargs.setdefault('headers', {}))
   23.16          res = self._gen_request('HEAD', *args, **kwargs)
   23.17          self._customGlobalHttpAsserts(res)
   23.18          return res
   23.19 @@ -99,9 +98,7 @@
   23.20  
   23.21      def _customizeRequest(self, meth, args, kwargs):
   23.22          '''I pass the request to the TestApp, after adding some code to make testing easier.'''
   23.23 -        if 'headers' not in kwargs:
   23.24 -            kwargs['headers'] = {}
   23.25 -        AddWsseAuthorization(kwargs['headers'])
   23.26 +        AddWsseAuthorization(kwargs.setdefault('headers', {}))
   23.27          res = meth(*args, **kwargs)
   23.28          self._customGlobalHttpAsserts(res)
   23.29          return res
    24.1 --- a/steward/server/tests/fixtures.py	Sun Aug 09 23:52:10 2009 +0200
    24.2 +++ b/steward/server/tests/fixtures.py	Sun Oct 18 09:56:42 2009 +0200
    24.3 @@ -32,7 +32,7 @@
    24.4      r.length = 42
    24.5      r.mimetype = 'text/plain; charset=utf-8'
    24.6      r.status = 'active'
    24.7 -    meta.Session.save(r)
    24.8 +    meta.Session.add(r)
    24.9  
   24.10  def loadStoreFixtures(conf):
   24.11      s = Pantry(conf['file_store_base_path'])
    25.1 --- a/steward/server/tests/functional/test_adminselector.py	Sun Aug 09 23:52:10 2009 +0200
    25.2 +++ b/steward/server/tests/functional/test_adminselector.py	Sun Oct 18 09:56:42 2009 +0200
    25.3 @@ -28,14 +28,14 @@
    25.4          self.app.delete(docId2, status=410)
    25.5  
    25.6          self.assertEquals(0, self._nrOfPurges())
    25.7 -        resp = self.app.get(url_for('admin_purge'))
    25.8 +        resp = self.app.post(url_for('admin_purge'))
    25.9          self.assertEquals(2, self._nrOfPurges())
   25.10  
   25.11          tree = etree.fromstring( resp.body )
   25.12          self.assertEquals('purged',tree.xpath('/result/text()')[0])
   25.13  
   25.14      def _publish(self, data):
   25.15 -        resp = self.app.post(url_for('asset_post'), data, 
   25.16 +        resp = self.app.post(url_for('asset_save'), data, 
   25.17                               headers={'Content-Type': 'text/plain; charset=ascii'})
   25.18          return resp.header('Location')
   25.19  
    26.1 --- a/steward/server/tests/functional/test_aliasselector.py	Sun Aug 09 23:52:10 2009 +0200
    26.2 +++ b/steward/server/tests/functional/test_aliasselector.py	Sun Oct 18 09:56:42 2009 +0200
    26.3 @@ -21,9 +21,9 @@
    26.4  
    26.5  class TestServicePage(TestController):
    26.6      def test_serviceRegistered(self):
    26.7 -        response = self.app.get( url_for('service_page') )
    26.8 +        response = self.app.get( url_for('service_index') )
    26.9          respTree = etree.fromstring(response.body)
   26.10 -        aliasParts = respTree.xpath('/services/service[@name="aliases"]')
   26.11 +        aliasParts = respTree.xpath('/services/service[@name="alias"]')
   26.12          self.assertEquals(1, len(aliasParts) )
   26.13          aliasDesc = aliasParts[0]
   26.14          self.assertEquals(1, len(aliasDesc.xpath('use[@name="save"]')) )
   26.15 @@ -100,15 +100,15 @@
   26.16          self.app.get(aliasUrl, status=404)
   26.17  
   26.18      def test_deleteAlias_noexiste(self): # Try to Delete an alias that doesn't exist.
   26.19 -        self.app.delete(url_for('alias_del', name="noexiste.txt"), status=410)        
   26.20 +        self.app.delete(url_for('alias_remove', name="noexiste.txt"), status=410)        
   26.21  
   26.22      def _postAnAlias(self, assetId, name, status=201, mime='text/plain; charset=ascii'):
   26.23 -        return self.app.post(url_for('alias_post', id=assetId), name,
   26.24 +        return self.app.post(url_for('alias_save', id=assetId), name,
   26.25                               headers={'Content-Type': mime},
   26.26                               status=status)
   26.27  
   26.28      def _postAnAsset(self, data):
   26.29 -        url = url_for('asset_post')
   26.30 +        url = url_for('asset_save')
   26.31          response = self.app.post(url, data, status=201)
   26.32          return  response.header('location')
   26.33  
   26.34 @@ -136,6 +136,6 @@
   26.35          self.app.get(url_for('alias_get', name=aliasName), status=404)
   26.36  
   26.37      def _postAnAsset(self, data):
   26.38 -        url = url_for('asset_post')
   26.39 +        url = url_for('asset_save')
   26.40          response = self.app.post(url, data, status=201)
   26.41          return  response.header('location')
    27.1 --- a/steward/server/tests/functional/test_assetselector.py	Sun Aug 09 23:52:10 2009 +0200
    27.2 +++ b/steward/server/tests/functional/test_assetselector.py	Sun Oct 18 09:56:42 2009 +0200
    27.3 @@ -47,7 +47,7 @@
    27.4          self.assertEquals("", responseHead.body)
    27.5      
    27.6      def test_post(self):
    27.7 -        url = url_for('asset_post')
    27.8 +        url = url_for('asset_save')
    27.9          data = "Esto son los datos enviado via un post al servicio."
   27.10          response = self.app.post(url, data, headers={'Content-Type': 'text/plain; charset=ascii'})
   27.11          getUrl = 'http://test.example.com:5000' + url_for('asset_get', id="CYIFP555TZC2AAQXEGXP5EZQVIZVDEYO")
   27.12 @@ -59,7 +59,7 @@
   27.13          self.assertEquals(len(data), int(responseGet.header('content-length')))
   27.14          
   27.15      def test_post_twice(self):
   27.16 -        url = url_for('asset_post')
   27.17 +        url = url_for('asset_save')
   27.18          data = "Esto son los datos enviado via un post al servicio. Vamos a postearlo dos veces"
   27.19          response = self.app.post(url, data, headers={'Content-Type': 'text/plain; charset=ascii'})
   27.20          loc1  = response.header('Location')
   27.21 @@ -69,12 +69,12 @@
   27.22  
   27.23      def test_post_no_content(self):
   27.24          # Test that posting without content is not accepted.
   27.25 -        url = url_for('asset_post')
   27.26 +        url = url_for('asset_save')
   27.27          noData = ""
   27.28          response = self.app.post(url, noData, headers={'Content-Type': 'text/plain; charset=ascii'}, status=400)
   27.29      
   27.30      def test_delete(self):
   27.31 -        postUrl = url_for('asset_post')
   27.32 +        postUrl = url_for('asset_save')
   27.33          data = "Documento para ser eleminado."
   27.34          response = self.app.post(postUrl, data)
   27.35          loc  = response.header('Location')
   27.36 @@ -82,7 +82,7 @@
   27.37          response = self.app.get(loc, status=404)
   27.38  
   27.39      def test_delete_twice(self):
   27.40 -        postUrl = url_for('asset_post')
   27.41 +        postUrl = url_for('asset_save')
   27.42          data = "Documento para ser eleminado, dos veces!"
   27.43          response = self.app.post(postUrl, data)
   27.44          loc  = response.header('Location')
   27.45 @@ -90,7 +90,7 @@
   27.46          self.app.delete(loc, status=410)
   27.47   
   27.48      def test_delete_create(self):
   27.49 -        postUrl = url_for('asset_post')
   27.50 +        postUrl = url_for('asset_save')
   27.51          data = "Document to be created, removed and re-created."
   27.52          response = self.app.post(postUrl, data, headers={'Content-Type': 'text/plain; charset=ascii'})
   27.53          locA  = response.header('Location')
   27.54 @@ -103,7 +103,7 @@
   27.55  
   27.56  class TestWebInterface(TestController):
   27.57      def test_post_via_form(self):
   27.58 -        url = url_for('asset_post') + "?ui=form"
   27.59 +        url = url_for('asset_save') + "?ui=form"
   27.60          content = 'This is content uploaded via a form'
   27.61          fileData = [('file', 'filename.txt', content)]
   27.62          response = self.app.post(url, {'mimetype': 'text/plain'}, upload_files=fileData)
   27.63 @@ -125,11 +125,11 @@
   27.64          self.assertEquals(len(content), int(responseGet.header('content-length')))
   27.65  
   27.66      def test_post_missing_content(self):
   27.67 -        url = url_for('asset_post') + "?ui=form"
   27.68 +        url = url_for('asset_save') + "?ui=form"
   27.69          response = self.app.post(url, {'mimetype': 'text/plain'}, status=400)
   27.70  
   27.71      def test_delete(self):
   27.72 -        postUrl = url_for('asset_post')
   27.73 +        postUrl = url_for('asset_save')
   27.74          data = "Document to be remove via the ui"
   27.75          response = self.app.post(postUrl, data)
   27.76          objLoc  = response.header('Location')
   27.77 @@ -146,38 +146,21 @@
   27.78          self.app.post(url_for('asset_ui'), {'use': 'remove', 'wrong': 'param'}, status=400)
   27.79          self.app.post(url_for('asset_ui'), {'use': 'remove', 'id': '1231', 'extra': 'param'}, status=400)
   27.80  
   27.81 -### Example filefox3 form posting.
   27.82 -        
   27.83 -# POST /new?ui=form HTTP/1.1
   27.84 -# Host: localhost:5000
   27.85 -# User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.0.5) Gecko/2008120121 Firefox/3.0.5
   27.86 -# Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
   27.87 -# Accept-Language: en-us,en;q=0.5
   27.88 -# Accept-Encoding: gzip,deflate
   27.89 -# Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
   27.90 -# Keep-Alive: 300
   27.91 -# Connection: keep-alive
   27.92 -# Referer: http://localhost:5000/
   27.93 -# Authorization: Basic dGVzdGVyOjEyMzEyMw==
   27.94 -# Content-Type: multipart/form-data; boundary=---------------------------935817076107365704609123648
   27.95 -# Content-Length: 474
   27.96 +class TestAssetSelectorController_anon(TestController):
   27.97 +    def test_get(self):
   27.98 +        url = url_for('asset_get', id="C7VNTJYDXUF2YBA7GH5MABLUMXG2YEXT")
   27.99 +        headers = {'Authorization':None}
  27.100 +        response = self.app.get(url, headers=headers)
  27.101  
  27.102 -# -----------------------------935817076107365704609123648
  27.103 -# Content-Disposition: form-data; name="file"; filename="airport-data.txt"
  27.104 -# Content-Type: text/plain
  27.105 +    def test_post(self):
  27.106 +        url = url_for('asset_save')
  27.107 +        headers = {'Content-Type': 'text/plain; charset=ascii',
  27.108 +                   'Authorization':None, 
  27.109 +                   }
  27.110 +        data = "This is test data sent to the service."
  27.111 +        response = self.app.post(url, data, headers=headers, status=401)
  27.112  
  27.113 -# Data goes here
  27.114 -
  27.115 -# -----------------------------935817076107365704609123648
  27.116 -# Content-Disposition: form-data; name="mimetype"
  27.117 -
  27.118 -# text/plain
  27.119 -# -----------------------------935817076107365704609123648--
  27.120 -# HTTP/1.0 201 CREATED
  27.121 -# Server: PasteWSGIServer/0.5 Python/2.5.2
  27.122 -# Date: Sun, 11 Jan 2009 22:38:51 GMT
  27.123 -# location: http://localhost:5000/3I42H3S6NNFQ2MSVX7XZKYAYSCX5QBYJ
  27.124 -# pragma: no-cache
  27.125 -# cache-control: no-cache
  27.126 -# Connection: close
  27.127 -
  27.128 +    def test_delete(self):
  27.129 +        url = url_for('asset_remove', id="C7VNTJYDXUF2YBA7GH5MABLUMXG2YEXT")
  27.130 +        headers = {'Authorization':None}
  27.131 +        response = self.app.delete(url, headers=headers, status=401)
    28.1 --- a/steward/server/tests/functional/test_atomselector.py	Sun Aug 09 23:52:10 2009 +0200
    28.2 +++ b/steward/server/tests/functional/test_atomselector.py	Sun Oct 18 09:56:42 2009 +0200
    28.3 @@ -28,13 +28,13 @@
    28.4  
    28.5  class TestServicePage(TestController):
    28.6      def test_serviceRegistered(self):
    28.7 -        response = self.app.get( url_for('service_page') )
    28.8 +        response = self.app.get( url_for('service_index') )
    28.9          respTree = etree.fromstring(response.body)
   28.10          desc = respTree.xpath('/services/service[@name="atom"]')[0]
   28.11          self.assertEquals(1, len(desc.xpath('use[@name="service"][@method="GET"]')) )
   28.12 -        self.assertEquals(1, len(desc.xpath('use[@name="save"][@method="POST"]')) )
   28.13 +        self.assertEquals(1, len(desc.xpath('use[@name="publish"][@method="POST"]')) )
   28.14          self.assertEquals(1, len(desc.xpath('use[@name="feed"][@method="GET"]')) )
   28.15 -        self.assertEquals(1, len(desc.xpath('use[@name="get"][@method="GET"]')) )
   28.16 +        self.assertEquals(1, len(desc.xpath('use[@name="entry"][@method="GET"]')) )
   28.17  
   28.18  class TestCollection(TestController):
   28.19      def test_getAtomServiceDocument(self):
   28.20 @@ -105,6 +105,13 @@
   28.21          self.assertTrue( xml_compare(postEntry, getEntry,
   28.22                                       reporter=self.fail, strip_whitespaces = True ) )
   28.23  
   28.24 +    def test_entryGet_missingId(self):
   28.25 +        self.app.get(url_for("atom_entry", id=""), status=404)
   28.26 +
   28.27 +    def test_entryGet_notexisting(self):
   28.28 +        self.app.get(url_for("atom_entry", id="FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), status=404)
   28.29 +
   28.30 +
   28.31      def test_publishDocument_via_entry(self):
   28.32          xmlDoc = """<?xml version='1.0' encoding='UTF-8'?>
   28.33  <entry xmlns="http://purl.org/atom/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
   28.34 @@ -136,12 +143,12 @@
   28.35                  self.assertEquals(res, value)
   28.36  
   28.37      def _stdEvents(self): # using the std interface, until atom publishing is implemented.
   28.38 -        self.app.post(url_for('asset_post'), "Data to store.",
   28.39 +        self.app.post(url_for('asset_save'), "Data to store.",
   28.40                        headers={'Content-Type': 'text/plain; charset=ascii'}).header('Location')
   28.41 -        loc = self.app.post(url_for('asset_post'), "Data in store to be removed",
   28.42 +        loc = self.app.post(url_for('asset_save'), "Data in store to be removed",
   28.43                        headers={'Content-Type': 'text/plain; charset=ascii'}).header('Location')
   28.44          self.app.delete(loc, status=410)
   28.45 -        self.app.post(url_for('asset_post'), "Other data to save.", 
   28.46 +        self.app.post(url_for('asset_save'), "Other data to save.", 
   28.47                        headers={'Content-Type': 'text/plain; charset=ascii'})
   28.48  
   28.49  
    29.1 --- a/steward/server/tests/functional/test_reportselector.py	Sun Aug 09 23:52:10 2009 +0200
    29.2 +++ b/steward/server/tests/functional/test_reportselector.py	Sun Oct 18 09:56:42 2009 +0200
    29.3 @@ -25,8 +25,8 @@
    29.4  
    29.5  class TestServicePage(TestController):
    29.6      def test_serviceRegistered_reports(self):
    29.7 -        serviceType='reports'
    29.8 -        response = self.app.get( url_for('service_page') )
    29.9 +        serviceType='report'
   29.10 +        response = self.app.get( url_for('service_index') )
   29.11          respTree = etree.fromstring(response.body)
   29.12          service = respTree.xpath('/services/service[@name="%s"]' % serviceType)
   29.13          self.assertEquals(1, len(service) )
   29.14 @@ -122,8 +122,8 @@
   29.15      def test_getEvents_noExiste(self):
   29.16          id = "NOEXISTENOEXISTENOEXISTENOEXISTE"
   29.17          self.app.get(url_for('asset_get', id=id), status=404)
   29.18 -        self.app.get(url_for('asset_head', id=id), status=404)
   29.19 -        self.app.get(url_for('asset_del', id=id), status=404)
   29.20 +        self.app.get(url_for('asset_check', id=id), status=404)
   29.21 +        self.app.get(url_for('asset_remove', id=id), status=404)
   29.22          response = self.app.get(url_for('report_events'))
   29.23          self.assertTrue("<report/>", response)
   29.24  
   29.25 @@ -149,10 +149,10 @@
   29.26              del x.attrib['date']
   29.27  
   29.28      def _stdEvents(self):
   29.29 -        loc = self.app.post(url_for('asset_post'), "datos para guardar.",
   29.30 +        loc = self.app.post(url_for('asset_save'), "datos para guardar.",
   29.31                        headers={'Content-Type': 'text/plain; charset=ascii'}).header('Location')
   29.32          self.app.get(loc)
   29.33 -        self.app.post(url_for('asset_post'), "otro dato para guardar.", 
   29.34 +        self.app.post(url_for('asset_save'), "otro dato para guardar.", 
   29.35                        headers={'Content-Type': 'text/plain; charset=ascii'})
   29.36          self.app.head(loc)
   29.37          self.app.get(loc)
    30.1 --- a/steward/server/tests/functional/test_serviceselector.py	Sun Aug 09 23:52:10 2009 +0200
    30.2 +++ b/steward/server/tests/functional/test_serviceselector.py	Sun Oct 18 09:56:42 2009 +0200
    30.3 @@ -24,27 +24,40 @@
    30.4  from steward.server.lib.service import Service
    30.5  
    30.6  from steward.server.tests import *
    30.7 -from steward.server.tests import CreateTestApp
    30.8 +from steward.server.tests import CreateTestApp, TestModel
    30.9  
   30.10  class TestServiceselectorController(TestController):
   30.11  
   30.12 -    def test_index(self):
   30.13 -        response = self.app.get(url_for("service_page"))
   30.14 +    def test_user_index(self):
   30.15 +        response = self.app.get(url_for("service_index"))
   30.16          servicesDoc = etree.fromstring(response.body)
   30.17 -
   30.18          self.assertEquals("Unittesting memory-only Store", servicesDoc.xpath('/services/store/@title')[0])
   30.19  
   30.20 -        self.assertEquals(5, len(servicesDoc.xpath('/services/service')), 'Unexpected number of services in document.')
   30.21 +        self.assertEquals(6, len(servicesDoc.xpath('/services/service')), 'Unexpected number of services in document.')
   30.22 +        self._checkService(servicesDoc, 'service', ['index'], None)
   30.23          self._checkService(servicesDoc, 'admin', ['purge'], None)
   30.24 -        self._checkService(servicesDoc, 'aliases', ['save', 'get', 'remove'], url_for('alias_ui'))
   30.25 -        self._checkService(servicesDoc, 'atom', ['service', 'save', 'feed', 'get'], None)
   30.26 -        self._checkService(servicesDoc, 'reports', ['events', 'changes'], None)
   30.27 -        self._checkService(servicesDoc, 'storage', ['save', 'get', 'remove', 'check'], url_for('asset_ui'))
   30.28 +        self._checkService(servicesDoc, 'alias', ['save', 'get', 'remove'], url_for('alias_ui'))
   30.29 +        self._checkService(servicesDoc, 'atom', ['service', 'publish', 'feed', 'entry'], None)
   30.30 +        self._checkService(servicesDoc, 'report', ['events', 'changes'], None)
   30.31 +        self._checkService(servicesDoc, 'asset', ['save', 'get', 'remove', 'check'], url_for('asset_ui'))
   30.32  
   30.33 -        self.assertTrue(url_for("asset_post").endswith(servicesDoc.xpath("/services/service[@name='storage']/use[@name='save']/@href")[0]))
   30.34 +        self.assertTrue(url_for("asset_save").endswith(servicesDoc.xpath("/services/service[@name='asset']/use[@name='save']/@href")[0]))
   30.35  
   30.36          self.assertEquals(len(response.body), int(response.header('Content-Length')))
   30.37          
   30.38 +    def test_anonymous_index(self):
   30.39 +        response = self.app.get(url_for("service_index"), headers={'Authorization': None})
   30.40 +        servicesDoc = etree.fromstring(response.body)
   30.41 +        self.assertEquals("Unittesting memory-only Store", servicesDoc.xpath('/services/store/@title')[0])
   30.42 +
   30.43 +        self.assertEquals(4, len(servicesDoc.xpath('/services/service')), 'Unexpected number of services in document.')
   30.44 +        self._checkService(servicesDoc, 'service', ['index'], None)
   30.45 +        self._checkService(servicesDoc, 'alias', ['get'], url_for('alias_ui'))
   30.46 +        self._checkService(servicesDoc, 'atom', ['service', 'feed', 'entry'], None)
   30.47 +        self._checkService(servicesDoc, 'asset', ['get', 'check'], url_for('asset_ui'))
   30.48 +
   30.49 +        self.assertEquals(len(response.body), int(response.header('Content-Length')))
   30.50 +
   30.51      def _checkService(self, servicesDoc, serviceName, usageNameList, uiUrl):
   30.52          if uiUrl:
   30.53              self.assertTrue(uiUrl.endswith(servicesDoc.xpath("/services/service[@name='%s']/@ui" % serviceName)[0]))
   30.54 @@ -53,14 +66,14 @@
   30.55          serviceUses = servicesDoc.xpath("/services/service[@name='%s']/use" % serviceName)
   30.56          self.assertNotEquals([], serviceUses)
   30.57          for u in serviceUses:
   30.58 -            self.assertTrue(u.attrib['name'] in usageNameList, 'missing use "%s" in service document' % u.attrib['name'])
   30.59 +            self.assertTrue(u.attrib['name'] in usageNameList, "missing use '%s' in section '%s' of document" % (u.attrib['name'], serviceName))
   30.60              usageNameList.remove(u.attrib['name'])
   30.61          self.assertEquals([], usageNameList, 'Not all expected services where found in document')
   30.62  
   30.63      def test_index_xslInstruction(self, app=None):
   30.64          if app is None:
   30.65              app = self.app
   30.66 -        response = app.get(url_for("service_page"))
   30.67 +        response = app.get(url_for("service_index"))
   30.68          serviceDoc = etree.fromstring(response.body)
   30.69          
   30.70          xslPI = serviceDoc.getprevious()
   30.71 @@ -70,22 +83,27 @@
   30.72      def test_index_xslInstruction_altPath(self):
   30.73          app = CreateTestApp("server/tests/test_urlpath.ini")
   30.74          self.test_index_xslInstruction(app)
   30.75 +        app = CreateTestApp("server/tests/test.ini") # seems to be needed.
   30.76  
   30.77 +class Test_authenticatedServicePage(TestModel):
   30.78 +    def setUp(self):
   30.79 +        super(Test_authenticatedServicePage, self).setUp()
   30.80 +        self.app = CreateTestApp("server/tests/test_auth_servicepage.ini")
   30.81 +    
   30.82      def test_index_wrongWsseAuth(self):
   30.83          headers = AddWsseAuthorization(user="tester", pw="Wrong Password")
   30.84 -        self.app.get(url_for(controller='serviceselector'), headers=headers, status=401)
   30.85 +        self.app.get(url_for('service_index'), headers=headers, status=401)
   30.86  
   30.87      def test_index_basicAuth(self):
   30.88          headers = AddBasicAuthorization(user="tester", pw="Wrong Password")
   30.89 -        self.app.get(url_for(controller='serviceselector'), headers=headers, status=401)
   30.90 +        self.app.get(url_for('service_index'), headers=headers, status=401)
   30.91  
   30.92          headers = AddBasicAuthorization(user="tester", pw="123123")
   30.93 -        self.app.get(url_for(controller='serviceselector'), headers=headers, status=200)
   30.94 +        self.app.get(url_for('service_index'), headers=headers, status=200)
   30.95  
   30.96      def test_auth_headers(self):
   30.97          headers = AddBasicAuthorization(user="tester", pw="Wrong Password")
   30.98 -        response = self.app.get(url_for(controller='serviceselector'), headers=headers, status=401)
   30.99 +        response = self.app.get(url_for('service_index'), headers=headers, status=401)
  30.100          for k,v in response.headers:
  30.101              if k == 'WWW-Authenticate':
  30.102                  self.assertTrue(v.startswith('WSSE') or v.startswith('Basic'))
  30.103 -    
    31.1 --- a/steward/server/tests/stubs.py	Sun Aug 09 23:52:10 2009 +0200
    31.2 +++ b/steward/server/tests/stubs.py	Sun Oct 18 09:56:42 2009 +0200
    31.3 @@ -27,24 +27,17 @@
    31.4  
    31.5  from steward.server.lib.rfc3339 import rfc3339
    31.6  
    31.7 -class FakeUserModel(object):
    31.8 -    def __init__(self, config):
    31.9 -        pass
   31.10 -
   31.11 -    def userHasPassword(self, username, password):
   31.12 -        return username == 'tester' and password == '123123'
   31.13 -
   31.14 -    def userHasRole(self, username, role):
   31.15 -        return username == 'tester' and role == 'publisher'
   31.16 -    
   31.17 -    def getPassword(self, username):
   31.18 -        return '123123'
   31.19 -
   31.20  def AddWsseAuthorization(headers=None, user='tester', pw='123123'):
   31.21 -    '''I add the WSSE authentization header to the dictionary but only if it's not already set.'''
   31.22 +    '''I add the WSSE authentization header to the dictionary but only if it's not already set.
   31.23 +If the headers['Authentization'] has a value of None no authorization will be added and the header itself will be removed.
   31.24 +    '''
   31.25      if headers is None:
   31.26          headers = {}
   31.27 -    if 'Authorization' not in headers:
   31.28 +    if 'Authorization' in headers:
   31.29 +        if headers['Authorization'] is None: 
   31.30 +            # Authorization == None means that no authorization should be added!
   31.31 +            del headers['Authorization']
   31.32 +    else:
   31.33          nonce = hex(randint(0, 2**128))[2:-1]
   31.34          now = rfc3339( time(), utc=True )
   31.35          passDigest = b64encode( sha(nonce + now + pw).digest() )
    32.1 --- a/steward/server/tests/test.ini	Sun Aug 09 23:52:10 2009 +0200
    32.2 +++ b/steward/server/tests/test.ini	Sun Oct 18 09:56:42 2009 +0200
    32.3 @@ -27,13 +27,3 @@
    32.4  
    32.5  file_store_base_path = %(here)s/store
    32.6  
    32.7 -# Set Authentication database for fake data.
    32.8 -authkit.wsse.authenticate.user.type = steward.server.lib.users:StewardUsers
    32.9 -authkit.wsse.authenticate.user.data = fake
   32.10 -fake_config = {
   32.11 -	    'host'  : 'ldap://localhost',
   32.12 -	    'basedn': 'dc=fake,dc=cl',
   32.13 -	    'people': 'ou=people',
   32.14 -	    'groups': 'ou=group',
   32.15 -	  }
   32.16 -
    33.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    33.2 +++ b/steward/server/tests/test_auth_servicepage.ini	Sun Oct 18 09:56:42 2009 +0200
    33.3 @@ -0,0 +1,21 @@
    33.4 +#
    33.5 +# steward - Pylons testing environment configuration
    33.6 +#
    33.7 +# The %(here)s variable will be replaced with the parent directory of this file
    33.8 +#
    33.9 +[DEFAULT]
   33.10 +debug = true
   33.11 +# Uncomment and replace with the address which should receive any error reports
   33.12 +#email_to = you@yourdomain.com
   33.13 +smtp_server = localhost
   33.14 +error_email_from = paste@localhost
   33.15 +
   33.16 +[server:main]
   33.17 +use = egg:Paste#http
   33.18 +host = 0.0.0.0
   33.19 +port = 5000
   33.20 +
   33.21 +[app:main]
   33.22 +use = config:test.ini
   33.23 +
   33.24 +protected_service_page = True
    34.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    34.2 +++ b/steward/server/tests/test_service.py	Sun Oct 18 09:56:42 2009 +0200
    34.3 @@ -0,0 +1,157 @@
    34.4 +# -*- coding: utf-8 -*-
    34.5 +## Copyright (C) 2008, 2009 Marijn Vriens
    34.6 +##
    34.7 +## This file is part of Steward
    34.8 +##
    34.9 +## Steward is free software: you can redistribute it and/or modify
   34.10 +## it under the terms of the GNU General Public License as published by
   34.11 +## the Free Software Foundation, either version 3 of the License, or
   34.12 +## (at your option) any later version.
   34.13 +##
   34.14 +## This program is distributed in the hope that it will be useful,
   34.15 +## but WITHOUT ANY WARRANTY; without even the implied warranty of
   34.16 +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   34.17 +## GNU General Public License for more details.
   34.18 +##
   34.19 +## You should have received a copy of the GNU General Public License
   34.20 +## along with this program.  If not, see <http://www.gnu.org/licenses/>.
   34.21 +##
   34.22 +
   34.23 +from steward.server.tests import *
   34.24 +from xml_compare import xml_compare
   34.25 +from lxml import etree
   34.26 +from steward.server.lib.service import Service, Services
   34.27 +from steward.server.lib.exceptions import InternalException
   34.28 +
   34.29 +class TestService(TestCase):
   34.30 +    def _permChecker(self):
   34.31 +        pass
   34.32 +
   34.33 +    def setUp(self):
   34.34 +        self.s = Service("test", "a test service", controller="testController", permission=self._permChecker)
   34.35 +
   34.36 +    def testCreateService(self):
   34.37 +        s = self.s
   34.38 +        self.assertEquals(s.name, "test")
   34.39 +        self.assertEquals(s.controller, "testController")
   34.40 +        self.assertEquals(s.permission, self._permChecker)
   34.41 +        self.assertEquals(s.desc, "a test service")
   34.42 +
   34.43 +    def testCreateService_noController(self):
   34.44 +        try:
   34.45 +            Service("test", "a test service", permission=self._permChecker)
   34.46 +        except InternalException, ex:
   34.47 +            pass
   34.48 +        else:
   34.49 +            self.fail("Should have raised exception")
   34.50 +
   34.51 +    def testAddUsage(self):
   34.52 +        self.s.addUsage("usage", "sample usage", "test/use")
   34.53 +        r = self.s.uses['usage']
   34.54 +        self.assertEquals(r.usageName, 'usage')
   34.55 +        self.assertEquals(r.usageDesc, "sample usage")
   34.56 +        self.assertEquals(r.url, "test/use")
   34.57 +        self.assertEquals(r.method, "get")
   34.58 +        self.assertEquals(r.action, "usageGet")
   34.59 +        self.assertEquals(r.routeName, "test_usage")
   34.60 +
   34.61 +    def testAddUsage_method(self): 
   34.62 +        self.s.addUsage("usage", "sample usage", "test/use", method="delete", action="fooBar")
   34.63 +        r = self.s.uses['usage']
   34.64 +        self.assertEquals(r.method, "delete")
   34.65 +        self.assertEquals(r.action, "fooBar")
   34.66 +
   34.67 +    def testAddMapConnections(self):
   34.68 +        self.s.addUsage("usage", "sample usage", "test/use")
   34.69 +        self.s.addUsage("usage2", "sample usage2", "test/use2")
   34.70 +        mapper = FakeMapper()
   34.71 +        self.s.addMapConnections(mapper)
   34.72 +
   34.73 +        r1 = self.s.uses['usage']
   34.74 +        self.assertEquals(r1.routeName, mapper.d[0]['routeName'])
   34.75 +        self.assertEquals(r1.url, mapper.d[0]['url'])
   34.76 +        self.assertEquals(self.s.controller, mapper.d[0]['controller'])
   34.77 +        self.assertEquals("usageGet", mapper.d[0]['action'])
   34.78 +
   34.79 +    def testAddUsage_requrements(self):
   34.80 +        self.s.addUsage("usage", "requirement usage", "test/{id}", requirements={'id':R"\d{4}"})
   34.81 +        r = self.s.uses['usage']
   34.82 +        self.assertEquals(R'\d{4}', r.requirements['id'])
   34.83 +        mapper = FakeMapper()
   34.84 +        self.s.addMapConnections(mapper)
   34.85 +        self.assertEquals({'id': R'\d{4}'}, mapper.d[0]['requirements'])
   34.86 +
   34.87 +    def testAddFormInterface(self):
   34.88 +        self.s.addFormInterface("test/ui")
   34.89 +        mapper = FakeMapper()
   34.90 +        self.s.addMapConnections(mapper)
   34.91 +        self.assertEquals('test_ui', mapper.d[0]['routeName'])
   34.92 +        self.assertEquals('test/ui', mapper.d[0]['url'])
   34.93 +        self.assertEquals('uiGet', mapper.d[0]['action'])
   34.94 +        
   34.95 +    def testGetByRouteName(self):
   34.96 +        self.s.addUsage("usage", "sample usage", "test/use")
   34.97 +        self.s.addUsage("usage2", "sample usage2", "test/use2")
   34.98 +        self.assertEquals('usageGet',  self.s.getByRouteName('test_usage').action)
   34.99 +        self.assertEquals('usage2Get', self.s.getByRouteName('test_usage2').action)        
  34.100 +
  34.101 +
  34.102 +class TestService_permissions(TestCase):
  34.103 +    def _permChecker(self):
  34.104 +        pass
  34.105 +
  34.106 +    def test_default(self):
  34.107 +        self.s = Service("test", "a test service", controller="testController")
  34.108 +        self.s.addUsage("default", "sample usage", "test/use")
  34.109 +        r = self.s.uses['default']
  34.110 +        self.assertEquals(None, r.permission)
  34.111 +
  34.112 +        self.s.addUsage("checked", "sample usage", "test/use", permission=self._permChecker)
  34.113 +        r = self.s.uses['checked']
  34.114 +        self.assertEquals(self._permChecker, r.permission)
  34.115 +
  34.116 +    def test_checkedController(self):
  34.117 +        self.s = Service("test", "a test service", controller="testController", permission=self._permChecker)
  34.118 +        self.s.addUsage("default", "sample usage", "test/use")
  34.119 +        r = self.s.uses['default']
  34.120 +        self.assertEquals(self._permChecker, r.permission)
  34.121 +
  34.122 +
  34.123 +class TestServices(TestCase):
  34.124 +    def setUp(self):
  34.125 +        self.sList = Services("unittest store")
  34.126 +    def testAddService(self):
  34.127 +        s1 = Service("foo", "foo service", controller="testController")
  34.128 +        s2 = Service("bar", "bar service", controller="testController")
  34.129 +        self.sList.addService(s1)
  34.130 +        self.sList.addService([s2])
  34.131 +
  34.132 +    def testToXml(self):
  34.133 +        s1 = Service("foo", "a foo service", controller="testController")
  34.134 +        s1.addUsage("use1", "some usage", "foo/use1", method="post")
  34.135 +        s1.addUsage("use2", "some usage2", "foo/use1", method="get")
  34.136 +        s2 = Service("bar", "a bar service", controller="testController")
  34.137 +        self.sList.addService([s1, s2])
  34.138 +        received = etree.fromstring(self.sList.toXml())
  34.139 +        self.assertTrue(xml_compare(self.expectedXml(), received))
  34.140 +
  34.141 +    def expectedXml(self):
  34.142 +        from steward.server import __version__ as projVersion
  34.143 +        href = self.sList._baseHref()
  34.144 +        return etree.fromstring("""<?xml version='1.0' encoding='utf-8'?>
  34.145 +<services name="steward" version="%s" base="%s">
  34.146 +  <store title="unittest store"/>
  34.147 +  <service name="foo" desc="a foo service">
  34.148 +    <use desc="some usage" href="foo/use1" method="POST" name="use1"/>
  34.149 +    <use desc="some usage2" href="foo/use1" method="GET" name="use2"/>
  34.150 +  </service>
  34.151 +  <service name="bar" desc="a bar service"/>
  34.152 +</services>""" % (projVersion, href))
  34.153 +
  34.154 +class FakeMapper(object):
  34.155 +    def __init__(self):
  34.156 +        self.d = []
  34.157 +    def connect(self, routeName, url, controller=None, action=None, conditions=None, requirements=None):
  34.158 +        assert(conditions != None)
  34.159 +        self.d.append(locals())
  34.160 +
    35.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    35.2 +++ b/steward/server/tests/test_serviceregistry.py	Sun Oct 18 09:56:42 2009 +0200
    35.3 @@ -0,0 +1,26 @@
    35.4 +# -*- coding: utf-8 -*-
    35.5 +## Copyright (C) 2008, 2009 Marijn Vriens
    35.6 +##
    35.7 +## This file is part of Steward
    35.8 +##
    35.9 +## Steward is free software: you can redistribute it and/or modify
   35.10 +## it under the terms of the GNU General Public License as published by
   35.11 +## the Free Software Foundation, either version 3 of the License, or
   35.12 +## (at your option) any later version.
   35.13 +##
   35.14 +## This program is distributed in the hope that it will be useful,
   35.15 +## but WITHOUT ANY WARRANTY; without even the implied warranty of
   35.16 +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   35.17 +## GNU General Public License for more details.
   35.18 +##
   35.19 +## You should have received a copy of the GNU General Public License
   35.20 +## along with this program.  If not, see <http://www.gnu.org/licenses/>.
   35.21 +##
   35.22 +
   35.23 +from steward.server.tests import *
   35.24 +
   35.25 +from steward.server.lib.serviceregistry import ServiceRegistry
   35.26 +
   35.27 +class TestService(TestCase):
   35.28 +    def test_create(self):
   35.29 +        sr = ServiceRegistry()
    36.1 --- a/steward/server/tests/test_users.py	Sun Aug 09 23:52:10 2009 +0200
    36.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    36.3 @@ -1,43 +0,0 @@
    36.4 -# -*- coding: utf-8 -*-
    36.5 -## Copyright (C) 2008, 2009 Marijn Vriens
    36.6 -##
    36.7 -## This file is part of Steward
    36.8 -##
    36.9 -## Steward is free software: you can redistribute it and/or modify
   36.10 -## it under the terms of the GNU General Public License as published by
   36.11 -## the Free Software Foundation, either version 3 of the License, or
   36.12 -## (at your option) any later version.
   36.13 -##
   36.14 -## This program is distributed in the hope that it will be useful,
   36.15 -## but WITHOUT ANY WARRANTY; without even the implied warranty of
   36.16 -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   36.17 -## GNU General Public License for more details.
   36.18 -##
   36.19 -## You should have received a copy of the GNU General Public License
   36.20 -## along with this program.  If not, see <http://www.gnu.org/licenses/>.
   36.21 -##
   36.22 -
   36.23 -from steward.server.tests import *
   36.24 -
   36.25 -from steward.server.lib.users import StewardUsers
   36.26 -
   36.27 -class TestStewardUsers(TestCase):
   36.28 -    def setUp(self):
   36.29 -        self.s = StewardUsers('fake', '')
   36.30 -
   36.31 -    def test_user_exists(self):
   36.32 -        self.assertTrue( self.s.user_exists("tester") )
   36.33 -
   36.34 -    def test_user_has_password(self):
   36.35 -        self.assertTrue(  self.s.user_has_password("tester", "123123") )
   36.36 -        self.assertFalse( self.s.user_has_password("tester", "Wrong") )
   36.37 -        self.assertFalse( self.s.user_has_password("noexiste", "123123") )
   36.38 -        self.assertFalse( self.s.user_has_password("noexiste", "Wrong") )
   36.39 -
   36.40 -    def test_user_has_role(self):
   36.41 -        self.assertTrue(  self.s.user_has_role("tester", "publisher") )
   36.42 -        self.assertFalse( self.s.user_has_role("tester", "noexiste") )
   36.43 -        self.assertFalse( self.s.user_has_role("noexiste", "noexiste") )
   36.44 -
   36.45 -    def test_creation_wrongType(self):
   36.46 -        self.assertRaises(Exception, StewardUsers, 'wrong', '')