From 2b506b02b1a611e05559baf2e1d179d18dc10abf Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 9 Sep 2011 11:59:46 +0200 Subject: [PATCH] ENHANCEMENT Allowing strict subdomain checks on 'www.example.com' vs. 'example.com' via Subsite::$strict_domain_matching (AIR-54) --- README.md | 7 ++ code/Subsite.php | 24 ++++++- tests/SubsiteTest.php | 151 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 171 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 9de4829..424b2c1 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,13 @@ You can mix the two together, if you want to have some subsites hosted off subdo Note that every site also has a ''www.''-prefixed version of the domain available. For example, if your subsite is accessible from ''wellington.example.org'' then it will also be accessible from '''www.wellington.example.org''. +### Strict Subdomain Matching ### + +The module tries to provide sensible defaults, in which it regards `example.com` and `www.example.com` +as the same domains. In case you want to distinguish between these variations, +set `Subsite::$strict_subdomain_matching` to TRUE. This won't affect wildcard/asterisk checks, +but removes the ambiguity about default subdomains. + ### Permissions ### Groups can be associated with one or more subsites, in which case the granted permissions diff --git a/code/Subsite.php b/code/Subsite.php index a72c3d5..d9656c3 100644 --- a/code/Subsite.php +++ b/code/Subsite.php @@ -20,6 +20,7 @@ class Subsite extends DataObject implements PermissionProvider { static $force_subsite = null; static $write_hostmap = true; + static $default_sort = "\"Title\" ASC"; static $db = array( @@ -76,6 +77,13 @@ class Subsite extends DataObject implements PermissionProvider { * are listed. */ protected static $allowed_themes = array(); + + /** + * @var Boolean If set to TRUE, don't assume 'www.example.com' and 'example.com' are the same. + * Doesn't affect wildcard matching, so '*.example.com' will match 'www.example.com' (but not 'example.com') + * in both TRUE or FALSE setting. + */ + static $strict_subdomain_matching = false; static function set_allowed_domains($domain){ user_error('Subsite::set_allowed_domains() is deprecated; it is no longer necessary ' @@ -320,7 +328,7 @@ JS; static function getSubsiteIDForDomain($host = null, $returnMainIfNotFound = true) { if($host == null) $host = $_SERVER['HTTP_HOST']; - $host = str_replace('www.','',$host); + if(!Subsite::$strict_subdomain_matching) $host = preg_replace('/^www\./', '', $host); $SQL_host = Convert::raw2sql($host); $matchingDomains = DataObject::get("SubsiteDomain", "'$SQL_host' LIKE replace(\"SubsiteDomain\".\"Domain\",'*','%')", @@ -329,7 +337,15 @@ JS; if($matchingDomains) { $subsiteIDs = array_unique($matchingDomains->column('SubsiteID')); - if(sizeof($subsiteIDs) > 1) user_error("Multiple subsites match '$host'", E_USER_WARNING); + $subsiteDomains = array_unique($matchingDomains->column('Domain')); + if(sizeof($subsiteIDs) > 1) { + throw new UnexpectedValueException(sprintf( + "Multiple subsites match on '%s': %s", + $host, + implode(',', $subsiteDomains) + )); + } + return $subsiteIDs[0]; } @@ -538,7 +554,9 @@ JS; if ($subsites) foreach($subsites as $subsite) { $domains = $subsite->Domains(); if ($domains) foreach($domains as $domain) { - $hostmap[str_replace('www.', '', $domain->Domain)] = $subsite->domain(); + $domainStr = $domain->Domain; + if(!Subsite::$strict_subdomain_matching) $domainStr = preg_replace('/^www\./', '', $domainStr); + $hostmap[$domainStr] = $subsite->domain(); } if ($subsite->DefaultSite) $hostmap['default'] = $subsite->domain(); } diff --git a/tests/SubsiteTest.php b/tests/SubsiteTest.php index a3a83d4..45d404d 100644 --- a/tests/SubsiteTest.php +++ b/tests/SubsiteTest.php @@ -1,7 +1,21 @@ origStrictSubdomainMatching = Subsite::$strict_subdomain_matching; + Subsite::$strict_subdomain_matching = false; + } + + function tearDown() { + parent::tearDown(); + + Subsite::$strict_subdomain_matching = $this->origStrictSubdomainMatching; + } /** * Create a new subsite from the template and verify that all the template's pages are copied @@ -57,28 +71,65 @@ class SubsiteTest extends SapphireTest { * Confirm that domain lookup is working */ function testDomainLookup() { + // Clear existing fixtures + foreach(DataObject::get('Subsite') as $subsite) $subsite->delete(); + foreach(DataObject::get('SubsiteDomain') as $domain) $domain->delete(); + + // Much more expressive than YML in this case + $subsite1 = $this->createSubsiteWithDomains(array( + 'one.example.org' => true, + 'one.*' => false, + )); + $subsite2 = $this->createSubsiteWithDomains(array( + 'two.mysite.com' => true, + '*.mysite.com' => false, + 'subdomain.onmultiplesubsites.com' => false, + )); + $subsite3 = $this->createSubsiteWithDomains(array( + 'three.*' => true, // wildcards in primary domain are not recommended + 'subdomain.unique.com' => false, + '*.onmultiplesubsites.com' => false, + )); + $this->assertEquals( - $this->idFromFixture('Subsite','domaintest1'), + $subsite3->ID, + Subsite::getSubsiteIDForDomain('subdomain.unique.com'), + 'Full unique match' + ); + + $this->assertEquals( + $subsite1->ID, Subsite::getSubsiteIDForDomain('one.example.org'), - 'Full match' + 'Full match, doesn\'t complain about multiple matches within a single subsite' + ); + + $failed = false; + try { + Subsite::getSubsiteIDForDomain('subdomain.onmultiplesubsites.com'); + } catch(UnexpectedValueException $e) { + $failed = true; + } + $this->assertTrue( + $failed, + 'Fails on multiple matches with wildcard vs. www across multiple subsites' ); $this->assertEquals( - $this->idFromFixture('Subsite','domaintest1'), - Subsite::getSubsiteIDForDomain('one.localhost'), - 'Fuzzy match suffixed with asterisk (rule "one.*")' + $subsite1->ID, + Subsite::getSubsiteIDForDomain('one.unique.com'), + 'Fuzzy match suffixed with wildcard (rule "one.*")' ); $this->assertEquals( - $this->idFromFixture('Subsite','domaintest2'), + $subsite2->ID, Subsite::getSubsiteIDForDomain('two.mysite.com'), 'Matches correct subsite for rule' ); $this->assertEquals( - $this->idFromFixture('Subsite','domaintest2'), + $subsite2->ID, Subsite::getSubsiteIDForDomain('other.mysite.com'), - 'Fuzzy match prefixed with asterisk (rule "*.mysite.com")' + 'Fuzzy match prefixed with wildcard (rule "*.mysite.com")' ); $this->assertEquals( @@ -88,6 +139,90 @@ class SubsiteTest extends SapphireTest { ); } + + function testStrictSubdomainMatching() { + // Clear existing fixtures + foreach(DataObject::get('Subsite') as $subsite) $subsite->delete(); + foreach(DataObject::get('SubsiteDomain') as $domain) $domain->delete(); + + // Much more expressive than YML in this case + $subsite1 = $this->createSubsiteWithDomains(array( + 'example.org' => true, + 'example.com' => false, + '*.wildcard.com' => false, + )); + $subsite2 = $this->createSubsiteWithDomains(array( + 'www.example.org' => true, + 'www.wildcard.com' => false, + )); + + Subsite::$strict_subdomain_matching = false; + + $this->assertEquals( + $subsite1->ID, + Subsite::getSubsiteIDForDomain('example.org'), + 'Exact matches without strict checking when not using www prefix' + ); + $this->assertEquals( + $subsite1->ID, + Subsite::getSubsiteIDForDomain('www.example.org'), + 'Matches without strict checking when using www prefix, still matching first domain regardless of www prefix (falling back to subsite primary key ordering)' + ); + $this->assertEquals( + $subsite1->ID, + Subsite::getSubsiteIDForDomain('www.example.com'), + 'Fuzzy matches without strict checking with www prefix' + ); + $this->assertEquals( + 0, + Subsite::getSubsiteIDForDomain('www.wildcard.com'), + 'Doesn\'t match www prefix without strict check, even if a wildcard subdomain is in place' + ); + + Subsite::$strict_subdomain_matching = true; + + $this->assertEquals( + $subsite1->ID, + Subsite::getSubsiteIDForDomain('example.org'), + 'Matches with strict checking when not using www prefix' + ); + $this->assertEquals( + $subsite2->ID, // not 1 + Subsite::getSubsiteIDForDomain('www.example.org'), + 'Matches with strict checking when using www prefix' + ); + $this->assertEquals( + 0, + Subsite::getSubsiteIDForDomain('www.example.com'), + 'Doesn\'t fuzzy match with strict checking when using www prefix' + ); + $failed = false; + try { + Subsite::getSubsiteIDForDomain('www.wildcard.com'); + } catch(UnexpectedValueException $e) { + $failed = true; + } + $this->assertTrue( + $failed, + 'Fails on multiple matches with strict checking and wildcard vs. www' + ); + + } + + protected function createSubsiteWithDomains($domains) { + $subsite = new Subsite(); + $subsite->write(); + foreach($domains as $domainStr => $isPrimary) { + $domain = new SubsiteDomain(array( + 'Domain' => $domainStr, + 'IsPrimary' => $isPrimary, + 'SubsiteID' => $subsite->ID + )); + $domain->write(); + } + + return $subsite; + } /** * Test the Subsite->domain() method