Commit 7ea5fc71 authored by Hanno Schlichting's avatar Hanno Schlichting

Move `Products.SiteAccess` into ZServer distribution.

parent 18ef29ca
...@@ -30,6 +30,8 @@ Features Added ...@@ -30,6 +30,8 @@ Features Added
Restructuring Restructuring
+++++++++++++ +++++++++++++
- Move `Products.SiteAccess` into ZServer distribution.
- Simplify Page Template and Scripts ZMI screens. - Simplify Page Template and Scripts ZMI screens.
- Change VHM id to `virtual_hosting` to match AppInitializer. - Change VHM id to `virtual_hosting` to match AppInitializer.
......
...@@ -213,7 +213,6 @@ class AppInitializer: ...@@ -213,7 +213,6 @@ class AppInitializer:
self.install_inituser() self.install_inituser()
self.install_products() self.install_products()
self.install_standards() self.install_standards()
self.install_virtual_hosting()
def install_app_manager(self): def install_app_manager(self):
global APP_MANAGER global APP_MANAGER
...@@ -267,19 +266,6 @@ class AppInitializer: ...@@ -267,19 +266,6 @@ class AppInitializer:
transaction.get().note('Migrated user folder') transaction.get().note('Migrated user folder')
transaction.commit() transaction.commit()
def install_virtual_hosting(self):
app = self.getApp()
if 'virtual_hosting' not in app:
from Products.SiteAccess.VirtualHostMonster \
import VirtualHostMonster
any_vhm = [obj for obj in app.values()
if isinstance(obj, VirtualHostMonster)]
if not any_vhm:
vhm = VirtualHostMonster()
vhm.id = 'virtual_hosting'
vhm.addToContainer(app)
self.commit('Added virtual_hosting')
def install_products(self): def install_products(self):
return install_products() return install_products()
......
...@@ -86,15 +86,6 @@ class TestInitialization(unittest.TestCase): ...@@ -86,15 +86,6 @@ class TestInitialization(unittest.TestCase):
app = getApp() app = getApp()
return AppInitializer(app) return AppInitializer(app)
def test_install_virtual_hosting(self):
self.configure(good_cfg)
i = self.getOne()
i.install_virtual_hosting()
app = i.getApp()
self.assertTrue('virtual_hosting' in app)
self.assertEqual(
app.virtual_hosting.meta_type, 'Virtual Host Monster')
def test_install_required_roles(self): def test_install_required_roles(self):
self.configure(good_cfg) self.configure(good_cfg)
i = self.getOne() i = self.getOne()
......
"""VirtualHostMonster module
Defines the VirtualHostMonster class
"""
from AccessControl.class_init import InitializeClass
from AccessControl.Permissions import view as View # NOQA
from AccessControl.SecurityInfo import ClassSecurityInfo
from Acquisition import Implicit
from App.special_dtml import DTMLFile
from OFS.SimpleItem import Item
from Persistence import Persistent
from ZPublisher.BeforeTraverse import NameCaller
from ZPublisher.BeforeTraverse import queryBeforeTraverse
from ZPublisher.BeforeTraverse import registerBeforeTraverse
from ZPublisher.BeforeTraverse import unregisterBeforeTraverse
from ZPublisher.BaseRequest import quote
from zExceptions import BadRequest
class VirtualHostMonster(Persistent, Item, Implicit):
"""Provide a simple drop-in solution for virtual hosting.
"""
meta_type = 'Virtual Host Monster'
priority = 25
id = 'virtual_hosting'
title = ''
lines = ()
have_map = 0
security = ClassSecurityInfo()
manage_options = (
{'label': 'About', 'action': 'manage_main'},
{'label': 'Mappings', 'action': 'manage_edit'},
)
security.declareProtected(View, 'manage_main')
manage_main = DTMLFile('www/VirtualHostMonster', globals(),
__name__='manage_main')
security.declareProtected('Add Site Roots', 'manage_edit')
manage_edit = DTMLFile('www/manage_edit', globals())
security.declareProtected('Add Site Roots', 'set_map')
def set_map(self, map_text, RESPONSE=None):
"Set domain to path mappings."
lines = map_text.split('\n')
self.fixed_map = fixed_map = {}
self.sub_map = sub_map = {}
new_lines = []
for line in lines:
line = line.split('#!')[0].strip()
if not line:
continue
try:
# Drop the protocol, if any
line = line.split('://')[-1]
try:
host, path = [x.strip() for x in line.split('/', 1)]
except:
raise ValueError(
'Line needs a slash between host and path: %s' % line)
pp = filter(None, path.split('/'))
if pp:
obpath = pp[:]
if obpath[0] == 'VirtualHostBase':
obpath = obpath[3:]
if 'VirtualHostRoot' in obpath:
i1 = obpath.index('VirtualHostRoot')
i2 = i1 + 1
while i2 < len(obpath) and obpath[i2][:4] == '_vh_':
i2 = i2 + 1
del obpath[i1:i2]
if obpath:
try:
ob = self.unrestrictedTraverse(obpath)
except:
raise ValueError(
'Path not found: %s' % obpath)
if not getattr(ob.aq_base, 'isAnObjectManager', 0):
raise ValueError(
'Path must lead to an Object Manager: %s' %
obpath)
if 'VirtualHostRoot' not in pp:
pp.append('/')
pp.reverse()
try:
int(host.replace('.', ''))
raise ValueError(
'IP addresses are not mappable: %s' % host)
except ValueError:
pass
if host[:2] == '*.':
host_map = sub_map
host = host[2:]
else:
host_map = fixed_map
hostname, port = (host.split(':', 1) + [None])[:2]
if hostname not in host_map:
host_map[hostname] = {}
host_map[hostname][port] = pp
except 'LineError' as msg:
line = '%s #! %s' % (line, msg)
new_lines.append(line)
self.lines = tuple(new_lines)
self.have_map = bool(fixed_map or sub_map) # booleanize
if RESPONSE is not None:
RESPONSE.redirect(
'manage_edit?manage_tabs_message=Changes%20Saved.')
def addToContainer(self, container):
container._setObject(self.id, self)
def manage_addToContainer(self, container, nextURL=''):
self.addToContainer(container)
def manage_beforeDelete(self, item, container):
if item is self:
unregisterBeforeTraverse(container, self.meta_type)
def manage_afterAdd(self, item, container):
if item is self:
if queryBeforeTraverse(container, self.meta_type):
raise BadRequest('This container already has a %s' %
self.meta_type)
id = self.id
if callable(id):
id = id()
# We want the original object, not stuff in between
container = container.this()
hook = NameCaller(id)
registerBeforeTraverse(
container, hook, self.meta_type, self.priority)
def __call__(self, client, request, response=None):
'''Traversing at home'''
vh_used = 0
stack = request['TraversalRequestNameStack']
path = None
while 1:
if stack and stack[-1] == 'VirtualHostBase':
vh_used = 1
stack.pop()
protocol = stack.pop()
host = stack.pop()
if ':' in host:
host, port = host.split(':')
request.setServerURL(protocol, host, port)
else:
request.setServerURL(protocol, host)
path = list(stack)
# Find and convert VirtualHostRoot directive
# If it is followed by one or more path elements that each
# start with '_vh_', use them to construct the path to the
# virtual root.
vh = -1
for ii in range(len(stack)):
if stack[ii] == 'VirtualHostRoot':
vh_used = 1
pp = ['']
at_end = (ii == len(stack) - 1)
if vh >= 0:
for jj in range(vh, ii):
pp.insert(1, stack[jj][4:])
stack[vh:ii + 1] = ['/'.join(pp), self.id]
ii = vh + 1
elif ii > 0 and stack[ii - 1][:1] == '/':
pp = stack[ii - 1].split('/')
stack[ii] = self.id
else:
stack[ii] = self.id
stack.insert(ii, '/')
ii += 1
path = stack[:ii]
# If the directive is on top of the stack, go ahead
# and process it right away.
if at_end:
request.setVirtualRoot(pp)
del stack[-2:]
break
elif vh < 0 and stack[ii][:4] == '_vh_':
vh = ii
if vh_used or not self.have_map:
if path is not None:
path.reverse()
vh_part = ''
if path and path[0].startswith('/'):
vh_part = path.pop(0)[1:]
if vh_part:
request['VIRTUAL_URL_PARTS'] = vup = (
request['SERVER_URL'],
vh_part, quote('/'.join(path)))
else:
request['VIRTUAL_URL_PARTS'] = vup = (
request['SERVER_URL'], quote('/'.join(path)))
request['VIRTUAL_URL'] = '/'.join(vup)
# new ACTUAL_URL
add = (path and
request['ACTUAL_URL'].endswith('/')) and '/' or ''
request['ACTUAL_URL'] = request['VIRTUAL_URL'] + add
return
vh_used = 1 # Only retry once.
# Try to apply the host map if one exists, and if no
# VirtualHost directives were found.
host = request['SERVER_URL'].split('://')[1].lower()
hostname, port = (host.split(':', 1) + [None])[:2]
ports = self.fixed_map.get(hostname, 0)
if not ports and self.sub_map:
get = self.sub_map.get
while hostname:
ports = get(hostname, 0)
if ports:
break
if '.' not in hostname:
return
hostname = hostname.split('.', 1)[1]
if ports:
pp = ports.get(port, 0)
if pp == 0 and port is not None:
# Try default port
pp = ports.get(None, 0)
if not pp:
return
# If there was no explicit VirtualHostRoot, add one at the end
if pp[0] == '/':
pp = pp[:]
pp.insert(1, self.id)
stack.extend(pp)
def __bobo_traverse__(self, request, name):
'''Traversing away'''
if name[:1] != '/':
return getattr(self, name)
parents = request.PARENTS
parents.pop() # I don't belong there
if len(name) > 1:
request.setVirtualRoot(name)
else:
request.setVirtualRoot([])
return parents.pop() # He'll get put back on
InitializeClass(VirtualHostMonster)
def manage_addVirtualHostMonster(self, id=None, REQUEST=None, **ignored):
""" """
container = self.this()
vhm = VirtualHostMonster()
container._setObject(vhm.getId(), vhm)
if REQUEST is not None:
goto = '%s/manage_main' % self.absolute_url()
qs = 'manage_tabs_message=Virtual+Host+Monster+added.'
REQUEST['RESPONSE'].redirect('%s?%s' % (goto, qs))
constructors = (
('manage_addVirtualHostMonster', manage_addVirtualHostMonster),
)
def initialize(context):
import VirtualHostMonster
context.registerClass(
instance_class=VirtualHostMonster.VirtualHostMonster,
permission='Add Virtual Host Monsters',
constructors=VirtualHostMonster.constructors,
)
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five">
<five:deprecatedManageAddDelete
class="Products.SiteAccess.VirtualHostMonster.VirtualHostMonster"/>
</configure>
"""Virtual Host Monster regression tests.
These tests mainly verify that OFS.Traversable.absolute_url()
works correctly in a VHM environment.
Also see http://zope.org/Collectors/Zope/809
"""
import unittest
class VHMRegressions(unittest.TestCase):
def setUp(self):
import transaction
from Testing.makerequest import makerequest
from Testing.ZopeTestCase.ZopeLite import app
transaction.begin()
self.app = makerequest(app())
if 'virtual_hosting' not in self.app.objectIds():
# If ZopeLite was imported, we have no default virtual
# host monster
from Products.SiteAccess.VirtualHostMonster \
import manage_addVirtualHostMonster
manage_addVirtualHostMonster(self.app, 'virtual_hosting')
self.app.manage_addFolder('folder')
self.app.folder.manage_addDTMLMethod('doc', '')
self.app.REQUEST.set('PARENTS', [self.app])
self.traverse = self.app.REQUEST.traverse
def tearDown(self):
import transaction
transaction.abort()
self.app._p_jar.close()
def testAbsoluteUrl(self):
m = self.app.folder.doc.absolute_url
self.assertEqual(m(), 'http://nohost/folder/doc')
def testAbsoluteUrlPath(self):
m = self.app.folder.doc.absolute_url_path
self.assertEqual(m(), '/folder/doc')
def testVirtualUrlPath(self):
m = self.app.folder.doc.absolute_url
self.assertEqual(m(relative=1), 'folder/doc')
m = self.app.folder.doc.virtual_url_path
self.assertEqual(m(), 'folder/doc')
def testPhysicalPath(self):
m = self.app.folder.doc.getPhysicalPath
self.assertEqual(m(), ('', 'folder', 'doc'))
def test_actual_url_no_VHR_no_doc_w_trailing_slash(self):
self.traverse('/VirtualHostBase/http/www.mysite.com:80'
'/folder/')
self.assertEqual(self.app.REQUEST['ACTUAL_URL'],
'http://www.mysite.com/folder/')
def test_actual_url_no_VHR_no_doc_no_trailing_slash(self):
self.traverse('/VirtualHostBase/http/www.mysite.com:80'
'/folder')
self.assertEqual(self.app.REQUEST['ACTUAL_URL'],
'http://www.mysite.com/folder')
def test_actual_url_no_VHR_w_doc_w_trailing_slash(self):
self.traverse('/VirtualHostBase/http/www.mysite.com:80'
'/folder/doc/')
self.assertEqual(self.app.REQUEST['ACTUAL_URL'],
'http://www.mysite.com/folder/doc/')
def test_actual_url_no_VHR_w_doc_no_trailing_slash(self):
self.traverse('/VirtualHostBase/http/www.mysite.com:80'
'/folder/doc')
self.assertEqual(self.app.REQUEST['ACTUAL_URL'],
'http://www.mysite.com/folder/doc')
def test_actual_url_w_VHR_w_doc_w_trailing_slash(self):
self.traverse('/VirtualHostBase/http/www.mysite.com:80'
'/folder/VirtualHostRoot/doc/')
self.assertEqual(self.app.REQUEST['ACTUAL_URL'],
'http://www.mysite.com/doc/')
def test_actual_url_w_VHR_w_doc_no_trailing_slash(self):
self.traverse('/VirtualHostBase/http/www.mysite.com:80'
'/folder/VirtualHostRoot/doc')
self.assertEqual(self.app.REQUEST['ACTUAL_URL'],
'http://www.mysite.com/doc')
def test_actual_url_w_VHR_no_doc_w_trailing_slash(self):
self.traverse('/VirtualHostBase/http/www.mysite.com:80'
'/folder/VirtualHostRoot/')
self.assertEqual(self.app.REQUEST['ACTUAL_URL'],
'http://www.mysite.com/')
def test_actual_url_w_VHR_no_doc_no_trailing_slash(self):
self.traverse('/VirtualHostBase/http/www.mysite.com:80'
'/folder/VirtualHostRoot')
self.assertEqual(self.app.REQUEST['ACTUAL_URL'],
'http://www.mysite.com/')
def gen_cases():
for vbase, ubase in (
('', 'http://nohost'),
('/VirtualHostBase/http/example.com:80', 'http://example.com')):
yield vbase, '', '', 'folder/doc', ubase
for vr, _vh, p in (
('folder', '', 'doc'),
('folder', 'foo', 'doc'),
('', 'foo', 'folder/doc')):
vparts = [vbase, vr, 'VirtualHostRoot']
if not vr:
del vparts[1]
if _vh:
vparts.append('_vh_' + _vh)
yield '/'.join(vparts), vr, _vh, p, ubase
for i, (vaddr, vr, _vh, p, ubase) in enumerate(gen_cases()):
def test(self, vaddr=vaddr, vr=vr, _vh=_vh, p=p, ubase=ubase):
ob = self.traverse('%s/%s/' % (vaddr, p))
sl_vh = (_vh and ('/' + _vh))
aup = sl_vh + (p and ('/' + p))
self.assertEqual(ob.absolute_url_path(), aup)
self.assertEqual(self.app.REQUEST['BASEPATH1'] + '/' + p, aup)
self.assertEqual(ob.absolute_url(), ubase + aup)
self.assertEqual(ob.absolute_url(relative=1), p)
self.assertEqual(ob.virtual_url_path(), p)
self.assertEqual(ob.getPhysicalPath(), ('', 'folder', 'doc'))
app = ob.aq_parent.aq_parent
# The absolute URL doesn't end with a slash
self.assertEqual(app.absolute_url(), ubase + sl_vh)
# The absolute URL path always begins with a slash
self.assertEqual(app.absolute_url_path(), '/' + _vh)
self.assertEqual(app.absolute_url(relative=1), '')
self.assertEqual(app.virtual_url_path(), '')
setattr(VHMRegressions, 'testTraverse%s' % i, test)
class VHMAddingTests(unittest.TestCase):
def setUp(self):
from OFS.Folder import Folder
super(VHMAddingTests, self).setUp()
self.root = Folder('root')
def _makeOne(self):
from Products.SiteAccess.VirtualHostMonster import VirtualHostMonster
return VirtualHostMonster()
def test_add_with_existing_vhm(self):
from Products.SiteAccess.VirtualHostMonster import \
manage_addVirtualHostMonster
from zExceptions import BadRequest
vhm1 = self._makeOne()
vhm1.manage_addToContainer(self.root)
vhm2 = self._makeOne()
self.assertRaises(BadRequest, vhm2.manage_addToContainer, self.root)
self.assertRaises(BadRequest, manage_addVirtualHostMonster, self.root)
def test_add_id_collision(self):
from OFS.Folder import Folder
from Products.SiteAccess.VirtualHostMonster import \
manage_addVirtualHostMonster
from zExceptions import BadRequest
self.root._setObject('virtual_hosting', Folder('virtual_hosting'))
vhm1 = self._makeOne()
self.assertRaises(BadRequest, vhm1.manage_addToContainer, self.root)
self.assertRaises(BadRequest, manage_addVirtualHostMonster, self.root)
def test_add_addToContainer(self):
from ZPublisher.BeforeTraverse import queryBeforeTraverse
vhm1 = self._makeOne()
vhm1.manage_addToContainer(self.root)
self.assertTrue(vhm1.getId() in self.root.objectIds())
self.assertTrue(queryBeforeTraverse(self.root, vhm1.meta_type))
def test_add_manage_addVirtualHostMonster(self):
from Products.SiteAccess.VirtualHostMonster import \
manage_addVirtualHostMonster
from Products.SiteAccess.VirtualHostMonster import VirtualHostMonster
from ZPublisher.BeforeTraverse import queryBeforeTraverse
manage_addVirtualHostMonster(self.root)
self.assertTrue(VirtualHostMonster.id in self.root.objectIds())
hook = queryBeforeTraverse(self.root, VirtualHostMonster.meta_type)
self.assertTrue(hook)
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<h3>The Virtual Host Monster supports virtual hosting</h3>
<p class="form-help">
It changes the protocol, host, and/or path of URLs generated by Zope.
Values affected include REQUEST variables starting with URL or BASE
(such as URL1, BASE2, URLPATH0), and the absolute_url() methods of
objects. By changing these, the Virtual Host Monster allows you to
host several different domains in a single Zope.
</p>
<p class="form-help">
The most common sort of virtual hosting setup is one in which you
create a Folder in your Zope root for each domain that you want to
serve. For instance <strong>http://www.buystuff.com</strong> is
served from Folder /buystuff.com while
<strong>http://www.mycause.org</strong> is served from /mycause.org.
</p>
<h3>One is enough</h3>
<p class="form-help">
A single Virtual Host Monster in your Zope root can handle all of your
virtual hosting needs. It doesn't matter what Id you give it, as long
as nothing else in your site has the same Id.
</p>
<h3>You must add special names in the path</h3>
<p class="form-help">
The VHM doesn't do anything unless it sees one
of the following special path elements in a URL:
<em>VirtualHostBase</em> sets the protocol and host, while
<em>VirtualHostRoot</em> sets the path root.</p>
<p class="form-help">
If the URL path of a request begins with
&quot;/VirtualHostBase/http/www.buystuff.com&quot;, for instance, then
URLs generated by Zope will start with
<strong>http://www.buystuff.com</strong>.
Since the port number was not specified, it is left unchanged. If
your Zope is running on port 8080, and you want generated URLs not to
include this port number, you must use
&quot;/VirtualHostBase/http/www.buystuff.com:80&quot;.
</p>
<p class="form-help">
If the URL contains <em>VirtualHostRoot</em>, then
all path elements up to that point are removed from generated URLs.
For instance, a request with path &quot;/a/b/c/VirtualHostRoot/d&quot;
will traverse &quot;a/b/c/d&quot; and then generate a URL with
path <strong>/d</strong>.
</p>
<h3>You add these names by rewriting incoming URLs</h3>
<p class="form-help">
Visitors to your site don't see these special names, of course.
You insert them into the path using either an external rewriter,
such as an Apache RewriteRule or ProxyPass directive, or by
setting up a mapping on the &quot;Mappings&quot; tab.
</p>
<p class="form-help">
For example, suppose Zope is running on port
8080 behind an Apache running on port 80. You place a Virtual Host
Monster in the Zope root Folder, and use Apache to rewrite &quot;/(.*)&quot;
to <strong>http://localhost:8080/VirtualHostBase/http/www.buystuff.com:80/buystuff.com/VirtualHostRoot/$1</strong>.
</p>
<p class="form-help">
You could get the same effect in a standalone Zope by adding the line
<code>www.buystuff.com/buystuff.com</code> to the &quot;Mappings&quot;
tab. In either case, requests for
<strong>http://www.buystuff.com/anything</strong>
will look for Zope object <strong>/buystuff.com/anything</strong>.
</p>
<p class="form-help">
You should only use the &quot;Mappings&quot; tab for simple virtual
hosting, in a Zope that is serving requests directly. Each mapping
line is a host name followed by a path to a Folder. The VHM checks
the host specified in each incoming request to see if it is in the
list. If it is, then the corresponding path is inserted at the start
of the path, followed by &quot;VirtualHostRoot&quot;.
</p>
<p class="form-help">
You can match
multiple subdomains by putting &quot;*.&quot; in front of the host
name, as in &quot;*.buystuff.com&quot;. If an exact match exists, it
is used instead of a wildcard match.
</p>
<h3>Inside-out hosting</h3>
<p class="form-help">
Another use for virtual hosting is to make Zope appear to be part of a
site controlled by another server. For example, Zope might only serve
the contents of <strong>http://www.mycause.org/dynamic_stuff</strong>.
To accomplish this, you want to add &quot;dynamic_stuff&quot; to the
start of all Zope-generated URLs.
</p>
<p class="form-help">
If you insert <em>VirtualHostRoot</em>, followed by one or more path elements
that start with '_vh_', then these elements will
be ignored during traversal and then added (without the '_vh_') to the
start of generated URLs.
For instance, a request for &quot;/a/VirtualHostRoot/_vh_z/&quot;
will traverse &quot;a&quot; and then generate URLs that start with
<strong>/z</strong>.
</p>
<p class="form-help">
In our example, you would have the main server send requests for
<strong>http://www.mycause.org/dynamic_stuff/anything</strong>
to Zope, rewritten as
<strong>/VirtualHostRoot/_vh_dynamic_stuff/anything</strong>.
</p>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<p class="form-help">
You may edit the mappings for this Virtual Host Monster using the form below.
<strong><em>You don't need to use this tab</em></strong> if you are using
Apache or some other front-end server to rewrite requests. This is
only for simple virtual hosting in a bare Zope server. If you place
the hostname that you use to manage your Zope in this list
<strong><em>you are likely to regret it</em></strong>, and will
probably need to manage Zope using its raw IP address to fix things.
</p>
<form action="set_map" method="post">
<table cellpadding="2" cellspacing="0" width="100%" border="0">
<dtml-with keyword_args mapping>
<tr>
<td align="left" valign="top" colspan="2" class="form-help">
Each line represents a path mapping for a single host
(<strong>host/path</strong>),
or a set of hosts (<strong>*.host/path</strong>).
<div style="width: 100%;">
<textarea name="map_text:text" wrap="off" style="width: 100%;"
rows="20"><dtml-in lines>&dtml-sequence-item;
</dtml-in></textarea>
</div>
</td>
</tr>
</dtml-with>
<tr>
<td align="left" valign="top" colspan="2">
<div class="form-element">
<dtml-if wl_isLocked>
<em>Locked</em>
<dtml-else>
<input class="form-element" type="submit" name="SUBMIT" value="Save Changes">
</dtml-if>
</div>
</td>
</tr>
</table>
</form>
<dtml-var manage_page_footer>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment