I have a custom routing class that checks versioning of an object to allow for creation of draft versions of pages that wont appear on the live site. When an admin clicks to preview a draft version of a page my PublishingHelper class on the front-end (called from the routing class) checks the user's permissions to see if they are allowed to view the draft version of this page.
I am using this code:
$context = sfContext::getInstance();
$user = $context->getUser();
But $user is NULL.
Does anyone have any ideas? All my searches seem to say this is the right way of getting the user object.
Thanks,
Tom
Thanks for the comments Till/Jon, Ive managed to fix this now. The factories fix didnt work because while the user class is instantiated none of the filters have run therefore I was left with a useless user object.
I solved my problems simply by taking pretty much all the code in the matchesUrl() function of my custom routing class and putting in a new function doRouting() in the same class. matchesUrl() now looks like this:
public function matchesUrl($url, $context = array())
{
if (false === $parameters = parent::matchesUrl($url, $context))
{
return false;
}
$parameters['module'] = 'content';
$parameters['action'] = 'route';
$this->url = $url;
$this->context = $context;
return $parameters;
}
and the routing is deferred to after the factories and filters by using my "content" module/controller:
class contentActions extends sfActions
{
public function executeRoute(sfWebRequest $request)
{
$router = $this->getRoute();
$router->doRouting($router->url, $router->context);
}
And the doRouting() function now forwards directly to the appropriate module/action (correctly taking into account user permissions).
I think implementing such a restrictions would be easier to implement as a filter: http://www.symfony-project.org/reference/1_4/en/12-Filters
Related
We are currently working on an application with a Google Login with Laravel with Socialite. We have a Auth user who gets a permission number ex. 264. We have made a function which returns an array with all binary numbers this permission number is made off.
Because calling this function every single time a page loads may be kinda heavy, we thought of adding this once when the Auth::user() is created. We thought of adding a custom constructor in the Model, but we can't make it work.
function __construct($attributes = array()) {
parent::__construct($attributes);
$this->permissionsArray = PermissionHelper::permissionConverter($this->permissions);
}
But we can't get it to work, $this doesn't have values when calling this function.
TLDR;
Directly after making the Auth user I want to call the permissionConverter function and save the data to the user so we can use it more often. Any suggestions on how to do this?
EDIT: I checked all answers out today, succeeded with one of them, but I assumed Laravel put the authenticated user in the SESSION or something. I found out it doesn't and it gets all the data from the database every request. We couldn't do what we requested for unfortunately. So I just had to refactor the script and make it as efficient as possible (although it became a bit less readable for less experienced programmers).
Thanks for the help :D
Maybe you can use this solution ? https://stackoverflow.com/a/25949698/7065748
Create a on the User Eloquent model a boot method with
class User extends BaseModel {
public static function boot() {
static::creating(function($model) {
$model->permissionsArray = PermissionHelper::permissionConverter($model->permissions);
});
// do the same for update (updating) if necessary
}
}
Can't you just use this method ?
If new user:
$user = new User(); // or User:create(['...']) directly
$user->name = 'toto';
// and all other data
or
$user = Auth::user();
then
$user->permissionsArray = PermissionHelper::permissionConverter($user->permissions);
$user->save();
I'm trying to add some functionality to the back end of a Bolt CMS installation that does the following:
Check if the user is a member of the "limited editor" group.
If so, only list content which they, personally, own.
This needs to be within the controller, not using Twig.
I've got the user object using
$user = $app['users']->getCurrentUser();
I guess I could use
in_array('limitededitor', $user["roles"]);
But I wondered if there was any existing function in Bolt that would streamline this, like "isAllowed" but for checking role membership?
This is what I've used in the past to determine whether I mount a controller (and thus give access to the new urls), the key part is the users service has a hasRole method but you need to check by user id.
public function checkAuth()
{
$currentUser = $this->app['users']->getCurrentUser();
$currentUserId = $currentUser['id'];
foreach (['admin', 'root', 'developer', 'editor'] as $role) {
if ($this->app['users']->hasRole($currentUserId, $role)) {
return true;
}
}
return false;
}
My ultimate goal is to check whether a certain route name falls within the secured area of the application.
I am thinking that to achieve that, I should parse security.yml, get the "firewalls" section and go through each firewall trying to match the path of my route name to the pattern of the "secured_area" firewall.
Being new to Symfony2, I tried digging in its source to find how it parses the security.yml itself, but I'm a bit overwhelmed by the number of classes involved in the process.
So, I'm asking for advice:
Is this the correct way to approach the problem or is there a more straight forward solution?
Any hints on how to write this?
Well, I ended up doing something along those lines:
$route = $this->router->getRouteCollection()->get($routeName);
$yamlParser = new Yaml\Parser();
$securityConfig = $yamlParser->parse(file_get_contents($this->securityFilePath));
foreach ($securityConfig['security']['firewalls'] as $firewallName => $definition) {
if (isset($definition['pattern']) && preg_match('{'.$definition['pattern'].'}', $route->getPath())) {
return $firewallName;
}
}
May not be the cleanest solution, but does the job.
With Symfony 3 there is another way to do that which is hinted at here https://symfony.com/blog/new-in-symfony-3-2-firewall-config-class-and-profiler
By injecting the firewall map to your controller or service you can use the getFirewallConfig(Request $request) with a dummy request instansiated with the url version of your route.
Something like this:
// Your controller or service
public function __construct(
UrlGeneratorInterface $router,
FirewallMapInterface $firewallMap,
) {
$this->router = $router;
$this->firewallMap = $firewallMap;
}
public function getFirewallId($routeName)
{
$fwConfig = $this->firewallMap->getFirewallConfig(
Request::create($this->router->generate($routeName),'GET'));
return $fwConfig->getName();
}
I have built a simple Notification system in my Cake app that I want to have a function that will create a new notification when I call a certain method. Because this is not something the user would actually access directly and is only database logic I have put it in the Notification model like so:
class Notification extends AppModel
{
public $name = 'Notification';
public function createNotification($userId, $content, $url)
{
$this->create();
$this->request->data['Notification']['user_id'] = $userId;
$this->request->data['Notification']['content'] = $content;
$this->request->data['Notification']['url'] = $url;
$result = $this->save($this->request->data);
if ($result)
{
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
}
}
And then whenever I want to create a notification within my app I just do:
$this->Notification->createNotification($userId,'Test','Test');
However this doesn't work! The controller is talking to the model fine, but it doesn't create the row in the database... I'm not sure why... but it would seem I'm doing this wrong by just doing all the code in the model and then calling it across the app.
Edit: Based on answers and comments below, I have tried the following the code to create a protected method in my notifications controller:
protected function _createNotification($userId, $content, $url)
{
$this->Notification->create();
$this->request->data['Notification']['user_id'] = $userId;
$this->request->data['Notification']['content'] = $content;
$this->request->data['Notification']['url'] = $url;
$result = $this->save($this->request->data);
if ($result)
{
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
}
Now the thing that is stumping me still (apologies if this is quite simple to others, but I have not used protected methods in CakePHP before) is how do I then call this from another controller? So for example If have a method in my PostsController and want to create a notification on successful save, how would I do this?
I thought about in my PostsController add method:
if($this->save($this->request-data){
$this->Notification->_createNotification($userId,'Test','Test');
}
But being protected I wouldn't be able to access the method from outside of the NotificationsController. Also I'm using the same syntax as if I was calling a function from a model so again it doesn't feel right.
Hopefully someone can help me out and get me back on track as this is a new area to me.
the controller should pass all data to the model
$this->createNotification($this->request->data);
the model then can use the data:
public function createNotification(array $data) {
$key = $data[$this->alias]['key'];
$data[...] = ...;
$this->create();
return $this->save($data);
}
you never ever try to access the controller (and/or its request object) from within a model.
you can also invoke the method from other models, of course:
public function otherModelsMethod() {
$this->Notification = ClassRegistry::init('Notification');
$data = array(
'Notification' => array(...)
);
$this->Notification->createNotification($data);
}
and you can make your methods verbose, but that usually makes it harder to read/understand/maintain with more and more arguments:
public function createNotification($userId, $content, $url) {
$data = array();
// assign the vars to $data
$data['user_id'] = $userId;
...
$this->create();
return $this->save($data);
}
so this is often not the cake way..
Methods in a model are not "publicly accessible" by definition. A user cannot call or invoke a method in a model. A user can only cause a controller action to be initiated, never anything in the model. If you don't call your model method from any controller, it's never going to be invoked. So forget about the "non-public" part of the question.
Your problem is that you're working in the model as if you were in a controller. There is no request object in a model. You just pass a data array into the model method and save that array. No need for $this->request. Just make a regular array(), put the data that was passed by the controller in there and save it.
The whole approach is totally wrong in the MVC context IMO and screams for the use of the CakePHP event system. Because what you want is in fact trigger some kind of event. Read http://book.cakephp.org/2.0/en/core-libraries/events.html
Trigger an Event and attach a global event listener that will listen for this kind of events and execute whatever it should do (save something to db) when an event happens. It's clean, flexible and extendible.
If you did a proper MVC stack for your app most, if not all, events aka notifications should be fired from within a model like when a post was saved successfully for example.
This is what I have ended up doing. While it certainly isn't glamorous. It works for what I want it to do and is a nice quick win as the notifications are only used in a few methods so I'm not creating a large amount of code that needs improving in the future.
First to create a notification I do the following:
$notificationContent = '<strong>'.$user['User']['username'].'</strong> has requested to be friends with you.';
$notificationUrl = Router::url(array('controller'=>'friends','action'=>'requests'));
$this->Notification->createNotification($friendId,$notificationContent,$notificationUrl);
Here I pass the content I want and the URL where the user can do something, in this case see the friend request they have been notified about. The url can be null if it's an information only notification.
The createNotification function is in the model only and looks like:
public function createNotification($userId, $content, $url = null)
{
$this->saveField('user_id',$userId);
$this->saveField('content',$content);
$this->saveField('url',$url);
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
This creates a new record in the table with the passed content, sets its status to 0 (which means unread) and the date it was created. The notification is then set as read when a user visits the notifications page.
Again this is most probably not an ideal solution to the problem outlined in this question... but it works and is easy to work with And may prove useful to others who are learning CakePHP who want to run functions from models when building prototype apps.
Remember nothing to stop you improving things in the future!
First of all, you can improve your last solution to do one save() (instead of 5) the following way:
public function createNotification($userId, $content, $url = null){
$data = array(
'user_id' => $userId,
'content' => $content,
'url' => $url,
'datetime' => date('Y-m-d H:i:s'),
'status' => 0
);
$this->create();
$this->save($data);
}
When I began programming CakePHP(1.3) more than a year ago I also had this problem.
(I wanted to use a function of a controller in any other controller.)
Because I didn't know/researched where to place code like this I've done it wrong for over a year in a very big project. Because the project is really really big I decided to leave it that way. This is what i do:
I add a function (without a view, underscored) to the app_controller.php:
class AppController extends Controller {
//........begin of controller..... skipped here
function _doSomething(){
//don't forget to load the used model
$this->loadModel('Notification');
//do ur magic (save or delete or find ;) )
$tadaaa = $this->Notification->find('first');
//return something
return $tadaaa;
}
}
This way you can access the function from your Notification controller and your Posts controller with:
$this->_doSomething();
I use this kind of functions to do things that have nothing to do with data submittance or reading, so i decided to keep them in the app_controller. In my project these functions are used to submit e-mails to users for example.. or post user actions to facebook from different controllers.
Hope I could make someone happy with this ;) but if you're planning to make a lot of these functions, it would be much better to place them in the model!
I need a solution where authenticated users are allowed access to certain Controllers/Actions based not on their user type :ie. admin or normal user (although I may add this using standard ACL later) but according to the current status of their user.
For example :
Have they been a member of the site for more than 1 week?
Have they filled in their profile fully?
Actually, now that I think about it, kind of like they have on this site with their priviledges and badges.
For dynamic condition-based tests like you are describing, you can use dynamic assertions in your Zend_Acl rules.
For example:
class My_Acl_IsProfileComplete implements Zend_Acl_Assert_Interface
{
protected $user;
public function __construct($user)
{
$this->user = $user;
}
public function assert(Zend_Acl $acl,
Zend_Acl_Role_Interface $role = null,
Zend_Acl_Resource_Interface $resource = null,
$privilege = null)
{
// check the user's profile
if (null === $this->user){
return false;
}
return $this->user->isProfileComplete(); // for example
}
}
Then when defining your Acl object:
$user = Zend_Auth::getInstance()->getIdentity();
$assertion = new My_Acl_Assertion_IsProfileComplete($user);
$acl->allow($role, $resource, $privilege, $assertion);
Of course, some of the details depend upon the specifics of what you need to check and what you can use in your depend upon what you store in your Zend_Auth::setIdentity() call - only a user Id, a full user object, etc. And the roles, resources, and privileges are completely app-specific. But hopefully this gives the idea.
Also, since the assertion object requires a user object at instantiation, this dynamic rule cannot be added at Bootstrap. But, you can create the core Acl instance with static rules during bootstrap and then register a front controller plugin (to run at preDispatch(), say) that adds the dynamic assertion. This way, the Acl is fully populated by the time you get to your controllers where presumably you would be checking them.
Just thinking out loud.