Building and running a shell

In this recipe, we will learn how to build and run a custom shell, which will ask for a username and a password, and add the given account to a list of user accounts. Based on the system created in the recipe Setting up a basic authentication system from Chapter 1, Authentication, this shell is a great help when looking to create test accounts.

Getting ready

To go through this recipe we need an authentication system. Follow the entire recipe Setting up a basic authentication system from Authentication chapter.

How to do it...

Create a file named user.php and place it in your app/vendors/shells folder, with the following contents:

<?php
App::import('Core', 'Security'),
class UserShell extends Shell {
public $uses = array('User'),
public function main() {
$user = $this->in('Enter the username (ENTER to abort):'),
if (empty($user)) {
$this->_stop();
}
$defaultPassword = $this->_randomPassword();;
$password = $this->in('Enter the password (ENTER to use generated):', null, $defaultPassword);
$this->out();
$this->out('USER: '.$user);
$this->out('PASSWORD: '.$password);
$this->out();
if (strtoupper($this->in('Proceed?', array('Y', 'N'), 'N')) != 'Y') {
$this->_stop();
}
$user = array('User' => array(
'username' => $user,
'password' => Security::hash($password, null, true)
));
$this->User->create();
if ($this->User->save($user)) {
$this->out('User created.'),
} else {
$this->error('Error while creating user.'),
}
}
protected function _randomPassword($size=10) {
$chars = '@!#$_';
foreach(array('A'=>'Z', 'a'=>'z', '0'=>'9') as $start => $end) {
for ($i=ord($start), $limiti=ord($end); $i <= $limiti; $i++) {
$chars .= chr($i);
}
}
$totalChars = strlen($chars);
$password = '';
for($i=0; $i < $size; $i++) {
$password .= $chars[rand(0, $totalChars-1)];
}
return $password;
}
}
?>

We are now ready to run our shell. Open a terminal window, and access the directory where your application resides. Inside this directory you should have your app/ and cake/ folders. For example, if your application is installed in /var/www/myapp, then /var/www/myapp/app should be your app/ folder, and /var/www/myapp/cake your cake/ folder. While standing in your application's main directory (/var/www/myapp in this example), run:

Note

To learn more about setting the right path when running shells, or how to add the cake shell script to your PATH environment variable see http://book.cakephp.org/view/1106/The-CakePHP-Console

If you are on a GNU Linux / Mac / Unix system:

../cake/console/cake user

If you are on Microsoft Windows:

..cakeconsolecake.bat user

Note

If you receive an error message such as Error: Class UserShell could not be loaded, this means that CakePHP is unable to find your app/ folder, which is probably because you have a different name for the app/ folder. In this case, you can specify the folder with the app argument, like so: $ cake/console/cake -app /var/www/myapp/app user.

Once the shell is run, it will ask us for the desired username and password, and will wait for a final confirmation before creating the account, as shown in the following screenshot:

How to do it...

We are now able to use this account when logging in through our application's login page.

How it works...

We started by importing the Security class, which is used for hashing the password prior to saving the user record. We then created a class named UserShell, extending it from CakePHP's Shell class, which offers us a set of methods and properties that are helpful when building shells. One of such properties is uses, which works the same way as a controller's uses property—by defining a list of application models that should be instantiated and ready to use from any method in the shell.

Our shell's entry point is the main() method. This comes as no surprise if you have any experience developing C, C++, or Java applications, as main() is also their entry function. If you have no such experience, then all there is to know is that main() will be automatically executed by CakePHP when our shell is invoked through the command line.

Our main() method starts by asking the user for their desired username. To ask for user input, we use the in() method (available through the Shell parent class), which takes up to three arguments:

  • prompt: The message that is shown to the user before asking for their input.
  • options: An optional set of values that the user should be restricted to when entering their input.
  • default: An optional default value that is to be used if the user enters no input by clicking Enter at the prompt.

If the user does not specify a user name, we exit the application by calling the _stop() method, available to all CakePHP classes that descend from Object, Shell being one of them.

Once we have our username, we need to ask for a password. As a useful alternative, we want to offer the user an automatically generated password. To generate this password, we implement a method called, not surprisingly, _randomPassword().

This method takes one argument, the size of the generated password, and builds it by randomly selecting an element from a defined set of characters. This set is constructed by including all characters between the letters A and Z, a and Z, and 0 and 9. For more secure passwords, we also included the symbols @ ! # $ and _ as valid characters.

When we use the in() method to ask the user for a password, we use this default generated password as its third argument (default.) After asking for the password, we show the user the choice for username and password, and ask for confirmation, utilizing the options argument in our call to in().

If the user confirms the operation, we proceed to create the user record, hashing the entered password with the Security::hash() method, which takes up to three arguments:

  • string: The string to be hashed.
  • method: The method to use for hashing, which can be any of: sha1, sha256, md5, or any other method supported by the hash() PHP function. Defaults to the following PHP functions, depending on their availability: sha1() (also used if sha1 is the chosen method), mhash() (also used if sha256 is the chosen method), hash(), and finally md5().
  • salt: If true, prefixes the string with the application's salt (available in the Configure setting Security.salt). If a string is specified, it is prefixed to the password being hashed in place of the application's Security.salt setting. If false, hashes the given string without a prefix.

If a record is created, we inform the user that the operation succeeded. Otherwise we use the error() method (available through the Shell parent class) which sends an error message through the standard error stream and exits the application.

Using the Auth component for hashing passwords

In this recipe, we called the Security::hash() method to hash passwords, by specifying the same exact arguments that are utilized in the Auth component. If we did not do so, we would have different hash values for the same passwords, which would render our shell useless, as any user account created with it wouldn't be able to log in.

The problem with this approach is that if the method that is used by the Auth component to hash passwords is changed, we would need to reflect such changes in our shell. Therefore, we may want to use the Auth component to do the hashing instead. This solution requires a bit of extra effort, as components are not natively available in a shell. Edit your app/vendors/shells/user.php file and remove the import of the Security class, and then add the following import statement at the beginning of the file:

App::import('Component', 'Auth'),

We now need to instantiate the AuthComponent class. Add the following code to the beginning of the main() method:

$this->Auth = new AuthComponent();

Finally change the definition of the data that is used for creating the User record, so its password field is hashed using the Auth component:

$user = array('User' => array(
'username' => $user,
'password' => $this->Auth->password($password)
));

See also

  • Parsing command line parameters
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.140.195.225