Friday, September 23, 2011

Database Configuration for MSSQL

If you are connecting to a MSSQL database, first ensure that your php-mssql package is installed. A simple command can show you this:
$ rpm -qa | grep php-mssql
The output should look something like this:
php-mssql-5.3.8-4.el6.remi.x86_64
Next check that freetds is installed.
$ rpm -qa | grep freetds
The output should look something like this:
freetds-0.82-6.el6.x86_64
You should now be able to connect to your MSSQL database directly from the command line. Make sure to replace the server, user and password with valid information.
$ /usr/bin/tsql -S [mssql.servername.or.ip] -U [ValidUser]
locale is "en-US.UTF-8"
locale charset is "UTF-8"
Password: [password]
1>
Enter "quit" to exit your successful connection. If the tsql command doesn't return the 1> prompt, verify that you can get to your MSSQL server with the following command:
ssh [mssql.servername.or.ip] -p 1433
Also verify that your username and password are valid.
At this point you can set your database configuration to connect to the database.
    var $default = array(
        'driver' => 'mssql',
        'persistent' => false,
        'host' => 'mssql.server.or.ip',
        'login' => 'username',
        'password' => 'password',
        'database' => 'database',
        'prefix' => ''
    );
Through much trial and error I've discovered that if you connect directly to the server in this manner some commands, such as mssql_bind, mssql_execute, and mssql_init don't work. Now you could setup and execute stored procedure commands through mssql_query this isn't as secure.
Instead, set up a freetds configuration for the server, connect with that instead, and the commands will now work fine.
Edit /etc/freetds.conf and add the following block:
[ServerIdentifier]
        host = [mssql.servername.or.ip]
        port = 1433
        tds version = 8.0
Then use the ServerIdentifier you chose instead of the server name or ip in the database configuration host variable and you're all set.

Monday, September 19, 2011

Multiple Applications, One Cake (or more)

With running multiple CakePHP applications, keeping them all up to date with the latest security fixes can be an issue. Instead of having to update each one individually, having a shared cake installation can be beneficial.

While there are many different methods of doing this, I couldn't get any of the ones I tried to work. So I deconstructed the process and made it VERY simple and functional. Obviously since we are using symlinks this is applicable only on Linux.

Step 1: Extract CakePHP to where you want your application to reside. e.g. /var/www/html/myApp/
Step 2: Extract CakePHP to where you want your Cake to reside. For ease in updating, I named mine the same as the version number. e.g. /var/www/cakephp-1.3.11
Step 3: While unnecessary, I found it convenient to create a symlink to the cake directory named 'cake', otherwise you have to either name your cake folder the same every update, or update all your application symlinks
$ ln -s /var/www/cakephp-1.3.11 /var/www/cake
Step 4: Delete the cake directory in your application.
$ rm -fr /var/www/html/myApp/cake
Step 5: Create a symlink in the application folder to the shared cake folder.
$ ln -s /var/www/cake/cake /var/www/html/myApp/cake
Now any application that has that symlink in it instead of the cake folder will use the shared folder. Update the shared folder once, all applications that are sharing it get updated.
Step 1: Extract new version of cake to same directory as old version of cake. e.g. /var/www/cakephp-1.3.12
Step 2: Update your shared simple symlink to the new directory
$ ln -f -s /var/www/cakephp-1.3.12 /var/www/cake
That easy.

If you want to have an application use an older version of Cake, just install it in the application itself as normal, OR install it next to the other version and change the symlink in the app folder to match the different version of cake.
Step 1: Extract different version of cake for sharing. e.g. /var/www/cakephp-2.0.0-RC2
Step 2: Update your application's symlink to the new directory
$ ln -f -s /var/www/cakephp-2.0.0-RC2/cake /var/www/html/my2.0App/cake

Tuesday, September 13, 2011

Database Configuration for Oracle

If you are connecting to an Oracle database, first ensure that your php_oci8 extension is installed. A simple php page can show you this:
<?php
  phpinfo();
?>
View the page and you should see a heading "OCI8" and it should tell you the version and if it is enabled. Next ensure you can talk to the database. Another simple php file can do this for you. This page will list all tables in the database (code from Oracle's website)
<?php
  $conn = oci_connect('username', 'password', 'host/database');
  $stid = oci_parse($conn, 'select table_name from user_tables');
  oci_execute($stid);
  echo "\n";
  while (($row = oci_fetch_array($stid, OCI_ASSOC+OCI_RETURN_NULLS)) != false) {
    echo "\n";
    foreach ($row as $item) {
      echo '  \n";
    }
    echo "\n";
  }
  echo '
'.($item !== null ? htmlentities($item, ENT_QUOTES) : ' ')."
'; ?>
Now that you know you can access your Oracle database via php, configure your CakePHP connection. Edit /app/config/database.php
var $default = array(
  'driver' => 'oracle',
  'connect' => 'oci_connect',
  'persistent' => false,
  'host' => 'host',
  'port' => 1521,
  'login' => 'username',
  'password' => 'password',
  'database' => 'host:1521/database',
  'prefix' => '',
);
And that's all there is to it.

Monday, August 8, 2011

Reset Forgotten Password

For a secure application one issue that will arise repeatedly is users forgetting their passwords. One method of correcting this is to allow users to reset their passwords by submitting their email address. Since the new password will be only be sent to a valid email address, only the appropriate user will know the new password.

For applications open to the public this method is not advised since anyone could possibly find a valid user email address and spam them with reset passwords.

First create the form /app/views/users/forgot.ctp
<h2>Forgot Password</h2>
<p>Submit your email address and a new password will be emailed to you.</p>
<?php
    echo $form->create('User');
    echo $form->input('email');
    echo $form->end('Send Password');
?>
<script type="text/javascript">
document.getElementById('UserEmail').focus()
</script>
Next edit your Users controller at /app/controllers/users_controller.php and add the forgot function.
    function forgot(){
        if(!empty($this->data)){
            $user = $this->User->findByEmail($this->data['User']['email']);
            if($user){
                $user['User']['password'] = $this->__generatePassword();
                // Since my users model requires a confirm_password to match the password on update, it is required here
                // See http://drug-ed.blogspot.com/2011/08/authentication-and-passwords.html
                $user['User']['confirm_password'] = $user['User']['password'];
                if ($this->__sendEmail($user['User']['email'], 'New Password', $user['User'], 'newpass')) {
                    if ($this->User->save($user['User'], array('fieldList' => array('password')))) {
                        $this->Session->setFlash(__('An email has been sent with your new password.', true));
                        $this->redirect(array('action' => 'login'));
                    } else {
                        $this->Session->setFlash(__('The password cound not be saved. Please try again.', true));
                    }
                } else {
                    $this->Session->setFlash(__('The email could not be sent. Please try again.', true));
                }
            } else {
                $this->Session->setFlash('User could not be found.');
            }
        }
    }
My __sendEmail() function above is described in detail here. Here is my __generatePassword function. Feel free to use it or create your own.
    function __generatePassword($length = 8){
        $characters = 'abcdefghijklmnpqrstuvwxyz';
        $numbers = '123456789'; // No 0 or O so as not to confuse
        $more = '!@#$%^&_+=-';
        $password = '';
        $alt = time();
        for($i = 0; $i <= $length; $i++){
            $alt += rand() % 10;
            if($alt % 3 == 1){
                if($alt % 2 == 1){
                    $password .= strtoupper($characters[(rand() % strlen($characters))]);
                } else {
                    $password .= $characters[(rand() % strlen($characters))];
                }
            } elseif($alt % 3 == 2){
                $password .= $numbers[(rand() % strlen($numbers))];       
            } else {
                $password .= $more[(rand() % strlen($more))];       
            }
        }
        return $password;
    }
Lastly, create your email templates. They can be quite simple. /app/views/elements/email/text/newpass.ctp
A new password has been requested for the account: <?php echo $data['username'].'.'.PHP_EOL.PHP_EOL; ?>
Your new password is: <?php echo $data['password'].PHP_EOL.PHP_EOL; ?>
Please use this password to log in, then you can change your password by clicking "Change Password."
Since the email is sent before saving, the password isn't hashed yet and will be visible to the user.

Lastly, ensure that you allow access to the /users/forgot view by editing /app/app_controller.php and add 'forgot' to your $this->Auth->allow() line.

Friday, August 5, 2011

Sending Email

Sending email in CakePHP is quite simple, however, it can be simpler, especially if you want to follow the DRY methodology. In the controller you want to send email from simply add the following function:
    function __sendEmail($to, $subject, $data, $template, $cc = false){
        $this->Email->reset();
        $this->Email->from = 'Your Name Here<your@email.here>';
        $this->Email->replyTo = 'your@email.here';
        $this->Email->additionalParams = '-fyour@email.here';
        $this->Email->sendAs = 'both';
        $this->Email->template = $template;
        $this->Email->subject = $subject;
        $this->Email->to = $to;
        if($cc) $this->Email->cc = array($cc);
        $this->set('data', $data);
        if ($this->Email->send()) {
            return true;
        } else {
            return false;
        }    
    }
Now you can call it with a single line:
$this->__sendEmail('to@email.here', 'Subject', $data, 'templateName', 'cc@email.here'));

Wednesday, August 3, 2011

More Validation Tips and Tricks

A good shortcut when creating Validation rules is to use the desired message as the key name. CakePHP will automatically use the key name as the error message in the absence of an explicit message.
    var $validate = array(
        'username' => array(
            'Username must contain only letters and numbers' => array(
                'rule' => '/^[a-z0-9]{3,}$/i',
                'required' => true
            ),
            'This username is already taken' => array(
                'rule' => 'isUnique'
            ),
            'Username must be at least 5 characters' => array(
                'rule' => array('minlength', 5),
            ) 
        )
    );

Tuesday, August 2, 2011

Authentication and Passwords

One issue that will pop up is trying to add or edit users with Authentication enabled. Validation won't work properly on the password field because Authentication will hash the password BEFORE it attempts to validate. For example checking for a minimum length will always succeed regardless of the actual password because SHA1 hashed passwords will always be 40 characters.

One method around this is performing the hashing manually. To do this you have to tell your users controller you want to perform your own hashing. Edit /app/controllers/users_controller.php and add the following function:
    function beforeFilter(){
        parent::beforeFilter();
        if($this->action == 'add' || $this->action == 'edit' || $this->action == 'password'){
            $this->Auth->authenticate = $this->User;
        }
    }
Now when you are using the add or edit actions authentication is done manually. You'll see why the action password is in there later.
Next edit the users model /app/model/users.php and add the following functions:
    function hashPasswords($data, $enforce=false) {
        if($enforce && isset($this->data[$this->alias]['password'])) {
            if(!empty($this->data[$this->alias]['password'])) {
                $this->data[$this->alias]['password'] = Security::hash($this->data[$this->alias]['password'], null, true);
            }
        }
        return $data;
    }

    function beforeSave() {
        $this->hashPasswords(null, true);
        return true;
    }
Now your users model will hash the passwords before save, allowing validation to take place first.

Another problem, however, is when editing a user, the hashed password is used in the password field of the form and becomes hashed again on save, actually changing the password! I'm surprised this issue isn't addressed in the core of CakePHP.

One way around this is to modify your edit view to clear the password field of the hashed password. Edit /app/views/users/edit.ctp and change this
    echo $this->Form->input('password');
to this
    echo $this->Form->input('password', array('value' => ''));
This will require you to enter a password every time you edit the user because your validation is set to require 5 characters in the password field.

So to get around that simply remove the password field from your edit form. You can also remove the edit action from your beforeFilter function in the users controller. Now you can edit the user without worrying about the password.

Then, to edit the password create a password function in your controller and a separate view. Edit /app/controllers/users_controller.php and add this function:
    function password($id = null){
        /* Only edit own account unless admin */
        if(!$this->Auth->user('admin')){
            $id = $this->Auth->user('id');
        }    
        if (!$id && empty($this->data)) {
            $this->Session->setFlash(__('Invalid user', true));
            $this->redirect(array('action' => 'index'));
        }
        if (!empty($this->data)) {
            if ($this->User->save($this->data, array('fieldList' => array('password')))) {
                $this->Session->setFlash(__('The password has been saved', true));
                $this->redirect(array('action' => 'index'));
            } else {
                $this->Session->setFlash(__('The password could not be saved. Please, try again.', true));
            }
        }
        if (empty($this->data)) {
            $this->data = $this->User->read(null, $id);
            /* Don't display current hashed password */
            $this->data['User']['password'] = '';
        }
Now create /app/views/users/password.ctp
<div class="users form"> 
<?php echo $this->Form->create('User'); ?>
    <fieldset><legend>Change Password</legend>
    <?php
        echo $this->Form->hidden('id');
        echo $this->Form->input('password', array('label' => 'New Password'));
        echo $this->Form->input('confirm_password', array('type' => 'password'));
    ?>
    </fieldset>
    <?php echo $this->Form->end('Save Password'); ?>
</div>
Then modify your users model /app/models/user.php and add the following validation rules and functions:
        'password' => array(
            'Your password must be at least 5 characters' => array(
                'rule' => array('minlength', 5)
            ),
            'You must enter the same password twice' => array(
                'rule' => array('matchPasswords', 'confirm_password'),
                'on' => 'update'
            )
        ),
        'confirm_password' => array(
            'rule' => 'notEmpty'
        )

    function matchPasswords($data, $confirm_password){
        if ($data['password'] != $this->data[$this->alias][$confirm_password]) {
            $this->invalidate($confirm_password, 'You must enter the same password twice');
            return false;
        }
        return true;
    }
Set appropriate authentication as desired, upload the files and you should be all set.

Monday, July 25, 2011

Log Database Errors

First read here: Catch database errors before it’s too late

It's as simple as adding an onError function to your app_model.php file, and whenever there is a database error, the error in the database will be logged in your CakePHP log.

Edit /app/app_model.php and add the following function:
    public function onError() {
        $db = ConnectionManager::getDataSource('default');
        $err = $db->lastError();
        $this->log($err);
        $this->log($this->data);
    }

Simple Form Security

First read this post: Make your CakePHP forms a lot more secure

The nuts and bolts of it is that by simply adding the Security component, it will automatically add a hash to all your forms and if someone tries modifying your form, it won't work.

And you can implment it as easy as this: Edit /app/app_controller.php and change the var $components line to include Security like this:
    var $components = array('Auth', 'Security', 'Session');

Stylesheets and Print Stylesheet

Adding a stylesheet to your site is as simple as changing the default layout.

Open /app/views/layouts/default.ctp and within the first 10 lines of the page is the line
echo $this->Html->css('cake.generic');
Cake comes with a default style in /app/webroot/css/cake.generic.css which you can edit until your heart's content. Or you can create your own stylesheet and put it into the same folder and change that line to direct your pages to the new style. Observe how the .css extension is not included in the cake function call.

For a Print Stylesheet, it is a little different. To set a print stylesheet you need to set the options to include the media type.
echo $this->Html->css('cake.print', 'stylesheet', array('media' => 'print'));

Thursday, July 14, 2011

Validation Tips and Tricks

When using validation, as mentioned previously, there are some tricks to automatically adding the class required to your form fields. Any validation rule that has minlength works. So a short, simple rule to just ensure the required class is added to the field label could look something like this:
    var $validate = array(
        'field_name' => array(
            'rule' => array('minlength', 1)
        )
    );
If you want to require user input but don't care about the required class for your labels, you can actually use something as simple as this:
    var $validate = array(
        'field_name' => 'notEmpty'
    );
If you expand that to the following code, however, you will also get the required class for your field label.
    var $validate = array(
        'field_name' => array(
            'rule' => 'notEmpty'
        )
    );
This is very useful when you want the required class for select fields.

Wednesday, July 13, 2011

Model: User: Display Name

When adding a record that has a relationship of "belongs to" with another table as mentioned previously, CakePHP automatically lists the records of the other table using the display name of that model. By default that display name field is "name". However, if your field doesn't have a "name" field, such as the Users table, you can specify the display name manually in the model.

Edit app/models/user.php and add the following line:
    var $displayField = 'username';
This does not support multiple fields, for example if you wanted to combine first_name and last_name. In order to do this you need to use virtualFields in your model. Virtual fields allow you to access a created field, but you cannot save it.

Edit app/models/user.php and add the following line:
    var $virtualFields = array( 'full_name' => "CONCAT(User.first_name, ' ', User.last_name)");
then change the display field from username to full_name.