# Copyright (c) 2004 Infrae. All rights reserved. # See also LICENSE.txt # Python import urllib import urlparse # Zope from AccessControl import Permissions, Unauthorized # Pydavclient import dav # Railroad from Products.Railroad import interfaces, errors, utils # local modules import rrxml ### DC_NS = u'http://purl.org/dc/elements/1.1/' class Service: """Bare Railroad service object, to be used in a Zope context """ __implements__ = (interfaces.IRailroadService, ) def __init__( self, repository_url, services_url, repository_name, client_name): if not repository_url.endswith('/'): raise errors.InvalidURLError('The repository URL should end with a slash') if not services_url.endswith('/'): raise errors.InvalidURLError('The services URL should end with a slash') self._repository_url = repository_url self._services_url = services_url self._repository_name = repository_name self._client_name = client_name self._unique_id_to_path = {} self._path_to_unique_id = {} # ACCESSORS def get_railroad_service(self): """Useful to get to this service through acquisistion """ return self.aq_inner def repository_url(self): """Return the base URL of the Railroad repository. """ return self._repository_url def services_url(self): """Return the base URL for the Railroad services. """ return self._services_url def repository_name(self): """Return the name the storage was configured with. """ return self._repository_name def client_name(self): """Return the name this CMS is registered with on the Railroad server. """ return self._client_name def generate_unique_id(self, type=None): """Return a UUID as 36 character string. The optional type parameter defines which type of UUID is generated. Possible values are: 1. time - time based UUID (default) 2. random - completely random UUID 3. choose - server systems choice """ # there is a service/uuid/ on railroad now # we'll use it for now # prepare the request uuid_url = urlparse.urljoin(self.services_url(), 'uuid') + '/' params = { 'type': 'time' } if type is not None: params['type'] = type.strip().lower() data = urllib.urlencode(params) # do request an read result, # which is one line with uuid in text form r = urllib.urlopen(uuid_url, data) uuid = r.read() return uuid def resource_url_for(self, proxy): """Return the repository URL for the given proxy object. This is the canonical way to get the URL to the resource. The url is constructed out of the configured repository URL and the path of the resource on the repository. Raises ProxyNotAssignedError if there is no resource assigned to this proxy. """ resource_path = proxy.resource_path() if resource_path is None: raise errors.ProxyNotAssignedError repository_url = self.repository_url() url = urlparse.urljoin(repository_url, resource_path) return url def proxy_for_unique_id(self, unique_id): """Get the Railroad proxy object for this unique id. Raises ProxyNotFoundError if there not proxy for this unique id. """ try: path = self._unique_id_to_path[unique_id] proxy = self.restrictedTraverse(path) except KeyError, e: raise errors.ProxyNotFoundError return proxy def fetch_properties_for(self, proxy, user=None, pw=None): """Return a dict with all WebDAV properties (in unicode) Raises ProxyNotAssignedError if there is no resource assigned to this proxy. """ # unassigned proxies cannot retrieve properties if not proxy.resource_path(): raise errors.ProxyNotAssignedError url = self.resource_url_for(proxy) dfile = dav.DAVResource(url) dfile.connect() if user is None and pw is None: user, pw = self.get_user_pw(self.REQUEST) dfile._conn.set_auth(user, pw) dfile._conn._realm = self.repository_name() dfile.update() ret = dfile.get_all_properties() ret = utils.decodePropertyDict(ret) return ret def find_unowned_resources ( self ): """Return a list of resources that are currently not owned. Parameters: mime_type : Only this mime-type will be considered. If not set (None) the result will include all types. start_time, end_time : The lastmodified date/time range to consider. The returned list elements are dicts with the following keys: url : The path part of the url to this resource. mime-type : The mime-type of this resource. size : The size in bytes. lastmodified: The ISO timestamp (as string) of the modification. """ # get base service url and append search path surl = urlparse.urljoin(self.services_url(), 'search/find_unowned') params = {} form = self.REQUEST.form mime_type = form.get('mime-type') start_time = form.get('start-time') end_time = form.get('end-time') if mime_type is not None: params['mime-type'] = mime_type if start_time is not None: params['start-date'] = start_time if end_time is not None: params['end-date'] = end_time # issue request and read returned data data = urllib.urlencode(params) req = urllib.urlopen(surl, data) data = req.read() # XML parsing to follow ret = rrxml.parse_search_result(data) return ret # AUTHORIZATION def authorize(self, id, method='GET'): """Check if the proxy with the given unique id allows the request specified by method. """ # The authorization is done by calling the appropriate method on # the proxy object, which triggers Zopes security mechanism. try: proxy = self.proxy_for_unique_id(id) except errors.ProxyNotFoundError, e: # XXX should we setStatus 404 when the proxy is not found? return self._error_response(e) maydo = utils.checkPermission(self._getPermissionForMethod(method),proxy) if not maydo: # XXX shouldn't that be an instance of the exception? res = self._error_response(Unauthorized) return res ret = repr(proxy) + '\n' self.REQUEST.RESPONSE.setStatus(200) ret = ret + 'ok\n' return ret def get_user_pw(self, request): """Return the user name and password the user is logged in as. Returns two empty string in case the user is not logged in. """ try: user, pw = request._authUserPW() except TypeError: user, pw = ('', '') return (user, pw) def _getPermissionForMethod(self,method): """Returns the CMS permission that is associated with a certain RR method """ return Permissions.view def _error_response(self, exception): request = self.REQUEST response = request.RESPONSE response.setStatus(401) realm = utils.get_realm() response.setHeader('WWW-Authenticate', 'basic realm="%s"' % realm, 1) ret = repr(exception) + '\n' + 'failed\n' return ret # MANIPULATORS def register_proxy(self, proxy): """Register the given proxy object with this service instance. The registration allow the mapping between the unique id of the proxy object and the object itself. See proxy_for_unique_id. """ self._p_changed = 1 unique_id = proxy.unique_id() path = proxy.getPhysicalPath() self._path_to_unique_id[path] = unique_id self._unique_id_to_path[unique_id] = path def unregister_proxy(self, proxy): """Unregister (forget) the given proxy object. The mapping for the proxy is removed from this service instance. """ self._p_changed = 1 unique_id = proxy.unique_id() path = proxy.getPhysicalPath() del self._path_to_unique_id[path] del self._unique_id_to_path[unique_id] def set_properties_for(self, proxy, props): """Set WebDAV properties where props is a dict with unicode keys and value. The keys are tuples in the form of (name, namespace-uri). Raises ProxyNotAssignedError if there no resource assigned to this proxy. """ if not proxy.resource_path(): raise errors.ProxyNotAssignedError url = self.resource_url_for(proxy) dfile = dav.DAVResource(url) dfile.connect() user, pw = self.get_user_pw(self.REQUEST) dfile._conn.set_auth(user, pw) dfile._conn._realm = self.repository_name() props = utils.encodePropertyDict(props) res = dfile.set_properties(props) return