Editor's note: This series was originally published in 2006, and has undergone several revisions since in order to keep up with ongoing developments on CakePHP. This revision was written for CakePHP V1.3.4.
This "Cook up Web sites fast with CakePHP" series is designed for PHP application developers who want to start using CakePHP to make their lives easier. In the end, you will have learned how to install and configure CakePHP, the basics of Model-View-Controller (MVC) design, how to validate user data in CakePHP, how to use CakePHP helpers, and how to get an application up and running quickly using CakePHP. It might sound like a lot to learn, but don't worry — CakePHP does most of it for you.
This article assumes you have already completed Part 1 and Part 2, and that you still have the working environment you set up for those tutorials. If you do not have CakePHP installed, you should run through Parts 1 and 2 before continuing.
It is assumed that you are familiar with PHP, have a fundamental grasp of database design, and are comfortable getting your hands dirty.
Before you begin, you need to have an environment in which you can work. CakePHP has reasonably minimal server requirements:
- An HTTP server that supports sessions (and preferably
mod_rewrite). This tutorial was written using Apache V2.2.4 withmod_rewriteenabled. - PHP V4.3.2 or later (including PHP V5). This tutorial was written using PHP V5.2.1.
- A supported database engine. This tutorial was written using MySQL V5.0.67.
You'll also need a database ready for your application to use. The article will provide syntax for creating any necessary tables in MySQL.
The simplest way to download CakePHP is to visit CakePHP.org and download the latest stable version from the Downloads section. This tutorial was written using V1.3.4. (see Resources).
At the end of Part 2, you were given another opportunity to put your skills to work by enhancing Tor. You were to use Bake to generate views and a controller for dealers, then verify that the dealer name is unique, fix a bug in the products ACLs, and change the products view to only show Edit and Delete buttons for the users who can use them. It was a long to-do list. How did you do?
Baking the dealers controller and views should have been straightforward. From your /webroot/app directory, you would have launched the Cake Console.
To generate the controller, you would have entered
C in the first menu and selected the
dealers controller. To generate the views, you
would have entered V in the first menu and
selected the dealers controller again. These
steps were virtually identical to the steps you followed to create the
products controller and views.
You also had to ensure that dealers had unique titles. You can do this by
adding a beforeValidate method to the
Dealer model, as shown in Listing 1.
Listing 1. The
Dealer model
<?php
class Dealer extends AppModel
{
var $name = 'Dealer';
var $hasMany = array ('Product' => array('className' => 'Product',
'foreignKey'=>'dealer_id'));
var $actsAs = array('Acl' => array('type' => 'controlled'));
function beforeValidate() {
if (!$this->id) {
if ($this->findByTitle($this->data['Dealer']['title'])) {
$this->invalidate('title_unique');
return false;
}
}
return true;
}
function parentNode() {
return null;
}
}
?>
|
This would have also required modification of the add view for the dealer,
much like you did for the user register view, to look for the
title_unique error.
If you're paying attention, you'll also notice that we added an
$actsAs line and a
parentNode method. These were necessary in
order to create the aco entries for Dealers. Without these entries, if you
created a new dealer, and tried to add a new product using that dealer,
you'd have run into some issues.
Another one of your tasks was to fix a bug in the
products
add method. As the method was left at
the end of Part 2, anyone could add a product, even if logged out. There are
several ways to fix this.
Using access-control lists (ACLs), you could have created an action in the
products controller and used it to grant create access to the
user's access-request object (ARO) on each dealer ACO. You then would have
deleted the action since you didn't need it anymore. Next, you could have
modified the dealer add action to grant
create permissions for the dealer ACO to the user's ARO.
Finally, in the products add method, you could
have checked to make sure the user had permissions.
But given the task at hand, there was a much simpler way, as shown in Listing 2.
Listing 2. Checking if a user is logged in on product's
add action
function add() {
$username = $this->Session->read('user');
if ($username) {
... the rest of your add function goes here
} else {
$this->redirect(array('controller' =>
'users', 'action'=>'login'), null, true);
}
}
|
Look familiar? It should. It's the same check you use to see if a user was
logged in on the user's index action. If the
user is logged in, he is obviously a user. It's something of a trick
solution, but it does show you that there's more than one way to skin a
user.
As it stands, the dealer actions are completely unprotected. Again, these should be modified so that only users can actually use them, but ACLs that are more stringent could and should be applied, though full coverage of this is too big to cover here. Consider something like this:
- Any user can add or view a dealer.
- Only the user who created a dealer can modify or delete the dealer.
This could be worked into the application further, such as:
- If a user has created a dealer, he can add another user to the dealership.
- Any user in a dealership can modify any product created for dealership.
There are many other ways this could be done. Experiment with a few. Don't be afraid to get your hands dirty.
The last thing you needed to do was modify the products views so that Edit and Delete buttons are only shown to the users who can edit or delete the product. To start, you should remove the Edit and Delete buttons from the Products index view. Instead, you could check each product for the user's rights and show or hide the buttons in the index view, but this solution may not perform to your satisfaction.
In the Products view action, you could check to see if the user has permission to update or delete the product and set variables that can be used to display or hide the buttons in the view. The view action for the Products controller should have something like the code in Listing 3.
Listing 3. Possible
view action solution
if (@$this->Acl->check(
array('model' => 'User', 'foreign_key' => (int) $this->Session->
read('user_id')),
array('model' => 'Product', 'foreign_key' => $product['Product']['id']),
'update')
) {
$this->set('showUpdate', TRUE);
} else {
$this->set('showUpdate', FALSE);
}
if (@$this->Acl->check(
array('model' => 'User', 'foreign_key' => (int) $this->Session->
read('user_id')),
array('model' => 'Product', 'foreign_key' => $product['Product']['id']),
'delete')
) {
$this->set('showDelete', TRUE);
} else {
$this->set('showDelete', FALSE);
}
|
And in products/view.ctp, you need something like what is shown in Listing 4.
Listing 4. Possible products/view.ctp solution
<?php if ($showUpdate) { ?>
<li><?php echo $html->link(... your edit link code ... ); ?> </li>
<?php } ?>
<?php if ($showDelete) { ?>
<li><?php echo $html->link(... your edit link code ... ); ?> </li>
<?php } ?>
|
Again, don't worry if your solutions don't exactly match these solutions. They are intended to be examples, not dogma. But you should download the code archive for Part 3 so that we're all on the same page.
When dealing with a Web application, the importance of data security cannot be overstated. At the risk of sounding like a credit security commercial, you have to be constantly on your guard against hackers, crackers, script kiddies, identity thieves, spammers, phishers, criminals, and just plain old bad people. Yet while it has been this way for years, data security is still frequently treated lightly. It doesn't matter how beautiful and elegant your Web application is, bad data security will bring your application to its knees. To understand how to deal with bad data, you should get familiar with some of the basic problems you face.
What it means to secure your data
Despite bad data security causing so much trouble, the whole problem can be summarized in five words: Know what you are getting. Data security does not mean stripping out all HTML — though you may want to. Nor does it mean stripping out any special characters — though you may want to. The fundamental principle is that your application knows what it is getting.
This is not the same as knowing what you want. Nor is it the same as knowing what your application has requested. And it's certainly not the same thing as accepting everything and anything.
A SQL injection vulnerability occurs when a user is able to pass SQL code
directly to the application in such a way that the code will be executed
in a query. For example, the user is presented with a login screen, in
which the user name and password is entered. The
password variable might be used in the query
shown in Listing 5
Listing 5. SQL statement with
password variable
"select * from users where username = '" + $username + "'
and password = '" + $password + "'"
|
Consider what would happen if the user submitted the following password:
' or '1' = '1. The final SQL statement might
look something like Listing 6.
Listing 6. Final SQL statement
"select * from users where username = 'zaphod'
and password = 'secret' or '1' = '1'"
|
Not checking for SQL-specific characters can open your application up to a wide range of vulnerabilities. Properly used, Cake can make it easy to protect your application from this type of vulnerability.
Cross-site scripting (XSS) refers to a large classification of vulnerabilities that focus on the ability to present malicious code to an unsuspecting user. This usually takes the form of malicious JavaScript, which could do anything from annoying the user to capturing data from cookies.
At the heart of the XSS vulnerability is an application that filters user
data improperly once submitted. For example, suppose you're building an
application with a forum and did no filtering against the user data.
Anything submitted for a forum post could be displayed. As an evil user,
suppose I submit the following text as a forum post:
<script>alert("EXPLETIVES!!!")</script>.
If the application permits this text, anyone who viewed my post would get a JavaScript alert box that shouted expletives at them. While not particularly harmful, it's certainly not something you want the boss to see.
This is a very simple example of a harmless XSS exploitation. While this example is fairly harmless, XSS is anything but. XSS has been used to steal passwords, steal credit card numbers, forge news stories, and much more. Protecting yourself and your application from XSS is important. CakePHP can help you do so.
Cross-Site Request Forgery (CRSF) may not be as popular or as well known as
XSS, but that doesn't make it any less dangerous. To demonstrate, suppose
your application included a forum, and the forum granted a thread's author
permission to delete the thread. You have built this into the forum as a
button that is only shown to the author, and you even verify that the
author is the one who made the request before performing the actual
delete. It might work by posting a field name called
action with the value
delete and a field name called
id that contains a unique ID for the thread. In
a query string, it might look something like:
http://localhost/forum.php?action=delete&id=1729.
Now suppose we post an image, or have the ability to specify an image as a signature, and we provide as the URL to that image the same query string. In HTML, it would ultimately look something like this:
<img src="http://localhost/forum.php?action=delete&id=1729">
|
I can't go to the URL directly myself because I'm not the author, and the
application knows it. But if the author views my post, the browser will
attempt to load the image by requesting
http://localhost/forum.php?action=delete&id=1729,
and since the author is making the request, the thread gets deleted.
This is an overly simple look at CSRF and how it works. CakePHP's Security component can help protect you from this as well.
The Sanitize component is used when you want data to fit a certain set of rules. In Cake, all incoming data is properly escaped, which nips most potential SQL Injection problems in the bud. Beyond that, Cake provides a number of methods in the Sanitize component you can use to clean up your data.
Sanitize is CakePHP's class to help you deal with data security issues. Unlike the ACL component discussed in Part 2, the Sanitize component is included by adding a line to the top of your controller. For example, if you want to use Sanitize in your products controller, the top of your controller might look like what is shown in Listing 7.
Listing 7. Using Sanitize in products
<?php
App::import('Sanitize');
class ProductsController extends AppController
{
... |
Sanitize provides four methods for applying varying levels of data security to user-submitted data. Each method serves a distinct purpose.
This is the strictest of the available methods. The
paranoid method will strip a string of all
characters that are not alphanumeric (a-z, A-Z or 0-9). The
paranoid method accepts an input string and an
optional array ($allowedChars). The
$allowedChars array can be used to pass an
array of characters that are also permissible. For example, if you wanted
data to be alphanumeric, underscores, and periods, you would use something
like this:
$clean = Sanitize::paranoid($your_data, array('_','.')); |
Note: The paranoid method will
strip out any spaces unless you pass ' ' (a
blank space, in quotes) as an allowed character in the
$allowedChars array.
This approach to data security is known as whitelisting. Rather than stripping out characters you don't want (blacklisting), you are stripping out all characters except for those you have explicitly stated are acceptable. This works best for dealing with pieces of data that must follow specific rules (such as user names, e-mail addresses, and passwords), but a whitelisting approach can be used for almost any type of nonbinary data.
The html method of Sanitize can pass two
parameters: the string being Sanitized and an array of options. Regardless
of the options you pass, the method will pass the string to the PHP
function htmlentities and return the result.
The options you can pass to the html method
include:
remove— If this is set to true, the string is passed to the PHP function strip_tags to strip out any HTML.charset— If you pass a value for charset, this value will be passed to the htmlentities function. If you do not pass a value for charset, Cake will attempt to determine a good value for this option and pass that to htmlentities instead.quotes— Valid values for this parameter areENT_COMPAT(convert double quotes, ignore single quotes),ENT_QUOTES(convert double and single quotes), andENT_NOQUOTES(do not convert double or single quotes). If you do not pass a value, it defaults toENT_QUOTES. This value is then passed on to thehtmlentitiesfunction.
The escape method accepts a string of any kind and returns a SQL safe
version of the same string. For example,
Sanitize::escape("O'Brien") would return a
string with the value O\'Brien.
The clean method takes a string or an array as
input and returns the same string or array after it has been recursively
"cleaned." The cleaning process covers a wide range of potential data
problems, such as handing tricky backslashes, weird spaces, HTML, carriage
returns, and more.
You can view the code for these methods in
cake/libs/sanitize.php to get a better handle
on what's involved. You should never modify the base CakePHP files,
however. It will make upgrading to future releases difficult and could
cause unexpected behavior.
Cake's Sanitize class makes it easy to clean up
your data. You should use the functionality it provides to help lock down
your application. Now let's take a look at a Cake component that can help
lock things down even more: the Security component.
To use the CakePHP Security component, add it to the array of components in
your controller, as you did with ACL:
var $components = array ('Acl', 'Security');.
By simply including the Security component, several things happen automatically, even if you don't use it to protect an action:
- Using the core Security class, an authentication key is generated and
written to the session. The authentication key will expire according
to the settings in
app/config/core.php. - The authentication key is set as a class variable in your controller.
- If any view generated by the controller uses
$form->create()to create a form, a hidden input field named_Token/keywill be included in the form containing the authentication key. When the form is posted, the value of this field is compared to the value of the authentication key in session, to verify that the form submission was valid. Once this validation takes place, a new authentication key is generated and set in the session.
Now that you have included the Security component, you need to create the
method beforeFilter in the controller using the
Security component. This method will be executed automatically by the
controller before any method called by the user. This is the place you
typically will want to perform security checks.
There are a couple ways you can use what the Security component is doing
for you here. The first is to require that forms use the
POST method. The second is to require a valid
authentication key. Using the two together creates a powerful security
base for your application.
The requirePost method tells CakePHP to ignore
any information submitted to the specified action unless
POST was used. The
requirePost method is passed a list of actions
within the controller you want to protect. For example, if you wanted to
use requirePost to protect the
delete and add
methods, your beforeFilter method would look
like Listing 8.
Listing 8. Using
requirePost to protect the delete and add methods
function beforeFilter()
{
$this->Security->requirePost('delete', 'add');
}
|
By requiring that an action only use data from a form post, you eliminate the ability for someone to forge a request using a query string.
The Security requireAuth method tells CakePHP to
validate any form submission with the authorization key mentioned earlier.
This validation will only happen if the form was submitted via
POST.
Like the requirePost method,
requireAuth is passed a list of actions within
the controller you want to protect. For example, if you wanted to use
requireAuth to protect the
delete and add
methods, your beforeFilter method would look
like Listing 9.
Listing 9. The
beforeFilter method
<
function beforeFilter()
{
$this->Security->requireAuth('delete', 'add');
}
|
To use requireAuth and
requirePost, you would just specify both in the
beforeFilter method (see Listing 10).
Listing 10. Specifying
requireAuth and requirePost
function beforeFilter()
{
$this->Security->requireAuth('delete', 'add');
$this->Security->requirePost('delete', 'add');
}
|
Using requireAuth and
requirePost to protect an action is a powerful
combination. But you can mix and match the two if you want varying levels
of protection for different methods (see Listing
11).
Listing 11. Mix and match
requireAuth and requirePost
function beforeFilter()
{
$this->Security->requireAuth('delete');
$this->Security->requirePost('delete', 'add');
}
|
By requiring that a form submission contains a valid authorization key
before processing, you make it much more difficult for someone to forge a
form submission for another user. Using
requireAuth and
requirePost together is a great way to help
lock down your application.
While there are obvious advantages to using
requireAuth to protect actions, there are also
some disadvantages that should be addressed. Most of these disadvantages
fall into the category of "caching problems."
The authentication key is regenerated every time a form is evaluated with
requireAuth. This means that if a user submits
a form with a key that has already been used, the form submission will be
considered invalid. There are several cases in which this could occur,
including but not limited to using multiple browser windows, using the
Back button to return to a previous page, browser
caching, proxy caching, and more. While you may be tempted to write off
these problems as user error, you should resist the temptation and plan on
handling invalid form submissions gracefully.
What happens to invalid form submissions?
If a request is rejected by requirePost or
requireAuth, the application exits and the user
is sent a 404 page. To override this behavior, you can set the
$blackHoleCallback property of the Security
component to the name of a function within the controller. For example, if
you had an action called invalid and a corresponding view, you could set
tell the Security component to send bad requests to the invalid action.
You can do this by adding the following line to the beginning of your
beforeFilter method:
$this->Security->blackHoleCallback='invalid';.
This gives you some control over presentation in the event that a user manages to submit an invalid request via legitimate means. A good example of this would be a user using the application, taking a lunch break, and returning to the application after the authentication key had expired.
Now that you know how to use the Sanitize and Security components, put them to work in Tor.
Start off by sanitizing everything. For products, users and dealers,
sanitize all data submitted. It's up to you to determine which Sanitize
method is best used. Next, look at the controllers and their actions, and
determine which ones need to be protected using
requireAuth, and which ones need to be
protected using requirePost. Implement the
Security component and use it accordingly. Finally, create an action for
each controller called invalid and use the method to inform the user that
their form submission was invalid.
The Sanitize and Security components of CakePHP are not magic bullets. Using them does not mean you can forget about application security. However, using them will make your life much easier in terms of dealing with some of the more common security-related tasks. By cleaning up your data and ignoring data submitted improperly, you have made excellent first steps in securing your application.
Part 4 focuses primarily on the Session component of CakePHP, demonstrating three ways to save session data, as well as the Request Handler component to help you manage multiple types of requests (mobile browsers, requests containing XML or HTML, etc.).
| Description | Name | Size | Download method |
|---|---|---|---|
| Part 3 source code | os-php-cake3.source.zip | 11KB | HTTP |
Information about download methods
Learn
- Visit CakePHP.org to learn more.
- The CakePHP API has been thoroughly documented. This is the place to
get the most up-to-date documentation.
- There's a ton of information available at
The Bakery, the CakePHP user
community.
-
CakePHP Data
Validation uses PHP Perl-compatible regular
expressions.
- Read a tutorial titled "How to use regular expressions in PHP."
- Want to learn more about design patterns?
Check out
Design Patterns: Elements of Reusable Object-Oriented
Software, also known as the "Gang Of Four"
book.
- Check out some Source
material for creating users.
- Check out Wikipedia's Model-View-Controller.
- Here is more useful background on the Model-View-Controller.
- Check out a list of software design patterns.
- Read more about Design
Patterns.
-
PHP.net is the central resource for PHP
developers.
- Check out the "Recommended PHP reading list."
- Browse all the PHP content on developerWorks.
- Follow developerWorks on
Twitter.
- Expand your PHP skills by checking out
IBM developerWorks' PHP project resources.
- To listen to interesting interviews and
discussions for software developers, check out developerWorks
podcasts.
- Using a database with PHP? Check out the
Zend
Core for IBM, a seamless, out-of-the-box, easy-to-install PHP
development and production environment that supports IBM DB2
V9.
- Stay current with developerWorks' Technical events and webcasts.
- Check out upcoming conferences, trade
shows, webcasts, and other Events around the world that are of interest to IBM open source
developers.
- Visit the developerWorks Open source
zone for extensive how-to information, tools, and project updates
to help you develop with open source technologies and use them with IBM's
products.
- Watch and learn about IBM and open source
technologies and product functions with the no-cost developerWorks On demand demos.
Get products and technologies
- Download CakePHP V1.3.4.
- Get the Apache HTTP Server. This article used
Apache V2.2.4.
- Get PHP. This article used PHP V5.3.2.
- Get MySQL. This article used
V5.0.67.
- Innovate your
next open source development project with IBM trial
software, available for download or on DVD.
- Download IBM product
evaluation versions or explore
the online trials in the IBM SOA Sandbox and get your hands on
application development tools and middleware products from DB2®,
Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
- Participate in developerWorks
blogs and get involved in the developerWorks community.
- Participate in the developerWorks PHP Forum: Developing PHP applications with IBM Information
Management products (DB2, IDS).




