[phplib] Not using PHPLIB (was: phplib migration into PEAR]) From: Kristian Koehntopp (kris <email protected>)
Date: 03/17/01

In netuse.lists.phplib you write:
>i agree, i'm also using the native functions.
>also, maxim's session4_custom.inc class is very handy for migrating from
>PHPLIB sessions to native sessions.

>but how do you manage auth, user and perm?

I hack something up. This is very fast. For example, in a 3 day
course in advanced PHP I held for Skoda Deutschland, we spent
the better part of a day discussing the following setup:

Needed is a system for authentication and permission checking in
a content management system. Introducing auto_prepend_file and
auto_append_file, we wrote to functions managing the corporate
identity, called ci_open() and ci_close(). This is pretty
standard stuff, without templates.

ci_open() is the function that is called on each page, and will
draw a table structure with navigation on the left side (we left
that part out, the CMS provides that), and a login box on the
left side below the navigation. Users that are not logged in
have no permissions and get standard content. That standard
content is, on some pages, a "go away" message.

Note that we are using "register_globals = Off" in this code.

Code:

<?php
 function ci_open() {
?>
<table border="0" cellpadding="4" cellspacing="0" bgcolor="#ffffff" width="100%">
<tr bgcolor="#cccccc">
 <td colspan="2"><h1>HEADLINE</h1></td>
</tr>

<tr>
 <td valign="top" align="left">
<?php
  handle_login();
  if (!is_login())
    loginform();
  else
    loggedinform();
?></td>
 <td width="100%" valign="top" align="left">
<?php
  }
  
  function ci_close() {
    global $HTTP_SESSION_VARS;
?>
</td>
</tr>
</table>
<hr>
<div align="right">
Logged in as: <b><?php
  if (is_login())
    echo $HTTP_SESSION_VARS["login"]["user"];
  else
    echo "anonym";
  echo "</b> mit <b>";
  echo join(" ", perm_list());
  echo "</b></div>\n";
  }
 ?>

This code draws a simple table with a static headline, a sidebar
with either a loginform() or a loggedinform(), both of which
will be discussed below. Think of them as something like the
stuff seen at http://www.koehntopp.de/php, left hand side, for
the moment. Most of the page is a main content area in the form
of the interior of a single large <td/>.

Each page calls the handle_login() function, which will trigger
if a user uses the loginform() or the loggedinform(). It will
log the user in or out accordingly.

To log the user in or out we need to be able to remember the
current state. Start a PHP 4 native session, and register an
empty array $HTTP_SESSION_VARS["login"] with it:

<?php
  # This should actually go into a local.inc file.
  define('AUTOLOGOUT_TIME', 15*60);
  
  session_start();
  session_register("login");

  # This should actually be part of auto_prepend_file
  ci_open();
?>
Content
<?php
  # You would put this into your auto_append_file
  ci_close();
 ?>

Now you can have handle_login() and everything that goes with
it:

<?php

# A predicate that will tell us, if a user is logged in.

# The $login array in the session will have the following
# structure:
#
# $login["user"] = name of the user logged in
# $login["exp"] = time_t when the current login expires,
# needs to be refreshed on each time.
#
# $login["realname"] = more user information, here a full name
# $login["perm"][<name>] = "yes"
# A hash of all permissions this particular
# user has. A permission is just a name, such
# as "admin", "edit_sports_page", "edit_local_page"
# and so on. It is the task of the permission system
# to make something of it.
#

function is_login() {
    global $HTTP_SESSION_VARS;

    # No array, no login.
    if (!isset($HTTP_SESSION_VARS["login"])
     or !isset($HTTP_SESSION_VARS["login"]["exp"])
     or !isset($HTTP_SESSION_VARS["login"]["user"])) {
      return false;
    }

    # Have array, but login expired.
    $now = time();
    if ($HTTP_SESSION_VARS["login"]["exp"] < $now)
      return false;

    # Here $HTTP_SESSION_VARS["login"]["user"] is
    # guaranteed and the login has not expired yet.

    # refresh
    $HTTP_SESSION_VARS["login"]["exp"] = $now + AUTOLOGOUT_TIME;
    
    # login is valid
    return true;
  }

# Draw a login form. That form is required to have form fields
# that match whatever is checked by handle_login() and it must
# have a submit button named "login". It must be POSTed.

# Within these constraints it may do whatever wants.
  function loginform() {
    global $HTTP_SERVER_VARS;
?>
<form action="<?php echo $HTTP_SERVER_VARS["PHP_SELF"] ?>" method="post">
<table border="0" cellspacing="0" cellpadding="0" bgcolor="#eeeeee">
<tr>
 <td>
  <input type="text" name="user" value=""><br>
  <font size="-2">Username</font>
 </td>
</tr>

<tr>
 <td>
  <input type="password" name="pass" value=""><br>
  <font size="-2">Paßwort</font>
 </td>
</tr>

<tr align="right">
 <td>
  <input type="submit" name="login" value="Go!">
 </td>
</tr>
</table>
</form>
<?php
  }

# Draw a loggedinform, that is, a logout button. That button
# must be named logout.

# We report the user name from the session.
  
  function loggedinform() {
    global $HTTP_SESSION_VARS;
    global $HTTP_SERVER_VARS;

?>
<form action="<?php echo $HTTP_SERVER_VARS["PHP_SELF"] ?>" method="post">
<table border="0" cellspacing="0" cellpadding="0" bgcolor="#eeeeee">
<tr>
 <td>
  <?php echo $HTTP_SESSION_VARS["login"]["user"] ?>
 </td>
</tr>

<tr align="right">
 <td>
  <input type="submit" name="logout" value="Let me out!">
 </td>
</tr>
</table>
</form>

<?php
  }

# Now we need to handle logins. That is, we must at least
# set up $login["user"] and $login["exp"]. We also must
# set up any additional information needed by higher level
# functions such as a permission check.

# Time to talk about perms. We have three main tables in
# n:m relationships, resulting in 5 actual tables:

# c_user - Account database
# pk is uid.
# login (unique), pass (binary), realname, all varchar not null

# c_group - Group list
# pk is gid.
# name (unique) varchar not null

# c_permission - Permission list
# pk is pid.
# name (unique) varchar not null

# We also have two joining tables, u_g_rel and g_p_rel:
#
# u_g_rel
# pk is (uid, gid)
# gid (index)

# g_p_rel
# pk is (gid, pid)
# pid (index)

# A user can be member of many groups, even none. Also,
# a group can have many member users, even none.

# A group has many attached permissions, even none. Also,
# a permission may be attached to many groups, even none.

# Optionally, you may attach permissions to users directly.
# That would require a sixth table, u_p_rel,
#
# u_p_rel
# pk is (uid, pid)
# pid (index)

# A user has many directly attached permissions, even none.
# Also, a permission may be directly attached to many users,
# even none.

# That's a trivial convenience extension we did not implement
# in our little course, but simple to add.

  function handle_login() {
    global $HTTP_POST_VARS;
    global $HTTP_SESSION_VARS;

# now, if a login form is being submitted, we need to have a
# user and a pass. We extract them and check. At the same time,
# we hunt down the permissions attached to the user via groups:
  
    if (isset($HTTP_POST_VARS["login"])) {
    
            if (!isset($HTTP_POST_VARS["user"])
             or !isset($HTTP_POST_VARS["pass"]))
               return;

            $user = $HTTP_POST_VARS["user"];
            $pass = $HTTP_POST_VARS["pass"];

            $query = sprintf("select c_user.login as login,
                                     c_user.realname as realname,
                                     c_permission.name as permission
                                from c_user,
                                     u_g_rel,
                                     c_group,
                                     g_p_rel,
                                     c_permission
                               where c_user.uid = u_g_rel.uid
                                 and u_g_rel.gid = c_group.gid
                                 and c_group.gid = g_p_rel.gid
                                 and g_p_rel.pid = c_permission.pid
                                 and c_user.login = '%s' and c_user.pass = '%s'",
                       $user,
                       $pass
                     );
            $db = new DB_CMS($query);
            if ($db->num_rows()) {

# We do have a result that is nonempty. Set up the attributes.

             $HTTP_SESSION_VARS["login"]["user"] = $user;
             $HTTP_SESSION_VARS["login"]["exp"] = time() + AUTOLOGOUT_TIME;
             $HTTP_SESSION_VARS["login"]["realname"] = $db->f("realname");
             while ($db->next_record()) {
               $HTTP_SESSION_VARS["login"]["perm"][$db->f("permission")] = "yes";
             }
            }

# Trivially, that may be extended to include the permissions from
#
# select c_permission.name as permission
# from c_user, u_p_rel, c_permission
# where c_user.uid = u_p_rel.uid and u_p_rel.pid = c_permission.pid
# and c_user.login = '%s' and c_user.pass = '%s'
#
# as well. These are simply added to the result set above.
#
# A user with no permissions cannot log in in the above scheme.
# Can be easily fixed, but requires then 3 queries on login.
# Not a problem, because logins are not time critical.
    }

# Handle a logout, if the button is pressed or if the login
# expires. Handling is trivial: Drop the $login array from the
# session.

    if (isset($HTTP_POST_VARS["logout"]) or ! is_login()) {
      $HTTP_SESSION_VARS["login"] = array();
    }
  }
 ?>

Now that we have a login and a logout, we may dare to think
about permissions. Actually, that's quite trivial with the above
setup, because we can simply check or enumerate
$login["perm"][].

<?php

# A function to generate the necessary option tags in a selection list.
#
# $o is a copy of $login["perm"][]. $class is the name of
# a database class.

  function perm_options($o = "", $class = "") {
    $db = new $class("select pid, name from c_permission order by name");
    while($db->next_record()) {
      echo ' <option value="', $db->f("pid"), '"';
      if (is_array($o) and isset($o[$db->f("name")])) {
        echo ' selected';
      }
      echo '>', $db->f("name");
      echo '</option>', "\n";
    }
  }

# Return $login["perm"][] as a simple array for easy
# enumeration.

  function perm_list() {
    global $HTTP_SESSION_VARS;
    
    if (is_array($HTTP_SESSION_VARS["login"]["perm"]))
      return array_keys($HTTP_SESSION_VARS["login"]["perm"]);
    else
      return array();
  }

# Predicate. Give it any number of permission names and it
# will return true if the current user posses all these
# permissions.

# if (!perm_have("editor_sports", "editor_culture")) {
# # go away
# } else {
# # get rich
# }

# That's an implicit "and".
  
  function perm_have() {
    global $HTTP_SESSION_VARS;

    $args = func_get_args();
    for ($i=0; $i<count($args); $i++) {
      if (!isset($HTTP_SESSION_VARS["login"]["perm"][$args[$i]]))
        return false;
    }
    
    return true;
  }

# Predicate. Give it any number of permission names
# and it will return true if the current user posses ANY
# of these permissions.

# if (!perm_have_any("admin", "editor")) {
# # you worm
# } else {
# # Master, how may I serve you?
# }

# That's an implicit "or".

  function perm_have_any() {
    global $HTTP_SESSION_VARS;
    
    $args = func_get_args();
    for ($i=0; $i<count($args); $i++) {
      if (isset($HTTP_SESSION_VARS["login"]["perm"][$args[$i]]))
        return true;
    }
    
    return false;
  }
 ?>

We then spent the rest of the day to write an editor for these
tables, and refining the system.

PHP courses for beginners, advanced courses and 3->4 migration
courses are available from NetUSE as a two or three day package
on your site, level of difficulty and content can be negotiated
to fit your capabilities and needs. Courses can have up to 8
participiants (6 are better for advanced courses). Contact
hr <email protected> (Mr. Heinz Rohde) for details.

Kristian

---------------------------------------------------------------------
To unsubscribe, e-mail: phplib-unsubscribe <email protected>
For additional commands, e-mail: phplib-help <email protected>