CakePHP on Webfusion, Ubuntu 8.04, suPHP
April 20, 2010 by Adam · Leave a Comment
If you install any CakePHP App on a Web Fusion Dedicated Server running Ubuntu 8.04 and suPHP you will likely get the following message:
Forbidden, perhaps you need to change the file permissions for this document or upload an index page.
This is because you need to add:
Options +FollowSymLinks
To the top of your .htaccess file inside the webroot folder.
Your homepage should then start loading after this, but clicking on any links will present you with a new error:
Internal Server Error, this is an error with your script, check your error log for more information.
You will need to modify .htaccess again and change the line:
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
to
RewriteRule ^(.*)$ /index.php?url=$1 [QSA,L]
The first is needed because suPHP/pretend root stops you from setting Options +FollowSymLinks in your httpd.conf file, and Webfusion don’t set it for you, I don’t really know why the second one is needed, but it seems to work.
Edit: Adding:
RewriteBase /
before
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
will also work, instead of adding the / to the above line
Integrating the Auth Component with a bespoke 3rd party Single Sign-On Service in CakePHP
September 14, 2009 by Adam · Leave a Comment
Background
I recently had to develop a marketing portal for a large French car manufacturer. They have a number of internal sites, which are accessed by all internal staff, all sales people at individual dealers, and also external agencies (such as us). They have one central “portal”, which is where a user logs in, and then has links to all of the other portals they have access to. This provides the basis of their “Single Sign-On” Service.
The Single Sign-On Service
Basically, the Single Sign-On Service is all based around this initial “portal” site. Each of the links from this portal to the other sites contained a “token” in the URL, which is an encrypted string containing the userid for the central “users” table, and a timestamp, which is the time the link was generated.
There is then a file (which we don’t have access to) in the server’s include_path which decodes this token, checks the session is valid and looks the user up in the central users table, assuming it was a valid portal token, it then sets all of the user’s details in the session and hands control back to you.
Our task was to integrate this with Cake’s Auth component, so we can cache the users in our local database and do our own application level access control. Using Cake, this was surprisingly easy, once you get past one tiny configuration issue.
Implementation
I got the guys in the webservices department to point the portal link for our site to the domain root. This way we can let Cake handle things automatically…
The first thing we need to do is tell Cake to execute the code inside UsersController::login(). We do that using autoRedirect in the AppController::beforeFilter().
function beforeFilter() {
$this->Auth->autoRedirect = false;
}
This now means Cake won’t try to log you in automatically, but it still redirects you to UsersController::login(), so the token that we had passed gets lost. I initially tried saving this to the Session, but Cake appears to reset this when it redirects to the login function, so I decided to save it in a Cookie instead.
function beforeFilter() {
$this->Auth->autoRedirect = false;
if(!empty($_GET['TOKEN'])) {
$login = array(
'token' => $_GET['TOKEN']
);
$this->Cookie->write('login', $login, false, 60);
}
}
The cookie only lasts 60 sections, as we shouldn’t ever need it for any longer than that.
The next step was the login function itself. We didn’t need to worry about connecting to the central user table, decrypting the token, or worrying about validity, as the login script provided on the server would deal with that.
function login() {
if ($this->Auth->user() == null) {
App::import('vendor', 'do_login', 'do_login/do_login.php');
$user = $this->_register_user();
if($user) {
if($this->Auth->login($user)) {
$this->redirect($this->Auth->redirect());
} else {
// this shouldn't happen if do_login.php does it's job!
}
} else {
// this should only happen if there's an error with the db
}
} else {
$this->redirect($this->Auth->redirect());
}
}
The above code first checks to see if the user is current logged into Cake, if they are, it just allows them to continue back to where they were. This might happen if they accidently close the window, and go back to the central portal to login.
If the user isn’t logged in, the first thing we do is include the provided login script. We can safely assume that once this has run, we have a valid user in the system. Next we call our private _register_user() function, which does a little bit of Cake trickery:
function _register_user() {
$sso_data = $this->Session->read('sso');
$this->Session->del('sso');
if (!empty($sso_data['internal']) && $sso_data['internal'] == true) {
$group = $this->User->Group->findByDefaultFor('internals');
$group = $group['Group']['id'];
$username = $sso_data['ddb_id'] . ".int";
} elseif(!empty($sso_data['agency']) && $sso_data['agency'] == true) {
$group = $this->User->Group->findByDefaultFor('agencies');
$group = $group['Group']['id'];
$username = $sso_data['ddb_id'] . ".agy";
} else {
$group = $this->User->Group->findByDefaultFor('internals');
$group = $group['Group']['id'];
$username = $sso_data['ddb_id'] . ".dlr";
}
$user = array(
'User' => array(
'username' => $username,
'password' => Security::hash($sso_data['ddb_id']),
'firstname' => $sso_data['forename'],
'lastname' => $sso_data['surname'],
'email' => $sso_data['email'],
'group_id' => $group
)
);
if($this->User->findCount(array('User.username' => $username)) == 0) {
$this->User->create();
if($this->User->save($user)) {
return $user;
} else {
return false;
}
} else {
return $user;
}
}
Taking this step by step, the first thing we do is read the user’s details from the Session. We can then safely delete this, as we shouldn’t need it again and don’t really want it there.
The next few lines check to see if the user is a dealer, an internal, or an agency user, and assigns them the appropriate group. We also set them up with a username for use with our Cake App which we know will be constant (central user database record id), and append a string to visually identify what type of user they are (this is just for our ease of use when reading the db).
We can then setup the Cake user array, using our new username and group, and the details from the Single Sign-On service. We use the central user database record id again as the password, it doesn’t matter what this is as at this point we know we already have a valid user, we just want something we can pass to Cake to log the user in. After a quick check to see if the username currently exists, we either save the new user and return the User details, or, we just return the details as they are.
This should leave us with a valid Cake User record in $user, which we can then pass to $this->Auth->login(), as if we were doing an ajax login, and finally redirect the user back to where they were.
One final pitfall – if a Cake login request comes from a 3rd party site, and you have security set to medium or high in config.php, the login will fail. You have to set your security to low for this to work. This isn’t the ideal situation, obviously we would prefer a security setting of “high”, but as our application doesn’t need to check the validity of the user we thought this was acceptable (in this instance).
How to alternate table row colours the easy way
September 11, 2009 by Adam · Leave a Comment
We’ve all seen the familiar code in our baked index files:
<?php
$i = 0;
foreach ($pricings as $pricing):
$class = null;
if ($i++ % 2 == 0) {
$class = ' class="altrow"';
}
?>
<tr <?php echo $class; ?>>
I’ve always thought that this wasn’t quite as elegant as it should be, and certainly not very DRY, as it was required everywhere you wanted alternate row colours. Well today I came across this nice little CakePHP helper by @markgandolfo for alternating table colours in a very similar way to Rails, which takes the above code and makes it look more like this:
<table> <?php foreach($items as $item): ?> <tr class="<?php echo $cycle->css(); ?>"> <td><?php echo $item; ?></td> </tr> <?php endforeach; ?> </table>
Much better.
Check it out here: http://github.com/markgandolfo/cakephp_cycle_css/tree/master
Simple “MVC” Javascript for CakePHP
September 9, 2009 by Adam · Leave a Comment
On a recent project we had the need to provide Javascript across a very large project, with lots of controllers, and lots of actions. We had a number of requirements for the Javascript:
Requirements
- We wanted the Javascript to be object oriented
- Although the application would not be public facing, internally the users were using a wide variety of platforms, and some were very old or very restrictive, so the application needed to degrade gracefully
- We didn’t want any inline Javascript, as with so many view files management would be a nightmare, and not very DRY
Design
Once the application was fully working without Javascript, we came up with a simple, yet powerful way to structure our code so that it required a minimal amount of modification to our views, yet allowed us the flexibility to customise every page. Our solution is not really MVC, as we only use the controller part of MVC, but it would be relatively easy to use the same method in cake to add the M & V. In a future post I may add this.
Implementation
The first step was to create app.js in webroot/js. This file would act as the Front Controller for our Javascript and handle the dispatching of or application. app.js should look something like this:
App = {
controller: null,
action: null,
afterFilter: function() {
if(isDefined(App.controller, 'afterFilter')) {
eval(App.controller + '.afterFilter();');
}
},
beforeFilter: function() {
if(isDefined(App.controller, 'beforeFilter')) {
eval(App.controller + '.beforeFilter();');
}
},
dispatch: function() {
App.beforeFilter();
if(isDefined(App.controller, App.action)) {
eval(App.controller + '.' + App.action + '();');
}
App.afterFilter();
}
}
function isDefined(object, variable) {
return (typeof(eval(object)[variable]) != 'undefined');
}
Breaking this up, you should see some familiar concepts from cake – we have our application-wide beforeFilter and afterFilter callbacks, which also call the beforeFilter and afterFilter methods on our current controller, and our dispatch function, which starts the Javascript execution, and defines the order of the callbacks. The isDefined function checks to see if the current action is defined on the current controller before trying to call it, this means that we don’t get a bunch of nasty undefined function errors if we don’t write a Javascript method for every action in our cake application.
The code that pulls it all together (and makes it nice and simple), it the code that we put in our cake layout file:
< ?php
link(
array(
//frameworks
'prototype/prototype',
'scriptaculous/scriptaculous',
//plugins
'prototip/prototip',
'lightview/lightview',
//mvc
'app',
'mvc/' . low($this->params['controller'])
)
);
?>
<script type="text/javascript">
window.onload = function() {
App.controller = '< ?php echo low($this->params['controller']); ?>';
App.action = '< ?php echo low($this->params['action']); ?>';
App.dispatch();
}
</script>
What we’re doing here is including our Javascript files in the normal cake way, with one exception – we are also loading a file in the directory /js/mvc with the same name as the current executing controller. Below that we are attaching a function to the onload event, and setting the controller and action variables to the current executing cake controller and action, and finally starting the whole thing off with a call to App.dispatch().
Example
The above code is very simple – but we think, very flexible, in it allows us to execute Javascript application wide, or on an action by action basis, allows us to keep everything Object Oriented, and means we don’t have to modify our view code to add new Javascript functionality, which is exactly what we need.
Search = {
beforeFilter: function() {
Event.observe(
'SearchQuery',
'click',
function() {
if($('SearchQuery').value == 'Enter keyword(s)...') {
$('SearchQuery').value = ''
}
}
);
},
results: function() {
alert('Here are your results...')
}
}
In the above example, we use the beforeFilter to remove the Enter keyword(s)… text from the search query box, which is available on every page in the search controller, and provide the user with a useless prompt on the results action, saying ‘Here are your results…’. Technically this should go into a Javascript “view” as it is a DOM manipulation, but that can wait for a future iteration.