Closed alexraputa closed 6 years ago
What's the actual code that is tested?
@samdark, this is an action that I testing:
public function actionCreate()
{
$resource = new SignUpResource();
if ($resource->load($this->request->post()) && $resource->validate()) {
try {
$this->service->create($resource);
$this->response->setStatusCode(201);
$this->response->getHeaders()->set('Location', Url::to('users/' . $resource->id, true));
return $resource;
} catch (\DomainException $e) {
\Yii::$app->errorHandler->logException($e);
}
} elseif ($resource->hasErrors()) {
return $resource;
}
}
rest controller:
class BaseController extends \yii\web\Controller
{
...
public $serializer = \tuyakhov\jsonapi\Serializer::class;
public $enableCsrfValidation = false;
public function behaviors(): array
{
return [
'contentNegotiator' => [
'class' => \yii\filters\ContentNegotiator::class,
'formats' => [
'application/vnd.api+json' => \yii\web\Response::FORMAT_JSON,
'application/json' => \yii\web\Response::FORMAT_JSON,
'application/xml' => \yii\web\Response::FORMAT_XML,
],
],
'verbFilter' => [
'class' => \yii\filters\VerbFilter::class,
'actions' => $this->verbs(),
],
'rateLimiter' => [
'class' => \yii\filters\RateLimiter::class,
],
];
}
config:
'components' => [
'request' => [
'parsers' => [
'application/vnd.api+json' => [
'class' => tuyakhov\jsonapi\JsonApiParser::class,
'memberNameCallback' => [tuyakhov\jsonapi\Inflector::class, 'variablize'],
],
'application/json' => yii\web\JsonParser::class,
],
],
'response' => [
'formatters' => [
yii\web\Response::FORMAT_JSON => [
'class' => tuyakhov\jsonapi\JsonApiResponseFormatter::class,
'prettyPrint' => YII_DEBUG,
'encodeOptions' => JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
],
],
],
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
...
'OPTIONS,GET users' => 'users/index',
'OPTIONS,POST users' => 'users/create',
],
],
],
'as corsFilter' => [
'class' => yii\filters\Cors::class,
],
It's not clear how to reproduce it. There are custom classes everywhere and excessive config. Would you please do a minimal test case that contains only necessary code to reproduce the issue starting with basic app?
@samdark, I'll try.
@samdark, the issue is reproduce in the basic app cors-bug.zip.
How to reproduce the issue.
composer install
php vendor/bin/codecept run api
The result should be fail.This is strange bacause Cors filter is not enabled in this project
@leandrogehlen and you confirm that commenting lines above make tests pass?
I'm looking for it now
I did some tests and the unique issue is that if you comment second line the test works.
Then, i can't see the problem, because the header: 'Content-Type'
is not set in request and ContentNegotiator
not add this header in response. I think this is right.
$I->seeResponseCodeIs(200);
//$I->seeHttpHeader('Content-Type', 'application/json; charset=UTF-8');
$I->seeHttpHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS');
$I->seeHttpHeader('Access-Control-Allow-Headers', 'Content-Type');
I think i found the problem, in example project was configured in web.php
:
'as corsFilter' => [
'class' => yii\filters\Cors::class,
],
'as authenticator' => [
'class' => yii\filters\auth\CompositeAuth::class,
'except' => ['site/index', 'debug/*'],
'authMethods' => [
['class' => yii\filters\auth\HttpBearerAuth::class],
],
],
This way behavior method is not considered. I removed this configuration and did it in controller :
class SiteController extends Controller
{
/**
* @inheritdoc
*/
public function behaviors()
{
return [
'contentNegotiator' => [
'class' => ContentNegotiator::class,
'formats' => [
'application/json' => Response::FORMAT_JSON,
],
],
'cors' => [
'class' => Cors::class,
]
]
...
I change the test to
$I->sendOPTIONS('/site/index?_format=json');
This worked, but i think the problem is the sequence, like cors
return false the following filters are not executed
Well, that's expected and exactly that was changed, isn't it?
This worked, but i think the problem is the sequence, like cors return false the following filters are not executed
This is the correct statement.
@samdark, i think so. @alexraputa if you change the filters sequence your problem is solved?
@leandrogehlen, no. If I change the filters sequence my authorization is failing. But the preflight test is passed.
@alexraputa why do you need these headers or authentication in preflight?
@leandrogehlen, this is definitely a bug.
The preflight response should contain Content-Type
, which will be the same as for a real response.
See CORS the section of Preflighted requests
and example for it.
I agreee, but this is happening. You don't adds the 'Content-Type' in your request test.
If you use the code bellow, will work
$I->haveHttpHeader('Content-Type', 'application/json; charset=UTF-8');
$I->haveHttpHeader('Access-Control-Request-Headers', 'Content-Type');
$I->haveHttpHeader('Access-Control-Request-Method', 'POST');
$I->haveHttpHeader('Accept', '*/*');
$I->sendOPTIONS('/users');
@leandrogehlen, I disagree with it. The Content-Type
of the request specifies the request body, which is not applicable to an OPTIONS request.
See OPTIONS request.
I use behaviors in this sequence. See Yii 2 CORS.
The behaviors will not work if the first of them return false
in the beforeAction
method.
@samdark, I think we should revert this https://github.com/yiisoft/yii2/commit/399dbce0cadbd7631f726841ece3ecf6a830444d feature, until we find a better solution.
@alexraputa, I understand but i belive the interruption in filter sequence is another issue, not associate to Cors. If it will considered a bug is a problem for all filters
@samdark, I can't see problem, does not make sense execute others filters in preflight, because after preflight the real request will be executed
@leandrogehlen, @samdark, Such frameworks as Laravel and Symfony does not stop the application in the cors filter if the request has OPTIONS method. They simply modify the response object and pass it further along the chain of middlewares. Why not do the same in Yii? We should not leave this as is.
@alexraputa, isn't to OPTIONS method is only in preflight: https://github.com/yiisoft/yii2/blob/master/framework/filters/Cors.php#L109
related #14618
@leandrogehlen, The preflight request is equal to OPTIONS request.
Unlike “simple requests”, "preflighted" requests first send an HTTP request by the OPTIONS method to the resource on the other domain, in order to determine whether the actual request is safe to send. Cross-site requests are preflighted like this since they may have implications to user data.
Example of a request from documentation (Preflighted requests):
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
Example of a response from documentation (Preflighted requests):
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
Example of a real request from documentation (Preflighted requests):
POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: http://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: http://foo.example
Pragma: no-cache
Cache-Control: no-cache
<?xml version="1.0"?><person><name>Arun</name></person>
Example of a real response from documentation (Preflighted requests):
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
[Some GZIP'd payload]
Both responses have the same content type - Content-Type: text/plain
.
@samdark, need your thoughts.
This https://github.com/yiisoft/yii2/commit/399dbce0cadbd7631f726841ece3ecf6a830444d feature also breaks BC if you have actionOptions like this:
public function actionOptions(): void
{
if (!$this->request->getIsOptions()) {
$this->response->setStatusCode(405);
}
$options = implode(', ', array_reduce($this->verbs(), 'array_merge', []));
$this->response->getHeaders()->set('Allow', $options);
$this->response->getHeaders()->set('Access-Control-Allow-Method', $options);
}
Because the response was stopped in the beforeAction()
method of the CORS filter. And your response will not contain the Allow
and the modified Access-Control-Allow-Method
headers for specific actions of controllers.
@alexraputa according to CORS spec https://www.w3.org/TR/cors/#preflight-request the only considered headers are CORS-related. The rest of the headers and body are simply ignored so it doesn't make any sense to send them.
Why would you need to send Allow
or Content-type
if they're simply ignored?
Why would you need to send Allow or Content-type if they're simply ignored?
@samdark, This is not true. The response from the server must support these headers. See rfc7231 and OPTIONS W/ this https://github.com/yiisoft/yii2/commit/399dbce0cadbd7631f726841ece3ecf6a830444d feature. My application on Ember.js 3.0 sends a preflight request:
And backend app on Yii 2 sends response:
W/o this https://github.com/yiisoft/yii2/commit/399dbce0cadbd7631f726841ece3ecf6a830444d feature.
This is a standard preflight request to the server. The other frontend frameworks such as React and Angular send the same preflight request.
If frontend app send the Accept: */*
(see Accept) header then backend app must send the allowed Content-Type
for a URL.
Well, this is not how preflight should work according to its specification: https://www.w3.org/TR/cors/#preflight-request
Do you have any actual misbehavior caused in your app because of the change?
My API is public and clients are using it. If the Accept
header receives the response with Content-Type
liketext/html
, then clients accordingly need to implement a serializer for this type of content. But then the API sends a real response with the type application/vnd.api+json
and the frontend application throws an error, because the serializer for application/vnd.api+json
is not implemented.
@alexraputa, this is not true, i have built an angular application with yii2 cors filters and it worked fine.
Preflight results:
Real request:
Yii2 Angular Project yii2-angular.zip
cd angular
npm install
ng build --watch
to access angular app use: http://localhost/yii2-angular/web/dist/
@leandrogehlen, I don't see any of CORS methods in your response.
The preflight request shouldn't have the Content-Type
header because it has an empty body.
But you force added the Content-Type
header into the request.
let headers = new HttpHeaders({
'Content-Type': 'application/json'
});
@samdark, please close this issue, he wants to have a response default format. it has nothing to do with issue.
Ok, if you want to have some rare bugs in a framework then just leave it.
@alexraputa which client do you have that doesn't follow CORS specification and looks at non-CORS headers in preflight?
@samdark, Which the headers of the my preflight request do not match with the CORS specification?
Accept
;Referer
;Access-Control-Request-Headers
;Origin
;User-Agent
;Access-Control-Request-Method
.Not the CORS specification itself but preflight part of it will just ignore what's not needed for CORS:
What matters (and is cached in browsers) in CORS reponse is:
That's it. Nothing about content-type, accept or anything else. Body doesn't matter as well.
So I'm asking again, what's the client that you're using that needs Accept
header in the response for preflight request?
@samdark,
My application on
Ember.js 3.0
sends a preflight request:
These are preflight requests specification in ASP.NET
(Preflight-requests):
The application doesn't set any request headers other than Accept, Accept-Language, Content-Language, Content-Type, or Last-Event-ID, and
The Content-Type header (if set) is one of the following: application/x-www-form-urlencoded multipart/form-data text/plain
You can close this issue If you think that this is not a bug.
You're confusing request and response. See response example there:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
Date: Wed, 20 May 2015 06:33:22 GMT
According to W3C spec, Cache-Control: no-cache
, Pragma: no-cache
and Date
are ignored and aren't cached on clients so it comes to:
HTTP/1.1 200 OK
Content-Length: 0
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
What exactly happened to Ember.js 3.0 app when we started to send these reduced CORS responses? Did it break somehow? How exactly?
These are preflight requests specification in ASP.NET (Preflight-requests)
@alexraputa according to that linked documentation those are not specifications but conditions that when meet the browser will omit or skip the process of sending a preflight request.
Those are conditions similar to what's on W3C except examples are a bit weird. They're OK but, as I said, they define request, not response.
@samdark, what do you think about it Preflighted requests (see example of preflight request/response)?
And about this https://github.com/yiisoft/yii2/issues/15665#issuecomment-367489876.
We already have the actionOptions
method in the ActiveController
class. The headers of this action are never included in the response if you use the CORS filter.
https://github.com/yiisoft/yii2/blob/77ad6bc00847d4964a0b2a82d3b70dcd7cb5a1cf/framework/rest/ActiveController.php#L102-L104 https://github.com/yiisoft/yii2/blob/77ad6bc00847d4964a0b2a82d3b70dcd7cb5a1cf/framework/rest/OptionsAction.php#L32-L44
What exactly happened to Ember.js 3.0 app when we started to send these reduced CORS responses? Did it break somehow? How exactly?
The front-end application is working. But the response from the API was significantly reduced, which does not give a full submission of the resource.
I think that these response examples are OK as well but, as pointed out by specification (W3C one), non-CORS headers are not taken into account. Means you can send whatever you want: any headers, body... anything. As long as response code is 2xx and CORS headers are there, it's fine. But these non-CSRF headers doesn't matter.
About https://github.com/yiisoft/yii2/issues/15665#issuecomment-367489876. Regular OPTIONS request and preflight request are two different things. There's no need to give out usual options response in CORS preflight response.
Or did you mean that regular OPTIONS response (non-preflight one) is broken?
The front-end application is working. But the response from the API was significantly reduced, which does not give a full submission of the resource.
Would you please elaborate "does not give a full submission of the resource"?
@samdark, https://github.com/yiisoft/yii2/issues/15665#issuecomment-367602078.
You can close this issue If you think that this is not a bug.
I don't get it :( Does this change affect non-preflight responses?
No, it's not affect. But it's confusing.
I don't modify any response on API, but framework send the Content-Type
: text/html
.
There's a regression in 2.0.14-dev
Cors::beforeAction()
after merge: https://github.com/yiisoft/yii2/commit/399dbce0cadbd7631f726841ece3ecf6a830444dThis is my test in Codeception:
Before merge https://github.com/yiisoft/yii2/commit/399dbce0cadbd7631f726841ece3ecf6a830444d:
After merge https://github.com/yiisoft/yii2/commit/399dbce0cadbd7631f726841ece3ecf6a830444d:
Additional info