Browse Source

Add a build() method to CorsBuilder.

Having a build() method lets us do 2 things:

* Call the cors methods in any order.
* Still enforce the constraint that headers should only be set for
  when an allowed origin is set.

It also makes the CorsBuilder easier to integrate in the future when we
support PSR7 request/response object.
Mark Story 10 years ago
parent
commit
0bfab982d9
2 changed files with 76 additions and 69 deletions
  1. 17 23
      src/Network/CorsBuilder.php
  2. 59 46
      tests/TestCase/Network/CorsBuilderTest.php

+ 17 - 23
src/Network/CorsBuilder.php

@@ -9,6 +9,7 @@ class CorsBuilder
     protected $_response;
     protected $_origin;
     protected $_isSsl;
+    protected $_headers = [];
 
     public function __construct(Response $response, $origin, $isSsl = false)
     {
@@ -17,18 +18,26 @@ class CorsBuilder
         $this->_response = $response;
     }
 
-    public function allowOrigin($domain)
+    public function build()
     {
         if (empty($this->_origin)) {
-            return $this;
+            return $this->_response;
+        }
+        if (isset($this->_headers['Access-Control-Allow-Origin'])) {
+            $this->_response->header($this->_headers);
         }
+        return $this->_response;
+    }
+
+    public function allowOrigin($domain)
+    {
         $allowed = $this->_normalizeDomains((array)$domain);
         foreach ($allowed as $domain) {
             if (!preg_match($domain['preg'], $this->_origin)) {
                 continue;
             }
             $value = $domain['original'] === '*' ? '*' : $this->_origin;
-            $this->_response->header('Access-Control-Allow-Origin', $value);
+            $this->_headers['Access-Control-Allow-Origin'] = $value;
             break;
         }
         return $this;
@@ -61,46 +70,31 @@ class CorsBuilder
 
     public function allowMethods(array $methods)
     {
-        if (empty($this->_origin)) {
-            return $this;
-        }
-        $this->_response->header('Access-Control-Allow-Methods', implode(', ', $methods));
+        $this->_headers['Access-Control-Allow-Methods'] = implode(', ', $methods);
         return $this;
     }
 
     public function allowCredentials()
     {
-        if (empty($this->_origin)) {
-            return $this;
-        }
-        $this->_response->header('Access-Control-Allow-Credentials', 'true');
+        $this->_headers['Access-Control-Allow-Credentials'] = 'true';
         return $this;
     }
 
     public function allowHeaders(array $headers)
     {
-        if (empty($this->_origin)) {
-            return $this;
-        }
-        $this->_response->header('Access-Control-Allow-Headers', implode(', ', $headers));
+        $this->_headers['Access-Control-Allow-Headers'] = implode(', ', $headers);
         return $this;
     }
 
     public function exposeHeaders(array $headers)
     {
-        if (empty($this->_origin)) {
-            return $this;
-        }
-        $this->_response->header('Access-Control-Expose-Headers', implode(', ', $headers));
+        $this->_headers['Access-Control-Expose-Headers'] = implode(', ', $headers);
         return $this;
     }
 
     public function maxAge($age)
     {
-        if (empty($this->_origin)) {
-            return $this;
-        }
-        $this->_response->header('Access-Control-Max-Age', $age);
+        $this->_headers['Access-Control-Max-Age'] = $age;
         return $this;
     }
 }

+ 59 - 46
tests/TestCase/Network/CorsBuilderTest.php

@@ -17,7 +17,7 @@ class CorsBuilderTest extends TestCase
         $response = new Response();
         $builder = new CorsBuilder($response, '');
         $this->assertSame($builder, $builder->allowOrigin(['*.example.com', '*.foo.com']));
-        $this->assertNoHeader($response, 'Access-Control-Origin');
+        $this->assertNoHeader($builder->build(), 'Access-Control-Origin');
     }
 
     /**
@@ -30,17 +30,18 @@ class CorsBuilderTest extends TestCase
         $response = new Response();
         $builder = new CorsBuilder($response, 'http://www.example.com');
         $this->assertSame($builder, $builder->allowOrigin('*'));
-        $this->assertHeader('*', $response, 'Access-Control-Allow-Origin');
+        $this->assertHeader('*', $builder->build(), 'Access-Control-Allow-Origin');
 
         $response = new Response();
         $builder = new CorsBuilder($response, 'http://www.example.com');
         $this->assertSame($builder, $builder->allowOrigin(['*.example.com', '*.foo.com']));
-        $this->assertHeader('http://www.example.com', $response, 'Access-Control-Allow-Origin');
+        $builder->build();
+        $this->assertHeader('http://www.example.com', $builder->build(), 'Access-Control-Allow-Origin');
 
         $response = new Response();
         $builder = new CorsBuilder($response, 'http://www.example.com');
         $this->assertSame($builder, $builder->allowOrigin('*.example.com'));
-        $this->assertHeader('http://www.example.com', $response, 'Access-Control-Allow-Origin');
+        $this->assertHeader('http://www.example.com', $builder->build(), 'Access-Control-Allow-Origin');
     }
 
     /**
@@ -58,92 +59,104 @@ class CorsBuilderTest extends TestCase
         $response = new Response();
         $builder = new CorsBuilder($response, 'http://www.example.com', true);
         $this->assertSame($builder, $builder->allowOrigin('https://example.com'));
-        $this->assertNoHeader($response, 'Access-Control-Allow-Origin');
+        $this->assertNoHeader($builder->build(), 'Access-Control-Allow-Origin');
 
         $response = new Response();
         $builder = new CorsBuilder($response, 'http://www.example.com');
         $this->assertSame($builder, $builder->allowOrigin('https://example.com'));
-        $this->assertNoHeader($response, 'Access-Control-Allow-Origin');
-    }
-
-    public function testAllowMethodsNoOrigin()
-    {
-        $response = new Response();
-        $builder = new CorsBuilder($response, '');
-        $this->assertSame($builder, $builder->allowMethods(['GET', 'POST']));
-        $this->assertNoHeader($response, 'Access-Control-Allow-Methods');
+        $this->assertNoHeader($builder->build(), 'Access-Control-Allow-Origin');
     }
 
     public function testAllowMethods()
     {
         $response = new Response();
         $builder = new CorsBuilder($response, 'http://example.com');
+        $builder->allowOrigin('*');
         $this->assertSame($builder, $builder->allowMethods(['GET', 'POST']));
-        $this->assertHeader('GET, POST', $response, 'Access-Control-Allow-Methods');
-    }
-
-    public function testAllowCredentialsNoOrigin()
-    {
-        $response = new Response();
-        $builder = new CorsBuilder($response, '');
-        $this->assertSame($builder, $builder->allowCredentials());
-        $this->assertNoHeader($response, 'Access-Control-Allow-Credentials');
+        $this->assertHeader('GET, POST', $builder->build(), 'Access-Control-Allow-Methods');
     }
 
     public function testAllowCredentials()
     {
         $response = new Response();
         $builder = new CorsBuilder($response, 'http://example.com');
+        $builder->allowOrigin('*');
         $this->assertSame($builder, $builder->allowCredentials());
-        $this->assertHeader('true', $response, 'Access-Control-Allow-Credentials');
-    }
-
-    public function testAllowHeadersNoOrigin()
-    {
-        $response = new Response();
-        $builder = new CorsBuilder($response, '');
-        $this->assertSame($builder, $builder->allowHeaders(['X-THING']));
-        $this->assertNoHeader($response, 'Access-Control-Allow-Headers');
+        $this->assertHeader('true', $builder->build(), 'Access-Control-Allow-Credentials');
     }
 
     public function testAllowHeaders()
     {
         $response = new Response();
         $builder = new CorsBuilder($response, 'http://example.com');
+        $builder->allowOrigin('*');
         $this->assertSame($builder, $builder->allowHeaders(['Content-Type', 'Accept']));
-        $this->assertHeader('Content-Type, Accept', $response, 'Access-Control-Allow-Headers');
+        $this->assertHeader('Content-Type, Accept', $builder->build(), 'Access-Control-Allow-Headers');
     }
 
-    public function testExposeHeadersNoOrigin()
+    public function testExposeHeaders()
     {
         $response = new Response();
-        $builder = new CorsBuilder($response, '');
-        $this->assertSame($builder, $builder->exposeHeaders(['X-THING']));
-        $this->assertNoHeader($response, 'Access-Control-Expose-Headers');
+        $builder = new CorsBuilder($response, 'http://example.com');
+        $builder->allowOrigin('*');
+        $this->assertSame($builder, $builder->exposeHeaders(['Content-Type', 'Accept']));
+        $this->assertHeader('Content-Type, Accept', $builder->build(), 'Access-Control-Expose-Headers');
     }
 
-    public function testExposeHeaders()
+    public function testMaxAge()
     {
         $response = new Response();
         $builder = new CorsBuilder($response, 'http://example.com');
-        $this->assertSame($builder, $builder->exposeHeaders(['Content-Type', 'Accept']));
-        $this->assertHeader('Content-Type, Accept', $response, 'Access-Control-Expose-Headers');
+        $builder->allowOrigin('*');
+        $this->assertSame($builder, $builder->maxAge(300));
+        $this->assertHeader('300', $builder->build(), 'Access-Control-Max-Age');
     }
 
-    public function testMaxAgeNoOrigin()
+    /**
+     * When no origin is allowed, none of the other headers should be applied.
+     *
+     * @return void
+     */
+    public function testNoAllowedOriginNoHeadersSet()
     {
         $response = new Response();
-        $builder = new CorsBuilder($response, '');
-        $this->assertSame($builder, $builder->maxAge(300));
+        $builder = new CorsBuilder($response, 'http://example.com');
+        $response = $builder->allowCredentials()
+            ->allowMethods(['GET', 'POST'])
+            ->allowHeaders(['Content-Type'])
+            ->exposeHeaders(['X-CSRF-Token'])
+            ->maxAge(300)
+            ->build();
+        $this->assertNoHeader($response, 'Access-Control-Allow-Origin');
+        $this->assertNoHeader($response, 'Access-Control-Allow-Headers');
+        $this->assertNoHeader($response, 'Access-Control-Expose-Headers');
+        $this->assertNoHeader($response, 'Access-Control-Allow-Methods');
+        $this->assertNoHeader($response, 'Access-Control-Allow-Authentication');
         $this->assertNoHeader($response, 'Access-Control-Max-Age');
     }
 
-    public function testMaxAge()
+    /**
+     * When an invalid origin is used, none of the other headers should be applied.
+     *
+     * @return void
+     */
+    public function testInvalidAllowedOriginNoHeadersSet()
     {
         $response = new Response();
         $builder = new CorsBuilder($response, 'http://example.com');
-        $this->assertSame($builder, $builder->maxAge(300));
-        $this->assertHeader('300', $response, 'Access-Control-Max-Age');
+        $response = $builder->allowOrigin(['http://google.com'])
+            ->allowCredentials()
+            ->allowMethods(['GET', 'POST'])
+            ->allowHeaders(['Content-Type'])
+            ->exposeHeaders(['X-CSRF-Token'])
+            ->maxAge(300)
+            ->build();
+        $this->assertNoHeader($response, 'Access-Control-Allow-Origin');
+        $this->assertNoHeader($response, 'Access-Control-Allow-Headers');
+        $this->assertNoHeader($response, 'Access-Control-Expose-Headers');
+        $this->assertNoHeader($response, 'Access-Control-Allow-Methods');
+        $this->assertNoHeader($response, 'Access-Control-Allow-Authentication');
+        $this->assertNoHeader($response, 'Access-Control-Max-Age');
     }
 
     /**