Access Control in PHP and my new OSS Project


Recently at work I have been working on a project that has made me have to think about project flexibility in a whole new way. The project involves a service we are creating which companies will be able to purchase and label as their own. We plan on accomplishing this by making use of the Smarty Templating Engine and some server-side magic so that we only have one code base for infinite iterations of the service.

For the most part all of the underlying logic is identical, but Access Control has to be configurable on an individual company basis. Our company has three access levels which map basically to “Basic”, “Pro”, and “Premium”, but Company A may only have “Basic” and “Pro” while Company C may have “Basic”, “Pro”, “Premium”, and “Super-Epic-AwesomeTown”. To complicate matters further, the access levels are not strictly hierarchical. A user who registers on a site may have purchased the “Basic” package and the “Premium” package while opting not to purchase “Pro” or “Super-Epic-AwesomeTown”.

After researching PHP Access Control libraries for a while, I discovered nothing that seemed to meet my needs in an easy to use and flexible way. So, like any good nerd I decided to write and Open-Source my own.

Introducing PHP-Bouncer

PHP-Bouncer uses the idea of roles to manage permissions, so in the above examples “Basic”, “Pro”, “Premium”, and “Super-Epic-AwesomeTown” would map to roles each of which would give access to specific sets of pages. Each role also has the option to override a page from any other roles, so if “Pro” gives you access to a page called “Buy Premium”, Premium may override that page and send you to “Premium Home”. Roles are defined Like this:

$bouncer = new Bouncer();
// Add a role     Name,      Array of pages role provides
$bouncer->addRole("Public", array("index.php", "about.php"));
// Add a role          Name,              Array of pages role provides
$bouncer->addRole("Registered User", array("myaccount.php", "editaccount.php", "viewusers.php"));
// Add a role          Name,   Array of pages role provides       List of pages that are overridden by other pages
$bouncer->addRole("Admin", array("stats.php", "manageusers.php"), array("viewusers.php" => "manageusers.php"));

With our roles defined, we can now create some users and give them roles (NOTE: The User class in this example extends the BouncerUser class, which provides basic role management functionality and is totally optional):

// Here we add some users. The user class here extends the BouncerUser class, so it can still do whatever you
// would normally create a user class to do..
$user1 = new User();
$user2 = new User();
$user3 = new User();

$user1->addRole("Public");
$user2->addRole("Registered User");
$user3->addRole("Admin");

And now, we take our users and our roles and check to see if they would have access to certain pages:

$bouncer->verifyAccess($user1->getRoles(), "index.php"); // True!
$bouncer->verifyAccess($user1->getRoles(), "viewusers.php"); // False! User 1 does not have access to this page.

$bouncer->verifyAccess($user2->getRoles(), "index.php"); // True!
$bouncer->verifyAccess($user2->getRoles(), "viewusers.php"); // True!

$bouncer->verifyAccess($user3->getRoles(), "index.php"); // True!
$bouncer->verifyAccess($user3->getRoles(), "viewusers.php"); // False! As an Admin, viewusers.php has been replaced
// with manageusers.php

This provides a good demo of how PHP-Bouncer works, but a better real-world example might be to include the following code in an include file on every page of your site:

$bouncer = new Bouncer();
$bouncer->addRole("Public", array("/index.php", "/test/test.php"));
$bouncer->addRole("Private", array("/smartyTest.php"));
$bouncer->addRole("Admin", array("/admin.php"), array("/smartyTest.php"=>"/admin.php"));
$bouncer->addRole("Observer", array("/observer.php"));
$publicOnly = array("Public");
$privateOnly = array("Private");
$observerOnly = array("Observer");
$adminOnly = array("Admin");
$allAccess = array("Public", "Private", "Admin", "Observer");
if(!$bouncer->verifyAccess($adminOnly, $_SERVER["PHP_SELF"])){
    echo "Permission denied for page ".$_SERVER["PHP_SELF"];
    exit;
}

PHP-Bouncer is (at the time of this writing) only about 3 days old, and far from perfect/complete, so feel free to check out the Google Code page for additional documentation and to download bouncer!

Advertisements

Making HTTP POST Requests in PHP


Recently at my job I have had several situations in which I needed to pass fairly large amounts of data to a particular page on a website for processing, and in which submitting a form isn’t a feasible option. Typically in this situation one would attempt to append the data to the url using GET parameters, but sometimes your data is too large or too sensitive to pass through the URL. Unfortunately, PHP does not provide an easy way to make post requests natively. Luckily for us, there is cURL. cURL allows us to communicate across multiple protocols to a variety of different server types. With cURL, we are able to make HTTP POST requests:

function post_request($url, $data) {
	$output = array();
	foreach ($data as $key => $value) {
		if(is_object($value) || is_array($value)){
			$data[$key] = serialize($value);
		}
	}
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_HEADER, false);
	curl_setopt($ch, CURLOPT_POST, true);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
	$result = curl_exec($ch);
	if ($result) {
		$output['status'] = "ok";
		$output['content'] = $result;
	} else {
		$output['status'] = "failure";
		$output['error'] = curl_error($ch);
	}
	curl_close($ch);
	return $output;

}

The above function accepts two parameters, the URL to make the request to, and an associative array containing the keys and values you would like to submit with your POST request. The function returns an associative array containing two values: status, which will either be equal to “ok” or “failure” depending on the success or failure of the POST request; and either ‘content’ or ‘error’ depending on success or failure. In the case of a successful POST request, ‘content’ will contain the full output from the page you have posted to (In other words, you may need to be prepared for an entire html document). Otherwise, ‘error’ will contain the details of the error as returned by cURL.

Taking Control of Your PHP Sessions: Using Database Sessions


Introduction

Recently while working on a project for work, I encountered an interesting issue. I needed to develop a way to add and remove permissions from a User in a system we are building (which will be made open source as a service to the internet community once development is complete), but I needed the ability to push out these permission changes even if a user is currently logged in to the system.

As far as I know PHP does not provide a way to edit User sessions other than the $_SESSION array, which only applies to the current User’s session. After some research, I discovered that it is possible to change PHP’s default session storage method. This can be very useful if you are using a CDN to share server load between multiple servers while still allowing data persistence through the session. Another useful benefit of storing your sessions in the Database is the ability to alter User sessions (for any user) on the fly.

There are a few things to note about editing User Sessions on the fly:

  • If you aren’t careful you can inadvertently edit the wrong session, leading to security issues
  • The way session data is stored in the database is through a single serialized data field. This means that before editing a session variable you must either parse and change only the value you wish to change, or unserialize the entire session data string, edit the individual values, and then re-serialize the data for insert.
  • I have never tried the above method, and therefore cannot provide any code to accomplish this task. If this changes, I will edit this post with the relevant code
  • If you are only storing basic login type data in the session (which would be pulled from the Database), killing a User’s session and allowing them to log back in is the safest way to ensure that any changes to the session will populate correctly

Setting up the Database

The SQL in this tutorial is written to work with MySQL, so if you’re using something else just bear in mind that your syntax may need to be tweaked, especially in the DDL.

Create the Table:

CREATE TABLE SESSION (
id VARCHAR(32) NOT NULL COMMENT 'Stores the Session ID',
access INT(10) UNSIGNED NOT NULL,
data TEXT,
PRIMARY KEY (`id`)
) ENGINE=INNODB
ROW_FORMAT=DEFAULT;

Create a few PHP classes

This first class I built handles the Database connection, and is in two parts: Config.inc.php (for configuring the database variables) and MySQLDatabase.class.php:

Config.inc.php

<?php
define("conf_hostname", "localhost");
define("conf_username", "user");
define("conf_password", "password");
define("conf_schema", "database_to_use");
?>

MySQLDatabase.class.php

<?php
include("Config.inc.php");
class MySQLDatabase{
	private $db;
	private $hostname;
	private $username;
	private $password;
	private $schema;
	
	function __construct() {
		if(func_num_args() == 0){
			$this->hostname = conf_hostname;
			$this->username = conf_username;
			$this->password = conf_password;
			$this->schema = conf_schema;
		}
		else{
			$params = func_get_args();
			$this->hostname = $params[0];
			$this->username = $params[1];
			$this->password = $params[2];
			$this->schema = $params[3];
		}
	}
	
	private function open(){
		$this->db = mysql_connect($this->hostname, $this->username, $this->password) or die ('Error connecting to mysql');
		mysql_select_db($this->schema, $this->db);
	}
	
	public function executeQuery($query){
		$this->open();
		$results = mysql_query($query, $this->db) or die ("Error in query: $query. ".mysql_error());
		return $results;
	}
	
	public function close(){
		mysql_close($this->db);
	}
	
}
?>

Next we need to create a class to handle some session related events:

DatabaseSessionHandler.class.php

<?php
include_once ("MySQLDatabase.class.php");
/**
 *
 */
class DatabaseSessionHandler {
	private $db;

	public function _open($save_path, $session_name) {
		$this -> db = new MySQLDatabase();
		return true;
	}

	public function _close() {
		$this -> db -> close();
	}

	function _read($id) {

		$id = mysql_real_escape_string($id);

		$query = "SELECT data
				FROM SESSION
				WHERE id = '$id'";

		if ($result = $this -> db -> executeQuery($query)) {
			if (mysql_num_rows($result)) {
				$record = mysql_fetch_assoc($result);
				return $record['data'];
			}
		}

		return '';
	}

	function _write($id, $data) {
		$access = time();

		$id = mysql_real_escape_string($id);
		$access = mysql_real_escape_string($access);
		$data = mysql_real_escape_string($data);

		$query = "REPLACE
				INTO SESSION
				VALUES ('$id', '$access', '$data')";

		return $this -> db -> executeQuery($query);
	}

	function _destroy($id) {

		$id = mysql_real_escape_string($id);

		$query = "DELETE
				FROM SESSION
				WHERE id = '$id'";

		return $this -> db -> executeQuery($query);
	}

	function _clean($max) {
		$old = time() - $max;
		$old = mysql_real_escape_string($old);

		$query = "DELETE
				FROM SESSION
				WHERE access < '$old'";

		return $this -> db -> executeQuery($query);
	}
	
	public function killUserSession($username){
		$query = "delete from SESSION where data like('%userID|s:%\"". mysql_real_escape_string($username) ."%\";first_name|s:%')";
		$this->db->executeQuery($query);
	}

}
?>

Replacing PHP’s Session with our Database Session

Now all we have left to do is actually utilize our new session handling. This is actually a relatively painless process. In my application the session is started in the header include file for each page. In this case, all I had to do was change:

<?php
     session_start();
?>

to:

<?php
$sess = new DatabaseSessionHandler();
session_set_save_handler(array(&$sess, '_open'),
                         array(&$sess, '_close'),
                         array(&$sess, '_read'),
                         array(&$sess, '_write'),
                         array(&$sess, '_destroy'),
                         array(&$sess, '_clean'));
session_start();
?>

From here on out, the session can be handled in code exactly like a PHP session, but now you also have the ability to edit User sessions on the fly!

Reading RSS feeds with Magpie RSS


Last semester I was taking a client-side programming class and ended up attempting to design a Twitter-esque web app using PHP and AJAX. The goal of the website was to create a twitter like interface for a news aggregation website. While my team and I were all competent programmers, there was one problem that we were all having a hard time solving at first: How to add realistic looking articles to our database while on a serious time crunch. After a little bit of debate and quite a bit of head-against-wall banging, we came upon the idea of pulling RSS feeds from major news organizations and parsing them out into byte-sized pieces (typically just the headline). We decided to use 3 major news organizations for simplicity’s sake since each RSS feed was just a little bit different, and we ended up going with NPR, CNN, and MSNBC World News.
(more…)