Skip to content

Commit 97c8ecc

Browse files
committed
added cors authentication
1 parent 9a55eba commit 97c8ecc

File tree

4 files changed

+225
-5
lines changed

4 files changed

+225
-5
lines changed

README.md

+19-1
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,22 @@ recaptcha configurations enable services to require reCaptcha authentication
161161

162162
set `enable` to `true` to enable services to have recaptcha
163163

164-
set the value of `secret` provided from google recaptcha
164+
set the value of `secret` provided from google recaptcha
165+
166+
#### CORS
167+
CORS configurations enables CORS (Cross Origin Resource Sharing) authentication
168+
169+
``` JSON
170+
"CORS":{
171+
"AllowedOrigins":[
172+
],
173+
"AllowedMethods":[
174+
],
175+
"AllowedHeaders":[
176+
],
177+
"AllowCredentials":false,
178+
"MaxAge":1000
179+
}
180+
```
181+
182+
Details of these can be found in [https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS)

config.json

+10
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,15 @@
3434
"enable":false,
3535
"secret":"",
3636
"url":"https://www.google.com/recaptcha/api/siteverify"
37+
},
38+
"CORS":{
39+
"AllowedOrigins":[
40+
],
41+
"AllowedMethods":[
42+
],
43+
"AllowedHeaders":[
44+
],
45+
"AllowCredentials":false,
46+
"MaxAge":1000
3747
}
3848
}

src/RESTFul/Routing/Route.php

+51-4
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,43 @@
1212
use PhpPlatform\RESTFul\Package;
1313
use PhpPlatform\Annotations\Annotation;
1414
use PhpPlatform\Errors\Exceptions\Http\_4XX\Unauthorized;
15+
use PhpPlatform\Errors\Exceptions\Http\_2XX\OK;
1516

1617
class Route {
1718

19+
private static $headers = array();
20+
1821
static function run($uri = null){
1922

2023
try{
24+
//cors authentication
25+
if(array_key_exists('HTTP_ORIGIN', $_SERVER)){
26+
$origin = $_SERVER['HTTP_ORIGIN'];
27+
}elseif(array_key_exists('HTTP_REFERER', $_SERVER)){
28+
$origin = $_SERVER['HTTP_REFERER'];
29+
}
30+
if(isset($origin)){
31+
$origin = trim($origin,'/');
32+
$requestDomain = "http".(isset($_SERVER['HTTPS'])?"s":"")."://".$_SERVER['HTTP_HOST'].($_SERVER['SERVER_PORT'] == 80 ?"":":".$_SERVER['SERVER_PORT']);
33+
if(strtolower($origin) != strtolower($requestDomain)){
34+
$allowedOrigins = Settings::getSettings(Package::Name,'CORS.AllowedOrigins');
35+
if(in_array($origin, $allowedOrigins)){
36+
self::$headers['Access-Control-Allow-Origin'] = $origin;
37+
self::$headers['Access-Control-Allow-Methods'] = implode(", ", Settings::getSettings(Package::Name,'CORS.AllowedMethods'));
38+
self::$headers['Access-Control-Allow-Headers'] = implode(", ", Settings::getSettings(Package::Name,'CORS.AllowedHeaders'));
39+
40+
$allowCredentails = Settings::getSettings(Package::Name,'CORS.AllowCredentials');
41+
if(is_bool($allowCredentails)){
42+
$allowCredentails = $allowCredentails ? 'true':'false';
43+
}
44+
self::$headers['Access-Control-Allow-Credentials'] = $allowCredentails;
45+
self::$headers['Access-Control-Max-Age'] = Settings::getSettings(Package::Name,'CORS.MaxAge');
46+
}else{
47+
throw new Unauthorized("CORS ERROR : $origin is not a allowed origin");
48+
}
49+
}
50+
}
51+
2152
// find route
2253
$route = self::findRoute($uri);
2354

@@ -54,6 +85,9 @@ static function run($uri = null){
5485
$httpResponse = $routeMethodReflection->invokeArgs($routeInstance, array_merge(array($httpRequest),$pathParams));
5586

5687
if($httpResponse instanceof HTTPResponse){
88+
foreach (self::$headers as $name=>$value){
89+
$httpResponse->setHeader($name, $value);
90+
}
5791
// flush HTTPResponse
5892
$httpResponse->flush($httpRequest->getHeader('Accept'));
5993
}else{
@@ -65,12 +99,15 @@ static function run($uri = null){
6599
if($h instanceof InternalServerError){
66100
$message = "Internal Server Error";
67101
}
68-
(new HTTPResponse($h->getCode(),$message))->flush();
102+
$httpResponse = new HTTPResponse($h->getCode(),$message);
69103
}catch (\Exception $e){
70104
new InternalServerError($e->getMessage()); // for logging purposes
71-
(new HTTPResponse(500,'Internal Server Error'))->flush();
105+
$httpResponse = new HTTPResponse(500,'Internal Server Error');
72106
}
73-
107+
foreach (self::$headers as $name=>$value){
108+
$httpResponse->setHeader($name, $value);
109+
}
110+
$httpResponse->flush();
74111
}
75112

76113
/**
@@ -115,7 +152,17 @@ private static function findRoute($uri = null){
115152
}
116153

117154
if(!isset($route[$method])){
118-
throw new MethodNotAllowed("$method method is not Allowed");
155+
if($method == "OPTIONS"){
156+
// for OPTIONS return OK , if Access-Control-Request-Method is found in $route configurations
157+
$_method = $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'];
158+
if(array_key_exists($_method, $route)){
159+
throw new OK();
160+
}else{
161+
throw new MethodNotAllowed("CORS ERROR, $_method is not Allowed");
162+
}
163+
}else{
164+
throw new MethodNotAllowed("$method method is not Allowed");
165+
}
119166
}
120167

121168
$route = $route[$method];

tests/RESTFul/ClientSide/TestCORS.php

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
namespace PhpPlatform\Tests\RESTFul\ClientSide;
4+
5+
use PhpPlatform\Tests\RESTFul\TestBase;
6+
use Guzzle\Http\Client;
7+
use PhpPlatform\Mock\Config\MockSettings;
8+
use PhpPlatform\RESTFul\Package;
9+
use Guzzle\Http\Exception\BadResponseException;
10+
11+
class TestCORS extends TestBase {
12+
13+
function testForNoCORSHeadersForSameOrigin(){
14+
MockSettings::setSettings(Package::Name, 'CORS', array(
15+
"AllowedOrigins"=>array('http://example.com'),
16+
"AllowedMethods"=>array('GET'),
17+
"AllowedHeaders"=>array(),
18+
"AllowCredentials"=>false,
19+
"MaxAge"=>1000
20+
));
21+
22+
$client = new Client();
23+
24+
// without Origin header in the request
25+
$request = $client->get(APP_DOMAIN.'/'.APP_PATH.'/test/route/simple');
26+
$response = $client->send($request);
27+
28+
$this->assertNull($response->getHeader('Access-Control-Allow-Origin'));
29+
$this->assertNull($response->getHeader('Access-Control-Allow-Methods'));
30+
$this->assertNull($response->getHeader('Access-Control-Allow-Headers'));
31+
$this->assertNull($response->getHeader('Access-Control-Allow-Credentials'));
32+
$this->assertNull($response->getHeader('Access-Control-Max-Age'));
33+
34+
// with Origin header in the request
35+
$request = $client->get(APP_DOMAIN.'/'.APP_PATH.'/test/route/simple');
36+
$request->setHeader('Origin', APP_DOMAIN);
37+
$response = $client->send($request);
38+
39+
$this->assertNull($response->getHeader('Access-Control-Allow-Origin'));
40+
$this->assertNull($response->getHeader('Access-Control-Allow-Methods'));
41+
$this->assertNull($response->getHeader('Access-Control-Allow-Headers'));
42+
$this->assertNull($response->getHeader('Access-Control-Allow-Credentials'));
43+
$this->assertNull($response->getHeader('Access-Control-Max-Age'));
44+
}
45+
46+
function testNotAllowedOrigin(){
47+
MockSettings::setSettings(Package::Name, 'CORS', array(
48+
"AllowedOrigins"=>array('http://example.com'),
49+
"AllowedMethods"=>array('GET'),
50+
"AllowedHeaders"=>array(),
51+
"AllowCredentials"=>false,
52+
"MaxAge"=>1000
53+
));
54+
55+
$client = new Client();
56+
$request = $client->get(APP_DOMAIN.'/'.APP_PATH.'/test/route/simple');
57+
$request->setHeader('Origin', "http://mydomain.com");
58+
try{
59+
$response = $client->send($request);
60+
}catch (BadResponseException $e){
61+
$response = $e->getResponse();
62+
$this->clearErrorLog();
63+
}
64+
$this->assertEquals(401, $response->getStatusCode());
65+
$this->assertEquals("CORS ERROR : http://mydomain.com is not a allowed origin", $response->getReasonPhrase());
66+
67+
$this->assertNull($response->getHeader('Access-Control-Allow-Origin'));
68+
$this->assertNull($response->getHeader('Access-Control-Allow-Methods'));
69+
$this->assertNull($response->getHeader('Access-Control-Allow-Headers'));
70+
$this->assertNull($response->getHeader('Access-Control-Allow-Credentials'));
71+
$this->assertNull($response->getHeader('Access-Control-Max-Age'));
72+
}
73+
74+
function testAllowedOrigin(){
75+
MockSettings::setSettings(Package::Name, 'CORS', array(
76+
"AllowedOrigins"=>array('http://example.com'),
77+
"AllowedMethods"=>array('GET'),
78+
"AllowedHeaders"=>array(),
79+
"AllowCredentials"=>false,
80+
"MaxAge"=>1000
81+
));
82+
83+
$client = new Client();
84+
85+
// allowed method
86+
$request = $client->get(APP_DOMAIN.'/'.APP_PATH.'/test/route/simple');
87+
$request->setHeader('Origin', "http://example.com");
88+
$response = $client->send($request);
89+
90+
$this->assertEquals('http://example.com',$response->getHeader('Access-Control-Allow-Origin'));
91+
$this->assertEquals('GET',$response->getHeader('Access-Control-Allow-Methods'));
92+
$this->assertEquals('',$response->getHeader('Access-Control-Allow-Headers'));
93+
$this->assertEquals('false',$response->getHeader('Access-Control-Allow-Credentials'));
94+
$this->assertEquals('1000',$response->getHeader('Access-Control-Max-Age'));
95+
96+
// not allowed method
97+
$jsonContent = '{"name":"raaghu","children":[{"name":"shri"},{"name":"di"}]}';
98+
$request = $client->post(APP_DOMAIN.'/'.APP_PATH.'/test/http-request/json',array("Content-Type"=>"application/json","Content-Length"=>strlen($jsonContent)),$jsonContent);
99+
$request->setHeader('Origin', "http://example.com");
100+
$response = $client->send($request);
101+
102+
$this->assertEquals('http://example.com',$response->getHeader('Access-Control-Allow-Origin'));
103+
$this->assertEquals('GET',$response->getHeader('Access-Control-Allow-Methods'));
104+
$this->assertEquals('',$response->getHeader('Access-Control-Allow-Headers'));
105+
$this->assertEquals('false',$response->getHeader('Access-Control-Allow-Credentials'));
106+
$this->assertEquals('1000',$response->getHeader('Access-Control-Max-Age'));
107+
108+
// for OPTIONS method (preflight request)
109+
$request = $client->options(APP_DOMAIN.'/'.APP_PATH.'/test/http-request/json');
110+
$request->setHeader('Origin', "http://example.com");
111+
$request->setHeader('Access-Control-Request-Method', "POST");
112+
$response = $client->send($request);
113+
114+
$this->assertContainsAndClearLog('[/test/http-request/json] OK : PhpPlatform\Errors\Exceptions\Http\_2XX\OK');
115+
116+
$this->assertEquals('http://example.com',$response->getHeader('Access-Control-Allow-Origin'));
117+
$this->assertEquals('GET',$response->getHeader('Access-Control-Allow-Methods'));
118+
$this->assertEquals('',$response->getHeader('Access-Control-Allow-Headers'));
119+
$this->assertEquals('false',$response->getHeader('Access-Control-Allow-Credentials'));
120+
$this->assertEquals('1000',$response->getHeader('Access-Control-Max-Age'));
121+
122+
123+
// with allowed headers , credentials and differrent max-age
124+
MockSettings::setSettings(Package::Name, 'CORS', array(
125+
"AllowedOrigins"=>array('http://example.com'),
126+
"AllowedMethods"=>array('GET','POST'),
127+
"AllowedHeaders"=>array('Content-Type','Php-Platform-Session-Cookie','Accept'),
128+
"AllowCredentials"=>'true',
129+
"MaxAge"=>500
130+
));
131+
$request = $client->post(APP_DOMAIN.'/'.APP_PATH.'/test/http-request/json',array("Content-Type"=>"application/json","Content-Length"=>strlen($jsonContent)),$jsonContent);
132+
$request->setHeader('Origin', "http://example.com");
133+
$response = $client->send($request);
134+
135+
$this->assertEquals('http://example.com',$response->getHeader('Access-Control-Allow-Origin'));
136+
$this->assertEquals('GET, POST',$response->getHeader('Access-Control-Allow-Methods'));
137+
$this->assertEquals('Content-Type, Php-Platform-Session-Cookie, Accept',$response->getHeader('Access-Control-Allow-Headers'));
138+
$this->assertEquals('true',$response->getHeader('Access-Control-Allow-Credentials'));
139+
$this->assertEquals('500',$response->getHeader('Access-Control-Max-Age'));
140+
141+
142+
}
143+
144+
145+
}

0 commit comments

Comments
 (0)