JSON view not working after implementing authorization in CakePHP - cakephp

Since I implemented authorization in my cakephp project, it seens I can't access my views with JSON anymore. Do I have to set a specific setting anywhere to make this work again?
I was hoping adding autoComplete to the allowedActions and isAuthorized would do the trick
app\Controller\BrandsController.php (stripped from unnecessary code)
<?php
App::uses('Sanitize', 'Utility');
class BrandsController extends AppController {
public $helpers = array('Html', 'Form', 'Session');
public $components = array('Session', 'RequestHandler');
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allowedActions = array('autoComplete');
}
public function isAuthorized($user) {
if ($this->action === 'view' || $this->action === 'index' || $this->action === 'autoComplete') {
return true;
}
return parent::isAuthorized($user);
}
public function autoComplete($name = null)
{
if(!$name)
{
$name = #$this->params->query['name'];
}
$name = Sanitize::paranoid($name, array(' '));
if (!$name || empty($name))
{
new NotFoundException(__('Invalid searchquery'));
}
else
{
$objects = $this->Brand->find('list',
array(
'conditions' => array("Brand.name LIKE" => "%" . $name . "%")
)
);
$this->layout = 'ajax';
$this->RequestHandler->setContent('json', 'application/json' );
$this->set(compact('objects'));
$this->render('json/output_json');
}
}
}
?>
app\View\Brands\json\output_json.ctp
<?php
Configure::write('debug', 0);
echo json_encode($objects);
?>
Call
<?php
$brandsAutoCompleteUrl = Router::url(array('controller' => 'brands', 'action' => 'autoComplete'));
?>
<script type="text/javascript">
$(document).ready(function(){
$.getJSON('<?php echo $brandsAutoCompleteUrl; ?>', { name: 'test' }, function(data) {
// never called
});
});
</script>
In my Chrome debug window it sais
GET http://localhost/cakephp/brands/autoComplete?name=tes 500 (Internal Server Error)
When I call this url from my browser directly, I get the expected results

I fixed this problem by replacing
$this->RequestHandler->setContent('json', 'application/json' );
by
$this->response->type(array('json' => 'application/json'));

correct allowing is
$this->Auth->allow('view', 'index');
This can be done only in a controller that is owner of allowed actions -
Link to documentation
Also - in firefox firebug or in chrome you can view response for your 500 HTTP error that is usually an html code with an exact CakePHP error - investigate this first.
Why do you add components and helpers in this controller instead of AppController? This components are common for any controller - it is reasonable to put them to AppController.
And if you haven't noticed - all helpers and components of AppController are merged with helpers and components of child controllers. You can't switch off Auth component by replacing components in child controller.
Recommendation:
Avoid using of '#' for code like #$this->params->query['name'];. Use !empty($this->params->query['name']) in 'if' condition
If you don't know: empty($name) == !$name, also !$name can trigger error in variable $name is not initiated - http://www.php.net/manual/ru/types.comparisons.php (1st table)

Related

How can I read Database table value with the help of configure:: write & configure:: read in cakephp 2.x

We are migrating our website from cakephp 1.3 to cakephp 2.x, so I am facing a problem to use our current code element.
We have a table which has all default content which we used many place so we have created as a db table setting , so we are using it with configure:: read in our different view.
Our Setting Model:
<?php
class Setting extends AppModel{
var $name = 'Setting' ;
var $actsAs = array(
'Multivalidatable',
);
function getSetting(){
$data = $this->find('list', array('fields'=>array('name', 'value')));
if(!empty($data)){
foreach($data as $key => $value)
{
Configure::write($key, $value);
}
}
}
var $validationSets = array(
// Start Of Admin Validation Set
'setting' => array(
'value'=>array(
'notEmpty'=>array(
'rule'=>'notEmpty',
'message' => 'Value is required.'
)
)
)
);
}
?>
So with this model we are setting some fields with configure::write, now we have used in view file like this
<?php echo Configure::read('Regpopup1.value');?>
but when I am migrating my code this logic is not working, please help me how can I use this code again with migration in cakephp 2.x
You can use something like that with Cakephp 2.x
App::uses('Controller', 'Controller');
class AppController extends Controller {
public function beforeFilter(){
$this->fetchSettings(); // Don't forget this.
}
public function fetchSettings(){
$this->loadModel('Setting');
$settings_array = $this->Setting->find('all');
foreach($settings_array as $key=>$value){
Configure::write($value['Setting']['key'], $value['Setting']['value']);
}
}
}

CakePHP 3.0 build a permission menu using Cells

I would like to create a menu in new CakePHP 3.0 and I found that using cells might be a good way. So let's say I created UserMenuCell
class UserMenuCell extends Cell {
protected $_validCellOptions = [];
public function display() {
$menu = [];
$menu[] = $this ->menu( __('Dashboard'), array( 'controller' => 'Users', 'action' => 'dashboard' ), 'fa-dashboard', [] );
if( $this -> Auth -> isAuthorized(null, ??? ))
$menu[] = $this ->menu( __('Barcodes'), array( 'controller' => 'Barcodes', 'action' => 'index' ), 'fa-table', [] );
$this -> set ( 'menu', $menu );
}
private function menu( $title, $url = [], $icon, $submenu = [] ) {
return ['title' => $title, 'url' => $url, 'icon' => $icon, 'submenu' => $submenu]; }
}
But I want to display Barcodes item only when current user is authorized to manage barcodes. How can I do it? I can't even access $this -> Auth to get current user.
In my cell's template is everything OK. I just need to create this nested array for menu.
According to Cookbook, the session is available from within Cells.
class UsermenuCell extends Cell
{
public function display()
{
var_dump($this->request->session()->read('Auth'));
}
}
Like this you could read the needed informations in your cell display function.
and if you pass the session variable?
<?= $this->cell('userMenu', $this->Session->read('Auth')); ?>
I think the problem is solved:
I can make controller to have static method for example static public function _isAuthorized($user, $request) which would handle the authorization logic (so every controller controls only its own permissions).
And then I can just call from anywhere for example PostsController::_isAuthorized($user, ['action' => 'add']). This should solve all problems I guess.
Also good point is to pass $this -> Auth -> user() into view, so it can be used in Cells (through parameter).
src/Controller/AppController.php
public function beforeFilter(Event $event) {
$this -> set('user', $this -> Auth -> user());
}
src/View/Cell/MenuCell.php
use App\Controller\PostsController; // Don't forget to use namespace of your Controller
class MenuCell extends Cell {
public function display($user) {
$menu = [];
if (PostsController::_isAuthorized($user, ['action' => 'add'])) // In that method you must handle authorization
$menu[] = ['title' => 'Add post', 'url' => array('controller' => 'Posts', 'action' => 'add')];
$this -> set ('menu', $menu); // Handle this in Template/Cell/Menu/display.ctp
}
}
src/Template/Cell/Menu/display.ctp - just to show how to render menu
<ul>
<?php foreach($menu as $item) {
echo '<li>' . $this -> Html -> link ($item['title'], $item['url']);
} ?>
</ul>
src/Template/Layout/default.ctp - render menu in main layout
<?= $this -> cell('Menu', array($user)) /* This is the user passed from beforeFilter */ ?>
Then you can play with isAuthorized methods. For example you can edit your AppController. Always when CakePHP calls isAuthorized function it will be redirected to YourNameController::_isAuthorized() static method (if exists).
src/Controller/AppController.php
public function isAuthorized( $user ) {
$childClass = get_called_class();
if(method_exists($childClass, '_isAuthorized'))
return $childClass::_isAuthorized($user, $this -> request);
return static::_isAuthorized($user, $request);
}
static public function _isAuthorized($user, $request)
{
if ($user['role'] == 'admin')
return true;
return false; // By default deny any unwanted access
}
This is an example of your controller. You can specify only static _isAuthorized($user, $request) method, because for purposes of CakePHP default behavior it will be called from AppController::isAuthorized (see code above).
src/Controller/PostController.php
static public function _isAuthorized($user, $request)
{
$action = ($request instanceof Cake\Network\Request) ? $request -> action : $request['action'];
if($action == 'add' && $user['role'] == 'CanAddPosts')
return true;
return parent::_isAuthorized($user, $request);
}
As you can see I made $request to accept an array or Cake\Network\Request object. That's because CakePHP call it with Request object but when I call it I don't need to create this object, since my parameters are easy (see code above MenuCell.php).
Of course you can now do more complex logic like user can have more roles separated by comma and you can explode this and check if user has permission by in_array.
Now it's really up to you what is your logic behind permissions. Every controller can handle it's own permission managing while you can always access these permissions with every user and every page request.

Events and Full Calendar in CakePHP

I set the FullCalendar plugin in CakePHP, i can see it, it's displayed, i'm trying to put the events from my PHP Controller (the view admin feed) to my Full Calendar Plugin . I don't know what's wrong , no events are displayed in my calendar . I will be thankfull if someone can find why my calandar is empty .
Look the screen, this is what i have in my console :
The answer is NULL .
The JS file :
// JavaScript Document
$(document).ready(function() {
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
defaultView: 'agendaWeek',
firstHour: 8,
weekMode: 'variable',
aspectRatio: 2,
editable: true,
events: {
url: FcRoot + '/events/feed',
color: 'yellow', // an option!
textColor: 'black', // an option!
},
eventRender: function(event, element) {
///
}
});
});
EventsController
// The feed action is called from JS to get the list of events (JSON)
public function admin_feed($id=null) {
$this->layout = "ajax";
$vars = $this->params['url'];
$conditions = array('conditions' => array('UNIX_TIMESTAMP(start) >=' => $vars['start'], 'UNIX_TIMESTAMP(start) <=' => $vars['end']));
$events = $this->Event->find('all', $conditions);
foreach($events as $event) {
if($event['Event']['all_day'] == 1) {
$allday = true;
$end = $event['Event']['start'];
} else {
$allday = false;
$end = $event['Event']['end'];
}
$data[] = array(
'id' => $event['Event']['id'],
'title'=>$event['Event']['title'],
'start'=>$event['Event']['start'],
'end' => $end
);
}
$this->set("json", json_encode($data));
}
In View/Events/admin_feed
<?php
echo $json;
?>
The response now :
You should really take advantage of routing and views for this. It allows you to properly organize your code and keep things DRY.
public function admin_feed($id=null) {
$vars = $this->params['url'];
$conditions = array('conditions' => array('UNIX_TIMESTAMP(start) >=' => $vars['start'], 'UNIX_TIMESTAMP(start) <=' => $vars['end']));
$events = $this->Event->find('all', $conditions);
foreach($events as $event) {
if($event['Event']['all_day'] == 1) {
$allday = true;
$end = $event['Event']['start'];
} else {
$allday = false;
$end = $event['Event']['end'];
}
$data[] = array(
'id' => $event['Event']['id'],
'title'=>$event['Event']['title'],
'start'=>$event['Event']['start'],
'end' => $end
);
}
$this->set("events", $data);
}
Now, your admin_feed action is usable as more than just a json feed (even though that may be just want you want). This makes testing easier as well.
Add the following to your routes, to tell Cake that you want to allow the json extension:
Router::parseExtensions('json');
Then, add the RequestHandler to your controller components. This component will automatically switch to your json view and layout when a json extension is found.
Next, add a layout for all json views in /View/Layout/json/default.ctp:
<?php
echo $this->fetch('content');
Then, add your view in /View/Events/json/admin_feed.ctp:
<?php
echo json_encode($events);
That's it. Now, if you want to use admin_feed to view events in HTML, you can by adding a view for it.
Your full calendar url should now be: FcRoot + '/admin/events/feed.json'. Try just visiting it in the browser to see if you see the json.
do not set it, just echo it
echo json_encode($data);
set values are supposed to be used in view, in your case you are just 'returning' as ajax response
for ajax calls, view is not necessary at all, put $this->autoRender = false; in you function: this will prevent 'seeking' for view file.

CakePHP Audit Log Plugin user authentication

I'm trying to implement the Audit Trail plugin - https://github.com/robwilkerson/CakePHP-Audit-Log-Plugin
It all works great, however i can't get the user authentication working by following the instructions i get the following error -
Fatal error: Call to undefined method CakeErrorController::currentUser()
I have followed the instructions by adding
protected function currentUser() {
$user = $this->Auth->user();
return $user[$this->Auth->userModel]; # Return the complete user array
}
and adding
public function beforeFilter() {
...
if( !empty( $this->data ) && empty( $this->data[$this->Auth->userModel] ) ) {
$this->data[$this->Auth->userModel] = $this->currentUser();
}
}
to my appController, has anyone implemented this before or recognise the error?
For Cakephp 2.4 you have to do some changes in order to work with the Auth component:
In the AppModel:
public function currentUser() {
$userId = AuthComponent::user('id');
//Get the information of the user
$currentUser = $this->importModel('User')->find('first', array(
'conditions'=>array('User.id'=>$userId),
));
//Return all the User
return $currentUser['User'];
}
And now in your AppController:
The true is you don't need to do anything else in your controller, it's just to prevent some problem. So, OPTIONAL:
if( !empty( $this->request->data ) && empty( $this->request->data[$this->Auth->userModel] ) ) {
$user['User']['id'] = $this->Auth->user('id');
$this->request->data[$this->Auth->userModel] = $user;
}
It works for me.
Don't add the currentUser() function to your AppController, it has to be in your AppModel. Here's what my currentUser() function looks like using CakePHP 2.3:
public function currentUser() {
return array('id' => AuthComponent::user('id'));
}

cake php link to the current page with different prefix

In my default layout I would like to show link which points to the current page but with different prefix. I am using prefix 'language' to use address like www.site.com/eng/controller/action/param.
I tried $this->Html->link('eng', array('language' => 'eng') );
But this creates link with url eng/controller/action without passed arguments, without named arguments and without url params.
How I can do this? I would prefer elegant solution like 1 line of code - I know it can be done but can't find it :(.
Try this:
// helper method, possibly AppHelper, or in AppController and set a view var
function getCurrentParams() {
$route = Router::currentRoute();
$params = $this->request->params;
$pass = $params['pass'];
$named = $params['named'];
unset($params['pass'], $params['named']);
if (!empty($route->options['pass'])) {
$pass = array();
}
return array_merge($params, $named, $pass);
}
$params = $this->SomeHelper->getCurrentParams();
$params['language'] = 'eng';
// use $params for your link now
in my case this easy solution worked too!
<?php
App::uses('HtmlHelper', 'View/Helper');
class MyHtmlHelper extends HtmlHelper {
public function url($url = null, $full = false) {
if(!isset($url['language']) && isset($this->params['language']) && is_array($url)) {
$url['language']= $this->params['language'];
}
if(count($url) == 1 && isset($url['language'])){
$params = $this->params;
$url = array_merge($params['pass'],$url);
}
return parent::url($url, $full);
}
}
and in AppController
public $helpers = array(
...
'Html' => array('className' => 'MyHtml')
...
);
$this->Html->link('eng', array('language' => 'eng', 'pass') );
Something like this should work:
$url = Router::parse($this->here);
$url['language'='end';
$this->Html->link('eng', $url);
It's not one line, but you could compress it into one line but it would be pretty unreadable.
You could wrap it in a helper / function which would be a one line call though ;-)

Resources