Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

developerWorks Community:

  • Close [x]

Learning PHP, Part 3: Authentication, objects, exceptions, and streaming

Nicholas Chase is the founder and creator of NoTooMi. In addition to technical writing for large corporations, he has been involved in website development for companies such as Lucent Technologies, Sun Microsystems, Oracle, and the Tampa Bay Buccaneers. He has been a high school physics teacher, a low-level-radioactive waste facility manager, an online science fiction magazine editor, a multimedia engineer, an Oracle instructor, and the chief technology officer of an interactive communications company. He is the author of several books, including XML Primer Plus (Sams 2002).

Summary:  This tutorial is Part 3 of a three-part "Learning PHP" series teaching you how to use PHP through building a simple workflow application. In this tutorial, you will learn about using HTTP authentication, streaming files, and how to create objects and exceptions.

03 Jan 2013 - Nicholas Chase updated content throughout this tutorial to reflect current PHP technology.

View more content in this series

Date:  03 Jan 2013 (Published 12 Jul 2005)
Level:  Intermediate PDF:  A4 and Letter (792 KB | 36 pages)Get Adobe® Reader®

Comments:  

Putting it together

Now that you have the file download process in place, it's time to put everything together and finish off the application. In this section, you will take care of some uncompleted miscellaneous tasks:

  • Detecting administrators
  • Creating the form that enables an administrator to approve files
  • Checking downloads to make sure they're not being called from another server

First you need to set up the administrators who will approve files.

Detecting administrators

When you originally created the users table in the database, you didn't take into consideration the fact that you need to distinguish between regular users and administrators, so you have to take care of that now. Log into MySQL and execute the following commands:

alter table users add status varchar(10) default 'USER';
update users set status = 'USER';
update users set status = 'ADMIN' where id=3;

The first command adds the new column, status, to the users table. You didn't specify the user type on the registration page, so you simply specify a default value of USER for any new users added to the system. The second command sets this status for the existing users. Finally, you choose a user to make into an administrator. (Make sure to use the appropriate id value for your data.)

Now that you have the data, you can create a function that returns the status of the current user. Add this function to scripts.txt as shown in Listing 28.


Listing 28. Detecting user status
function getUserStatus()
{
    $dbh = new PDO('mysql:host=localhost;dbname=workflow', 'wfuser', 'wfpass');
    $stmt = $dbh->prepare("select * from users where username= :username");

    $stmt->bindParam("username", $username);

    $username = $_SESSION["username"];

    $stmt->execute();

    $status = "NONE";
    if ($row = $stmt->fetch()) {
        $status = $row["status"];
    }

    $dbh = null;

    return $status;
}

To review how this process works, you create a connection to the appropriate database. Then prepare a SQL statement using a parameter for the username. Set that parameter to the actual username, stored in the $_SESSION variable. Next, execute that statement and attempt to get the first (and presumably only) row of data.

You start by defining the $status as NONE. If no row exists, this variable will simply stay as it is. On the other hand, if a row exists, you set the status equal to the value of the status column. Finally, close the connection and return the value.


Approving the file: The form

Now you're ready to add approval capabilities to the form. What you want is to display a check box for pending files if the user viewing the list of files is an administrator. The display_files() function in scripts.txt handles that (see Listing 29).


Listing 29. Adding admin functions
function display_files()
{

    $userStatus = getUserStatus($_SESSION["username"]);

    if ($userStatus == "ADMIN") {
        echo "<form action='/approve_action.php' method='POST'>";
    }

    $workflow = json_decode(file_get_contents(UPLOADEDFILES . "docinfo.json"), true);

    echo "<table width='100%'>";

    $files = $workflow["fileInfo"];

    echo "<tr><th>File Name</th>";
    echo "<th>Submitted By</th><th>Size</th>";
    echo "<th>Status</th>";
    if ($userStatus == "ADMIN") {
        echo "<th>Approve</th>";
    }
    echo "</tr>";

    for ($i = 0; $i < count($workflow["fileInfo"]); $i++) {
        $thisFile = $workflow["fileInfo"][$i];
        if (
            >($userStatus == "ADMIN") ||>
            ($thisFile["approvedBy"] != null) ||
            (
                    isset($_SESSION["username"]) &&
                    ($thisFile["submittedBy"] == $_SESSION["username"])
            )
        ) {

            echo "<tr>";
            echo "<td><a href='/loggedin/download_file.php?file=" .
                    $thisFile["fileName"] . "&filetype=" . $thisFile["fileType"] .
                    "'>" . $thisFile["fileName"] . "</a></td>";
            echo "<td>" . $thisFile["submittedBy"] . "<lt;/td>";
            echo "<td>" . $thisFile["size"] . "</td>";
            echo "<td>" . $thisFile["status"] . "<td>";
            if ($userStatus == "ADMIN") {
                if ($thisFile["status"] == "pending") {
                    echo "<input type='checkbox' name='toapprove[]' ".
                                   "value='" . $i . "' checked='checked' />";
                }
            }
            echo "</tr>";
        }
    }

    echo "</table>";
    if ($userStatus == "ADMIN") {
        echo "<input type='submit' value='Approve Checked Files' />";
        echo "</form>";
    }

}

Starting at the top, you first determine the user's status. This is important for two reasons. For one thing, if a user is an administrator, you need to display the approval form. For another, if a user is an administrator, you'll display all files, no matter who uploaded them or what their status is. To do that, you just add another condition to the if statement.

Within the actual file display, if the user is an administrator, and if the file is still pending, you display a pre-checked checkbox. The value of the checkbox is the number of the file, so you can refer to it later. Giving the user an array-like name (in this case toapprove[] tells the web server to expect multiple values for the same field name.

Figure 12 shows the result, a form with the appropriate fields (filename, submitter, file size, status) and check boxes to select for approval.


Figure 12. The approval form
Screen capture of the approval form

Approving the file: Updating the JSON

The actual form page that accepts the approval check boxes, approve_action.php, is very simple (see Listing 30).


Listing 30. Processing the approval form
<?php

  session_start();

  include "/scripts.txt";

  $allApprovals = $_POST["toapprove"];
  foreach ($allApprovals as $thisFileNumber) {
     approveFile($thisFileNumber);
  }
  echo "Files approved.";

?>

For each toapprove check box, you simply call the approveFile() function, in scripts.txt (see Listing 31).


Listing 31. Approving a form
function approveFile($fileNumber){

    $workflow = json_decode(file_get_contents(UPLOADEDFILES . "docinfo.json"), true);

    $workflow["fileInfo"][$fileNumber]["approvedBy"] = $_SESSION["username"];
    $workflow["fileInfo"][$fileNumber]["status"] = "approved";

    $jsonText = json_encode($workflow);
    file_put_contents(UPLOADEDFILES . "docinfo.json", $jsonText);

}

You start by loading the data using json_decode(), just as you do when displaying the files. In this case, you string together a number of different references to set the approvedBy value. The $workflow variable contains all the data. Its fileInfo property is an array that includes all of your files, so the $fileNumber refers to the one that you want. Once you have that, you can set the approvedBy property. Similarly, set the status to approved.

Finally, save the file.

Note that while you did it this way for simplicity's sake, in a production application, it's more efficient to open and load the file just once, make all the changes, then save the file.

Test this out by approving some of the files and redisplaying the files.


Security checks on download

As the last step, you'll need to add a security check to the download process. Because you control this process entirely through the application, you can use whichever checks you want. For this example, you'll check to make sure that the user clicked the link for a file on a page that is on your local server, preventing someone from linking to it from an external site, or even from bookmarking the link or sending someone else a raw link.

You start by creating a new exception, just for this occasion, in the WFDocument.php file (see Listing 32).


Listing 32. Disabling remote downloading
<?php
   include_once("/scripts.txt");

class NoFileExistsException extends Exception {

   public function informativeMessage(){
      $message = "The file, '".$this->getMessage()."', called on line ".
           $this->getLine()." of ".$this->getFile().", does not exist.";
      return $message;
   }

}

class ImproperRequestException extends Exception {

   public function logDownloadAttempt(){
      //Additional code here
      echo "Notifying administrator ...";
   }

}

class WFDocument {

   private $filename;
   private $filetype;

   function setFilename($newFilename){
      $this->filename = $newFilename;
   }
   function getFilename(){
      return $this->filename;
   }

   function setFiletype($newFiletype){
      $this->filetype = $newFiletype;
   }
   function getFiletype(){
      return $this->filetype;
   }

   function __construct($filename = "", $filetype = ""){
      $this->setFilename($filename);
      $this->setFiletype($filetype);
   }

   function download() {

      $filepath = UPLOADEDFILES.$this->filename;

      try {

         $referer = $_SERVER['HTTP_REFERER'];
         $noprotocol = substr($referer, 7, strlen($referer));
         $host = substr($noprotocol, 0, strpos($noprotocol, "/"));
         if ( $host != 'boxersrevenge' &&
                                $host != 'localhost'){
            throw new ImproperRequestException("Remote access not allowed.
                        Files must be accessed from the intranet.");
         }

         if(file_exists($filepath)){
           if ($stream = fopen($filepath, "rb")){
              $file_contents = stream_get_contents($stream);
              header("Content-type: ".$this->filetype);
              print($file_contents);
           } else {
              throw new Exception ("Cannot open file ".$filepath);
           }
         } else {
           throw new NoFileExistsException ($filepath);
         }
      } catch (ImproperRequestException $e){

         echo "<p style='color: red'>".$e->getMessage()."</p>";
         $e->logDownloadAttempt();

      } catch (Exception $e){

         echo "<p style='color: red'>".$e->getMessage()."</p>";

      }
   }
}

?>

In the ImproperRequestException, you create a new method, logDownloadAttempt(), that can send an email or perform some other action. You use that method in this exception type's catch block.

In the actual download() function, the first thing you do is get the HTTP_REFERER. This optional header is sent with a web request identifying the page from which the request was made. For example, if you link to developerWorks from your blog, and you click that link, the IBM logs show the URL of your blog as the HTTP_REFERER for that access.

In your case, you want to make sure the request is coming from your application, so you first strip off the "http://" string at the beginning, then save all the text up to the first slash (/). This is the hostname in the request.

For an external request, this hostname might be something along the lines of boxersrevenge.nicholaschase.com, but you're looking for only internal requests, so you accept boxersrevenge or localhost. If the request comes from anywhere else, you throw the ImproperRequestException, which is caught by the appropriate block.

Note that this method is not foolproof as far as security is concerned. Some browsers don't send referrer information properly because either they don't support it or the user has altered what's being sent. But this example should give you an idea of the types of things you can do to help control your content.

7 of 11 | Previous | Next

Comments



static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source, Linux, XML
ArticleID=133669
TutorialTitle=Learning PHP, Part 3: Authentication, objects, exceptions, and streaming
publish-date=01032013
author1-email=ibmquestions@nicholaschase.com
author1-email-cc=