I have configured the path in the module:
function mymodule_restws_resource_info() {
return array(
'mymodule_person' => array(
'label' => t('Person'),
'class' => 'PersonResourceController',
'menu_path' => 'api/person',
),
);
}
That part seems to work, because when I send a POST transaction via Advanced Rest Client in chrome, I get a 200 response (I get a 404 if I change the request url).
That said, in the module I also throw an error if attempting an update, delete or read. When I send any of those, I still get a 200 response.
Any of GET, POST, PUT or UPDATE gives me
Response does not contain any data
including the create method in the resource controller, so it's not firing.
Do I need to do something to invoke it beyond hitting the url with a post payload?
The problem seems to revolve around the workflow_moderation module being in the mix. If I enable restws and attempt just to hit node/1.json, I get a white screen and an error in the logs. It seems to be a known issue at this point that workflow_moderation is not including the correct parms when it passes the node on. https://drupal.org/node/1238040
Related
I am currently building an application using token based authentication with Angular and Laravel. I initially set things up just to test the API by creating a BookController . At first I was getting a Cross Origin Request Block error when I tried to call this data from Angular. However I managed to resolve this by adding the headers to my routes/web.php file. Here is the whole file. NB: After adding these headers I was succesfully able to use the API even from another domain
<?php
header('Access-Control-Allow-Origin: *');
header( 'Access-Control-Allow-Headers: Authorization, Content-Type' );
//Route::get('/', 'BookController#show');
//Route::resource('book/create', 'BookController#create');
Auth::routes();
Route::get('/', 'HomeController#index');
Route::resource('book', 'BookController');
Route::resource('authenticate', 'AuthenticateController', ['only' => ['index']]);
Route::post('authenticate', 'AuthenticateController#authenticate');
However I am currently following this tutorial to set up token based authentication. https://scotch.io/tutorials/token-based-authentication-for-angularjs-and-laravel-apps
To summarise , my issue is when I submit the form containing username and password I am getting the following errors. Below I will try elaborate a bit more but it is quite difficult as there is alot to it.
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at http://www.example.local/authenticate/.
(Reason: CORS header 'Access-Control-Allow-Origin' missing).
And
Possibly unhandled rejection:
{"data":null,"status":-1,"config":{"method":"POST","transformRequest":[null],"transformResponse":[null],"jsonpCallbackParam":"callback","url":"http://www.example.local/authenticate/","data":{"email":"dasdas#Dasa.com","password":"fsdfd"},"withCredentials":false,"headers":{"Accept":"application/json,
text/plain,
/","Content-Type":"application/json;charset=utf-8"}},"statusText":""}
I am using Angular UI Router V 0.4.2 and satellizer. My Angular version is 1.6.2 It using a different domain than the API. Much like the working example above.
On the laravel side I also followed this tutorial to add middleware to attempt to resolve this but no luck.
http://en.vedovelli.com.br/2015/web-development/Laravel-5-1-enable-CORS/
I will also include my AuthenticateController.php file..
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use App\User;
class AuthenticateController extends Controller
{
public function __construct()
{
// Apply the jwt.auth middleware to all methods in this controller
// except for the authenticate method. We don't want to prevent
// the user from retrieving their token if they don't already have it
$this->middleware('jwt.auth', ['except' => ['authenticate']]);
$this->middleware('cors');
}
public function index()
{
// Retrieve all the users in the database and return them
$users = User::all();
return $users;
}
public function authenticate(Request $request)
{
$credentials = $request->only('email', 'password');
try {
// verify the credentials and create a token for the user
if (! $token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
// something went wrong
return response()->json(['error' => 'could_not_create_token'], 500);
}
// if no errors are encountered we can return a JWT
return response()->json(compact('token'));
}
}
My issue is I do not even know if the "possibly unhandled rejection" is related to the "Cross-Origin Request Blocked" error. But I have to assume it is.
Can you recognise anything from my routes files that may be allowing one and not another?
EDIT:
I have noticed the difference between one request and another is that one is a GET request while another is an OPTIONS request. This may be the cause.
I have since added Header set Access-Control-Allow-Origin "*" to both the virtual hosts config file in Apache and to a .htaccess file in the root of the Laravel project. Still no change.
I am wondering is this related something in Angular
Your server code needs to handle that OPTIONS request by sending a headers-only response to it that includes the Access-Control-Allow-Methods: GET, POST, PUT, DELETE header and Access-Control-Allow-Headers: Authorization, Content-Type header.
Or you can just try using https://github.com/barryvdh/laravel-cors which makes all this easier.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests has general info you might want to read up on.
I am calling an authentication service where I do a $http.post which returns a 303 resonse, redirecting to a get call returning the response.
When I make the post call using Postman, I get the desired response but when I do an angular $http.post call, it returns me a 401 error (which is user not authorized)
Am I missing something while making the angular call? The backend service seems to work fine as it works fine on Postman.
This is how the $http call looks:
$http.post(url, userData).success(function(data, status) {
//handle success
}.error(function(data, status) {
//handle error
});
The url and the user data is constructed absolutely fine in this case.
The reason that you get a GET call is that the browser handle the 303 response before the angular can reach that. And the handling sequence is first go to the browser and then go to the angular framework.
So briefly what happens is : you make call to the server --> the server return the 303 response -> your browser handle the 303 and make a request to some url (should be 'location' in the response header) --> the server receive the request and return the 401 authorized response --> again the browser receive the 401 response first but this time the browser redirect the response to the angular --> at last you can receive the data and status inside the error().
The solution for this could be switching to other response status code like 2xx, and you can get the location from the body. Then you can do the redirection manually. If you HAVE to use 303 or other 3xx as the response code I don't think there's any effective solution at this moment because you can't do much to the browser. As far as I know there might be a solution at browser level but don't know when that will happen.
Hope this can help anyone has the similar issue like this although it has been nearly one year since this issue raised.
Some other ref: https://groups.google.com/forum/#!topic/angular/GKkdipdMbdo
There's similar solution you can see from the link above.
I faced this issue and I found a redirect url in error object after lots of hours struggle.
loginWithLinkedIn() {
let data = {
// some kind of information here
}
return this.http.get(`https://www.someurl.com/oauth/v2/authorization`).subscribe(res => {
console.log(res)
}, err => {
console.log(err.url) // here is the redirect url
window.location.href = err.url
})
}
Note: when you make a request and you get 303 response which is considered as error, that's why we think we are getting error but error contains useful info.
I want to serve JSONP content with CakePHP and was wondering what's the proper way of doing it so.
Currently I'm able to serve JSON content automatically by following this CakePHP guide.
Ok, I found a solution on this site. Basically you override the afterFilter method with:
public function afterFilter() {
parent::afterFilter();
if (empty($this->request->query['callback']) || $this->response->type() != 'application/json') {
return;
}
// jsonp response
App::uses('Sanitize', 'Utility');
$callbackFuncName = Sanitize::clean($this->request->query['callback']);
$out = $this->response->body();
$out = sprintf("%s(%s)", $callbackFuncName, $out);
$this->response->body($out);
}
I hope it helps someone else as well.
I've as yet not found a complete example of how to correctly return JSONP using CakePHP 2, so I'm going to write it down. OP asks for the correct way, but his answer doesn't use the native options available now in 2.4. For 2.4+, this is the correct method, straight from their documentation:
Set up your views to accept/use JSON (documentation):
Add Router::parseExtensions('json'); to your routes.php config file. This tells Cake to accept .json URI extensions
Add RequestHandler to the list of components in the controller you're going to be using
Cake gets smart here, and now offers you different views for normal requests and JSON/XML etc. requests, allowing you flexibility in how to return those results, if needed. You should now be able to access an action in your controller by:
using the URI /controller/action (which would use the view in /view/controller/action.ctp), OR
using the URI /controller/action.json (which would use the view in /view/controller/json/action.ctp)
If you don't want to define those views i.e. you don't need to do any further processing, and the response is ready to go, you can tell CakePHP to ignore the views and return the data immediately using _serialize. Using _serialize will tell Cake to format your response in the correct format (XML, JSON etc.), set the headers and return it as needed without you needing to do anything else (documentation). To take advantage of this magic:
Set the variables you want to return as you would a view variable i.e. $this->set('post', $post);
Tell Cake to serialize it into XML, JSON etc. by calling $this->set('_serialize', array('posts'));, where the parameter is the view variable you just set in the previous line
And that's it. All headers and responses will be taken over by Cake. This just leaves the JSONP to get working (documentation):
Tell Cake to consider the request a JSONP request by setting $this->set('_jsonp', true);, and Cake will go find the callback function name parameter, and format the response to work with that callback function name. Literally, setting that one parameter does all the work for you.
So, assuming you've set up Cake to accept .json requests, this is what your typical action could look like to work with JSONP:
public function getTheFirstPost()
$post = $this->Post->find('first');
$this->set(array(
'post' => $post, <-- Set the post in the view
'_serialize' => array('post'), <-- Tell cake to use that post
'_jsonp' => true <-- And wrap it in the callback function
)
);
And the JS:
$.ajax({
url: "/controller/get-the-first-post.json",
context: document.body,
dataType: 'jsonp'
}).done(function (data) {
console.log(data);
});
For CakePHP 2.4 and above, you can do this instead.
http://book.cakephp.org/2.0/en/views/json-and-xml-views.html#jsonp-response
So you can simply write:
$this->set('_jsonp', true);
in the relevant action.
Or you can simply write:
/**
*
* beforeRender method
*
* #return void
*/
public function beforeRender() {
parent::beforeRender();
$this->set('_jsonp', true);
}
I am a 3+ years old in cakephp and facing a somewhat strange issue
with submitting a form to plugin controller's action (i am using
plugin first time). After trying different known things i am posting
this one.
Going straight into the matter here is the form in my "forum" plugin's search_controller.php's "index" view:
echo $form->create("Search", array('url'=>array('controller' =>
'search', 'action' => 'index','plugin'=>'forum'),
'id'=>'searchFormMain'));
<input type="text" name="data[Search][keyword]" style="width:357px; margin-left:9px;"><p><span id="searchButton"><input
type="image" src="/img/button_search.jpg" style="height:40px;width:
136px;border:0;" class="handcursor"></span></p>
</form>
As i am submitting this form to "index" action of search controller of
forum plugin, the following code does print nothing:
public function index($type='') {
if(!empty($this->data)) {
pr($this->data);
die;
}
}
While if i try the same code within beforeFilter of the same
controller i.e. search_controller.php it works well and prints as
follows:
Array
(
[Search] => Array
(
[keyword] => Hello Forum
)
)
And finally here is the beforeFilter code (of search_controller.php):
public function beforeFilter() {
parent::beforeFilter();
if(!empty($this->data)) {
pr($this->data);
}
}
Fyi, it does not matter if i comment out "parent::beforeFilter();" or
even disable $uses of my controller (if they look doubtful to you)
the result is same i.e. the control is not going in to "index" action
in the case of form submit while is working fine in the case of page
call. The url/action to page is http://localhost.rfdf.org/forum/search/index.
If i call the url directly it loads the form fine but when i submit it, it
never gets into the "index" action of the controller thus no view
rendered.
If i try the same set of code out of "forum" plugin environment i.e. in normal application it works just fine
I have been trying to find a way out of this for last 3+ hours now but
no success. I would appreciate any help in solving this puzzle.
I got it, finally!
It was Securty compontent dropping the request into the blackHole whenever it failed to find a security token with the form data. I learned that "Security" component "interferes" with $form->create() method and places a token as a hidden field with each $form->create() call. On the form submit, just after beforeFilter and right before getting into the controller "action" it checks for this token and simply dies everything on a validation failure. Unfortunately there is no error message or entry to cake log.
In my case i had been creating my own custom tag and not with the help of $form->create method so no token was being generated which lead to all the pain.
I resolved it by placing
$this->Security->validatePost = false;
at the end of beforeFilter.
Thanks everyone!
Have you tried putting an else into that if(!empty($this->data)) and doing a pr() as it could be that your post is not empty.
Either that or the format of your url array is not correct.
From ln759, http://api.cakephp.org/view_source/router/#line-757
$defaults = $params = array('plugin' => null, 'controller' => null, 'action' => 'index');
So I guess you need plugin first?
Are you using ACL or any of the like? In the beforeFilter, do a pr of the request. See which action is being requested to make sure that the request is correct
In my controller, I check a condition to see if the user is allowed to do something. If the check fails, I want to send a 403 back to the browser. How do I do that in Cakephp?
EDIT - This question is quite old and covers different versions of the CakePHP framework. Following is a summary of which version each answer applies to. Don't forget to vote on the solution that helps most.
CakePHP 3.x and 4.x - using response object (Roberto's answer)
CakePHP 2.x - using exceptions (Brad Koch's answer) [preferred solution]
CakePHP 2.x - setting header only (Asa Ayers' answer)
CakePHP 1.x - using error handler (my other answer)
CakePHP 1.x - setting header only (this answer)
EDIT #2 - A more detailed answer for CakePHP 2.x has been added by Mark37.
EDIT #3 - Added solution for CakePHP. (May 2018: CakePHP 3.5 did some function renaming, solution by Roberto is still valid.)
By looking at the relevant API code from the previous comment, it seems you can call Controller::header($status) to output a header without redirection. In your case, the proper usage is most likely:
$this->header('HTTP/1.1 403 Forbidden');
$this->response->statusCode(403);
Will set the status code when Cake is ready to send the response. CakeResponse::send() expects to send the status code and message, so in my tests I think my using header() was getting overwritten. using $this->header('HTTP/1.1 400 Bad Request') doesn't work either because Cake expects any call to $this->header to be split on a colon ex: $this->header('Location: ...')
Notes concerning CakePHP 3.x seem to be missing, so to make this thread complete:
For CakePHP 3.x and 4.x use:
$response = $this->response->withStatus(403);
return $response;
For versions before CakePHP 3.3.x you can use the same style as CakePHP 2.x:
$this->response->statusCode('code');
Note that using the PHP function directly also works (http_response_code(403); die();), though using the response object seems like the intended method.
In CakePHP 2, the preferred method is to throw an exception:
throw new ForbiddenException();
I'm adding in my two cents here because I don't feel like any of these answers covered this topic as thoroughly as I would have liked (at least for Cake 2.x).
If you want to throw an error status, use the Exception classes (as mentioned in other answers):
throw new BadRequestException(); // 400 Bad Request
// Or customize the code...
throw new BadRequestException('Custom error message', 405); // 405 Method Not Allowed
Fun fact: Cake will automatically do some magical error rendering even for RESTful calls via the ExceptionRenderer class. Even more fun of a fact is that it's based on the Status Code, not the fact that an Exception might have been thrown, so if you set the status code to > 400 on your own you will likely get error messages even if you didn't want them.
If you want to return a specific status code for a REST JSON/XML endpoint, take advantage of the new CakeResponse object, but also make sure that you add the special _serialize variable or you'll end up with a 'view not found' error as cake will attempt to find a view to render your JSON/XML. (This is by design - see the JsonView/XmlView class.)
$this->response->setStatus(201); // 201 Created
$this->set('_serialize', array()); // Value must be something other than null
And lastly, if you want to send a non-200 status for a regularly rendered page, you can just use the setStatus() method with nothing else as mentioned in a previous answer:
$this->response->setStatus(201);
UPDATE:
$this->response->setStatus('code');
is no longer available. Use
$this->response->statusCode('code');
Upon revisiting this question, and reading Adriano's comment on my previous answer (regarding redirecting the user to a friendly page), I have come up with a new solution.
Within a controller you can call $this->cakeError('error404') to generate a friendly 404 page. This can can be customised (as with other errors) by creating file at 'app/views/errors/error404.ctp'.
After having a closer look at the code for cakeError, my recommendation is to try extending Cake's ErrorHandler by creating a file at 'app/error.php' or (possibly more preferable) 'app/app_error.php'.
The code for your error403 (mimicking the error404 code) could read as follows:
class AppError extends ErrorHandler {
function error403($params) {
extract($params, EXTR_OVERWRITE);
$this->error(array(
'code' => '403',
'name' => 'Forbidden',
'message' => sprintf(__("Access was forbidden to the requested address %s on this server.", true), $url, $message)));
$this->_stop();
}
}
You should also be able to provide a custom view for this error by creating 'app/views/errors/error403.ctp'. Here is a modified version of the error404 view:
<h2><?php echo $name; ?></h2>
<p class="error">
<strong>Error: </strong>
<?php echo sprintf(__("Access was forbidden to the requested address %s on this server.", true), "<strong>'{$message}'</strong>")?>
</p>
It has changed again since CakePHP 3.6:
Use now
$this->setResponse($this->response->withStatus(403) );
return $this->response; // use this line also
instead of
$response = $this->response->withStatus(403);
https://api.cakephp.org/3.7/class-Cake.Controller.Controller.html#_setResponse
Perhaps something in this section of the cakephp manual can help you.
redirect(string $url, integer $status,
boolean $exit)
The flow control method you’ll use
most often is redirect(). This method
takes its first parameter in the form
of a CakePHP-relative URL. When a user
has successfully placed an order, you
might wish to redirect them to a
receipt screen. The second parameter
of redirect() allows you to define an
HTTP status code to accompany the
redirect. You may want to use 301
(moved permanently) or 303 (see
other), depending on the nature of the
redirect.
The method will issue an exit() after
the redirect unless you set the third
parameter to false.
You can use cakephp response for custom message:
$this->response->header('HTTP/1.0 201', 'custom message');
$this->response->send();
Core PHP link code works in cakePHP.
header('HTTP/1.1 403 Forbidden');