Posts Tagged ‘system design’

How to limit user access in CakePHP: findMy

May 7th, 2010

Perhaps like many people starting a CakePHP project, I created a site where users could log in and create/modify their own files (in my case Japanese flashcards and tests) while not being able to screw with other people’s stuff.

One would think that you could solve this with some nifty ACL and Auth work, but if you thought that, then you would be wrong.

Unforunately ACL only lets you determine what actions a user is allowed to perform, not on which items they’re allowed to perform it.

And Auth only checks to make sure a person is logged in, not who they are or what they’re doing.

The Traditional Way

However, thanks to the glory of cake, it’s not that hard to limit a search by user! Just replace your find with the following function.

$my_file = $this->SomeModel->findAllByUserId($this>Auth->user(id);

And we’re done!

Oops! Not so fast!

We just got all of the SomeModels! Well crap, I guess we’ll have to make an option array

$opt = array(
   'conditions' => array(
      'SomeModel.user_id' => $this->Auth->user('id'),
   ),
   'order' => 'SomeModel.date DESC',
);
$my_file = $this->SomeModel->find('first', $opt);

That’s a lot of work for just getting my latest SomeModel. Even more work if I have to replace every instance of a simple find with something like that. I also need to do the same thing for every save and delete as well to make sure that they’re not saving over someone else’s data.

Sure, I could put that code throughout all my Controllers, but that’s messy — and if I forget to put it somewhere someone gets their data deleted.

So what can we do?

Well, I don’t know about you, but I put a function in my AppModel file that lets me easily make sure that whoever’s touching the file has permission to do so.

function findMy($type, $options=array())
{
   if($this->hasField('user_id') && !empty($_SESSION['Auth']['User']['id'])){
      $options['conditions'][$this->alias.'.user_id'] = $_SESSION['Auth']['User']['id'];
      return parent::find($type, $options);
   }
   else{
      return parent::find($type, $options);
   }
}

What this does

  1. Make sure that the model has an ‘user_id’ field
  2. Make sure that the user is logged in
  3. Add the user_id condition to the find options
    • Be sure to use the $this->alias in case you’re using an alias for the Model in a BelongsTo or HasMany relationship.
  4. Find the data

This is a pretty simple function, and can form the base for any logic that you want to do.

For example, if you want to allow Admins to view anything, just add an $_SESSION['Auth']['User']['role'] == ‘admin’ to the mix.

If you want to allow users access to any ‘public’ items, add a condition for ‘OR is_public == true’

Extending the function

This function is set in the AppModel, so it can be overridden in any of your defined models if you need to do any custom filtering on a per-model basis.

Also, it is possible to extend this to save and delete functions, to make sure that users are only saving or deleting their own files. If they try to delete a file that doesn’t belong to them, return false and alert the user that that file could not be found.

function deleteMy($id = null, $cascade = true)
{
   if (!empty($id)) {
      $this->id = $id;
   }
   $id = $this->id;

   if($this->hasField('user_id') && !empty($_SESSION['Auth']['User']['id'])){
      $opt = array(
         'conditions' => array(
            $this->alias.'.user_id' => $_SESSION['Auth']['User']['id'],
            $this->alias.'.id' => $id,
            ),
         );
      if($this->find('count', $opt) > 0){
         return parent::delete($id, $cascade);
      }
      else{
         return false;
      }

   }
   else
      return parent::delete($id, $cascade);
}

Conclusion

The power of CakePHP comes from it’s infinite extensibility, and the fact that at it’s core, it’s still just a PHP program.

While I recommend following MVC practices, and to use the built-in CakePHP functions as much as possible, there are times when you just need to do it the simple way.

Also, for those who balk at my use of $_SESSION, I talked with one of the CakePHP core developers at a conference a while ago, and was asking him about this problem.

I asked,

“Why is Auth only available in controllers? It would be much more useful if we could use it everywhere. Is it a design decision?”

He replied.

“Because it’s a component. That’s the only reason.”

Remember: the framework is there to help you. You are not its slave.

The Programmer’s Folly: Simple is best

November 24th, 2009

First, let me get this out in the open: I am not a marketer. I am not a content-organizer. I am a programmer. I like writing code, and I like creating new ways to do things. I like making things because it helps me see how they work — out-of-the-box-solutions bore me.

And that’s where I screw up

On Monday it was a national holiday here in Japan (勤労感謝の日) and I had about 4 hours to kill while the wife was out on her daily walk. I decided to work on my site — and what I needed more than anything was a nice heatmap to log user clicks.

I used the amazing ClickHeat which is a free heat-mapping application written in PHP. It works right out of the box, is amazingly configurable, and runs quickly thanks to it being written in clear PHP.

Wouldn’t this be great as a CakePHP Plugin!

This is where things start to get messy.

I love CakePHP. It makes development easy, is rather fast, and is highly extensible. So, I decided to wrap the ClickHeat application in a cake plugin, make it “easy to integrate” and put it in the CakePHP Bakery, instantly receiving fame, fortune and the accolades of my peers!

4 hours later

I had a mostly-working prototype that logged the data beautifully (although I couldn’t decide how to group the data on a dynamic site),  but didn’t have any of the nifty functions of the ClickHeat software such as sorting by date, admin panels, etc. because I hadn’t built those views yet. But I was hopeful!

Then my wife came home and I stopped programming for the day.

The next day, in 10 minutes

I decided to install ClickHeat on one of my corporate sites at work.

  1. Copy ClickHeat folder
  2. Set Cache & Log permissions
  3. Turn on Japanese Interface
  4. Done

10 minutes. In ten minutes I had accomplished what I couldn’t complete in 4 hours, because I was prepared to use an out-of-the-box solution instead of trying the be the programming bad-ass and integrate it with CakePHP.

So this morning, I copied the folder to my CakePHP dir, made one change to the .htaccess file:

RewriteRule    clickheat/(.*)   -   [L]

And now I have working heatmaps on my site. (I also put them on my wordpress blog, so that you can see them in action for yourself).

See the amazing Japanese Programming Heatmaps! (User: demo / Password: demo)

The final Score

So, what’s the final score?

Being a “bad-ass” programmer

  1. 4 Hours Development
  2. 1 extra hour of planning before sleep
  3. No Viewing Functionality
  4. Installation NOT user friendly
  5. Call time of 300ms per click for spinning up the cake processor
  6. Logging Works

Being a smart programmer

  1. 1 Change in my htaccess file
  2. 10 minutes to install/configure
  3. Call time of 150ms / click
  4. Not following “best practices” for cakephp

I think it’s pretty obvious what the best choice here is.

While I adore CakePHP and the things it lets you do simply by following convention — it’s very easy to get sucked into the “Best Practice” mindset, and waste a lot of time working on something that honestly doesn’t need to be tinkered with.

Don’t re-invent the wheel

It’s amazing how many times you can read that phrase and still find yourself re-inventing wheel after wheel after wheel. If you have a tool that does a job — use it. Do not worry about your fiddly code, and your “it’s not made here” mentality.

If the application is LACKING you can always edit it. But it makes no sense to start with something that does everything that you want, and then try to hack it apart just because you can. (Although I have to admit, it can be fun)

With those four hours, I could have

  • written 1 article for my site,
  • uploaded around 10 flashcard packs
  • added 2 tests to the site
  • read a book
  • played a lot of video games
  • watched a movie
  • watched four (4) episodes of Doctor Who or The Green Wing.

So Keith, this is a message to you:

  • Don’t be a tool
  • Use your time wisely
  • If something works, do NOT break it just because you want to see how it works.
  • (Eat your veggies — love mom)

You never know what you need until you don’t have it

August 25th, 2009

Just like the age-old axiom: “You never know what you have until it’s gone,” in programming and web design the more apropo phrase should be “You never know what you need until after you launch.”

Now, I’m perhaps the worst pundit of designing before creating (as I’ll delve into in another post) especially when I’m trying to learn a new system at the same time (as was the case with cakePHP and Ippatsu). However, I’ve noticed that even the most anal-retentive planners and system designers cannot foresee exactly what features and what data will be necessary for their users.

We can all make grand ideas about what would be a) cool to have or b) necessary for our users. But in the end, without actually having the system move, and have those desired functions come to the forefront, there’s really no way to figure out everything that you’ll need right from the get-go.

So, as I launched my site last week, and have finally started getting users coming in, I’ve noticed that there is some admin-only information that I really want.

  1. User information pages — At current, I can’t see how many flashcards each individual user has, and what they’ve been doing lately. I can only see how many times each flashcard has been registered, but not by whom.
  2. User login numbers — I currently have the login times of each user logged for security reasons, but it would be nice to also have the number of times each user has logged in. I could conceivably use this in the future for a “most active users” contest or widget or something.
  3. Full news pages — My top news page only shows excerpts, much like my grammar article pages (because they use the same template) but since I’m planning on having a lot more consistent and necessary data on that page, I think we need to show the full news articles, or at least until the “More” tag.
  4. A more complete messaging system — At current, I have a rather anemic messaging system that just logs a message to the DB with a topic, and who sent the message. I really need to pump that up a little so that I can have a full messaging system and then be able to extend that to the users.

So, within a week of opening, I already have a load of changes and fixes that to be perfectly honest, I should have had to start with, but never even noticed the need for.

Next time I’m going to delve into some programming issues, and talk about generating automatic sitemaps with CakePHP. Till then!