Добро пожаловать!
Здесь вы можете найти ответ на интересующий вас вопрос в отрасли сайтостроения, познакомится ближе с web технологиями и web стандартами.

Статьи

Каждому по правам

Введение

В предыдущих статьях по PHP, мы уже рассматривали некоторые вопросы безопасности и доступа к функциям скрипта. Так, в статье "Аусвайс контроль", была описана реализация сценария, который ограничивал доступ не зарегистрированных пользователей к определенным ресурсам сайта. Это был не сложный сценарий, в котором доступ либо предоставлялся, либо запрещался. В сложных системах, права доступа разграничиваются на более мелкие составляющие. Одним пользователям разрешено делать одни действия, другим – совсем иные. Именно такую гибкую систему распределения прав мы сейчас и напишем. Как обычно, средствами разработки выступят PHP и MySQL.

Структура БД

Для наглядной демонстрации разграничения уровней доступа отличным примером послужит скрипт новостей. Он будет показывать на странице новости, которые хранятся в таблице и загружаются из БД. Также наша БД будет содержать еще одну таблицу, в которой будут храниться имена пользователей и информация об их правах доступа. Таким образом, одни пользователи смогут, к примеру, добавлять новости, другим будет разрешаться корректировать и удалять новости. Будут и такие пользователи, у которых права доступа позволят делать все, вплоть до создания и удаления пользователей.

Нам понадобится БД, создадим и назовем ее news_service, выполнив такой sql-запрос:

CREATE DATABASE `news_service`;

Теперь создадим таблицу для хранения новостей. Для этого следует выполнить следующий sql-запрос:

CREATE TABLE `news` (
  `id` int(11) NOT NULL auto_increment,
  `header` varchar(25) NOT NULL default '',
  `text` text NOT NULL,
  `date` varchar(10) NOT NULL default '',
  PRIMARY KEY  (`id`)
)

Здесь мы имеем четыре поля. Ключевое поле id служит привязкой к новости. В поле header хранится заголовок новости. Сам текст новости будет храниться в поле text. Поле date служит для информации о дате размещения новости на сайте. Теперь разберем структуру таблицы auth, которая отвечает за права пользователей в системе. Для создания таблицы выполним такой sql-запрос:

CREATE TABLE `auth` (
  `login` varchar(50) NOT NULL default '',
  `pass` varchar(32) NOT NULL default '',
  `add` enum('true','false') NOT NULL default 'false',
  `edit` enum('true','false') NOT NULL default 'false',
  `delete` enum('true','false') NOT NULL default 'false',
  `add_user` enum('true','false') NOT NULL default 'false',
  `del_user` enum('true','false') NOT NULL default 'false',
  PRIMARY KEY  (`login`)
)

В ключевое поле login записывается имя пользователя. Поле pass хранит md5-хэш пароля. Все последующие поля определяют доступ к функциям и могут иметь только значения true(разрешено) или false(запрещено). По-умолчанию, все поля имеют значение false, то есть функции запрещены. Поля определяют такие свойства:

  • add – добавление новостей
  • edit – редактирование новостей
  • delete – удаление новостей
  • add_user – создание нового пользователя
  • del_user – удаление пользователя

Авторизация

После того, как мы подготовили БД к работе, можно приступать за написание скрипта, который будет с ней работать. Весь код будет находиться в одном файле, назовем его news.php. Участок кода, где будет проходить авторизация пользователя, будет выполняться после любой попытки воспользоваться сервисом. Другими словами, этот участок кода должен многократно повторяться. Чтобы избежать этого, поместим код обрабатываемый авторизацию пользователей в отдельную функцию, которая будет определять, разрешено ли запрашиваемое пользователем действие или нет. Начнем написания кода, именно с функции auth():

function auth($do)
{
  /* глобальная переменная для подключения к БД */
  global $db_connect;
  /* проверка на заполненные cookie */
  if  (empty($_COOKIE['login']) || empty($_COOKIE['pass']))
  {
  /* форма ввода логина и пароля */
  echo '.::Необходима авторизация::.<p>';
  echo '<form action=news.php method=POST>';

  echo 'Имя:<br> <input type=text name=name><br>';
  echo 'Пароль:<br> <input type=password name=pass><p>';
  echo '<input type=submit value=Ok>';

  echo '</form>';
  exit;
  }
  /* проверяем наличия пользователя с указанным логином и паролем */
  $rez = mysql_query("SELECT * 
                      FROM `auth` 
                      WHERE `login`='".$_COOKIE['login']."' AND 
                            `pass`='"  .$_COOKIE['pass']."' AND 
                            `".$do."`='true'",$db_connect);

  //если пользователь найден
  if (mysql_num_rows($rez) > 0)
    return true;
  else
    return false;
}

Сначала мы проверяем, нужно ли вводить пользователю данные или их можно взять из, введенных им ранее, cookie. Если cookie не заполнены, мы выводим форму для авторизации, где нужно указать имя и пароль (рис. 1). После этого идет запрос к БД, условием которого является поиск пользователя с указанным логином, паролем и правами на выполнение действия, которое выполняется. Если авторизация пройдена успешно, то в результате функция auth() вернет true, иначе будет возвращено значение false.

Напишем в коде стандартные строки для подключения к серверу БД:

$db_connect = mysql_connect('localhost','root','');
mysql_select_db('news_service',$db_connect);

Здесь, при необходимости, вам нужно заменить имя сервера БД (localhost), имя пользователя (root) и пароль (здесь без пароля). Имя нашей БД - news_service, вы также можете изменить его на имя своей БД. Далее допишем обработку авторизации пользователя, которая идет вне функции, чтобы отлавливать переданные параметры формы.

if (!empty($_POST['name']) && !empty($_POST['pass'])) {
  $name = $_POST['name']; //логин
  $pass = md5($_POST['pass']); // хэш пароля
  //запрос по данному пользователю
  $rez = mysql_query("SELECT login FROM `auth` WHERE login='".$name."' AND pass='".$pass."'");

  //если найден
  if (mysql_num_rows($rez) > 0) {
    //записываем данные в cookie
    setcookie('login',$name,time()+300);
    setcookie('pass',$pass,time()+300);
  }
}

После получения от пользователя его логина и пароля, мы проверяем его подлинность. Если в таблице auth нашлась запись с указанным логином и хэшем пароля, то данный логин и хэш пароля записываются в cookie к пользователю на компьютер. Если введенная информация не верная, то мы не делаем ничего, так как запись не верных данных не имеет смысла. Работать без требования авторизации можно будет на продолжении пяти минут, после этого будет снова показан запрос на авторизацию. Чтобы увеличить время работы, необходимо изменить значение параметра функции setcookie(). Сейчас там указано 300 секунд, то есть 5 минут.

Работа с новостями

Теперь будем отлавливать возможные действия пользователя. Сначала, выполним добавление новостей:

if (!empty($_POST['text_add']) && !empty($_POST['header_add']) && auth('add'))
  mysql_query('INSERT INTO `news`
               (`id`,`header`,`text`,`date`) 
               VALUES
               (\'\',\''.$_POST['header_add'].'\', 
                \''.$_POST['text_add'].'\',
                \''.date('d.m.Y').'\')');

Если скрипт получает на обработку текст и заголовок для добавления, а также имеет права на добавление новых новостей, то скрипт добавляет новую запись в таблицу. Первое поле мы не заполняем, так как оно заполняется автоматически, далее записываем заголовок и текст новости. С помощью функции date() мы записываем текущую дату в формате дд.мм.ГГГГ, которая отображает дату добавления новости.

Обработка редактирования новости:

if ( !empty($_POST['text_edit']) && 
     !empty($_POST['header_edit']) && 
     !empty($_POST['id']) && 
     auth('edit') )
{
  mysql_query('UPDATE `news` 
               SET `header`="'.$_POST['header_edit'].'", 
                   `text`="'.$_POST['text_edit'].'" 
               WHERE id='.$_POST['id']);
}

Если передается отредактированный заголовок и текст новости, а также текущему пользователю разрешено изменять новости, то мы обновляем данные в таблице новостей. Идентификация требуемой новости осуществляется по ключевому полю id, указывающем на номер новости, которую мы редактируем.

Удаление пользователя из системы:

if (!empty($_POST['del_name']) && auth('del_user') )
  mysql_query('DELETE FROM `auth` 
               WHERE `login` = "'.$_POST["del_name"].'"');

Если введено имя пользователя, которого нужно удалить и у пользователя есть на это права, запись с указанным логином будет удалена из таблицы.

Добавление нового пользователя в систему:

if (!empty($_POST['add_name']) && 
    !empty($_POST['add_name_pass']) && 
    auth('add_user') )
{
  /* переменные, отвечающие за права доступа нового пользователя */
  $add = 'false';
  $edit = 'false';
  $del = 'false';

  $del_user = 'false';
  $add_user = 'false';
  /* проверка, на установленные разрешения для нового пользователя */
  if (!empty($_POST['add']) && $_POST['add'] == 'on')
    $add = 'true';
  if (!empty($_POST['del']) && $_POST['del'] == 'on')
    $del = 'true';
  if (!empty($_POST['edit']) && $_POST['edit'] == 'on')
    $edit = 'true';
  if (!empty($_POST['add_user']) && $_POST['add_user'] == 'on')
    $add_user = 'true';
  if (!empty($_POST['del_user']) && $_POST['del_user'] == 'on')
    $del_user = 'true';

  /* проверка на совпадающие имена */
  $rez = mysql_query('SELECT login from `auth` WHERE login=\''.$_POST["add_name"].'\'');
  //если логин еще не используется
  if ( mysql_num_rows($rez) == 0 )
  {
//добавление новой записи в таблицу
     mysql_query('INSERT INTO `auth`
	             (`login`,`pass`,`add`,`edit`,`delete`,`add_user`,`del_user`)
				  VALUES
				  (\''.$_POST['add_name'].'\',
				  \''.md5($_POST['add_name_pass']).'\',
                  \''.$add.'\',\''.$edit.'\',
                  \''.$del.'\',
                  \''.$add_user.'\',
                  \''.$del_user.'\')');
   }
}

При создании нового пользователя, проверяется, имеет ли право на такие действия текущий пользователь. Все права пользователя определяются переменными: $add, $edit, $del, $del_user и $add_user. Изначально, все запрещено, то есть, значения переменных равны false. Полученные данные из формы, определяют новые права пользователя. Последняя проверка, на совпадение имен в таблице. Если все указано верно, то в таблицу auth вносится новая запись. Теперь напишем отображение форм, для каждого из обрабатываемых действий.

Форма для добавления новости:

if ( !empty($_GET['do']) )
{
  if ($_GET['do']=='add' && auth('add'))
  {
     echo '<a href=news.php>Главная страница</a><p>
           <form action=news.php method=POST>
           Заголовок:<br><input type=text name=header_add size=30 maxlength=25><br>
           Новость:<br>
           <textarea name=text_add cols=40 rows=4></textarea> <p>
           <input type=submit value="Добавить новость">
           </form>';
     exit;
  }

Все действия по отображению отлавливаются с помощью переменной do, которая передается через адресную строку. Форма добавления новости показана на рисунке 2.

Форма редактирования новости:

if ($_GET['do']=='edit' && !empty($_GET['id']) && auth('edit') )
{
    $rez = mysql_query('SELECT header,text from `news` WHERE id='.$_GET['id']);
    $mass = mysql_fetch_array($rez);
    echo '<a href=news.php>Главная страница</a><p>
          <form action=news.php method=POST>
          <input type=hidden name=id value='.$_GET["id"].'>
          <input type=text name=header_edit value="'.$mass["header"].'" 
          size=30 maxlength=25><br>
          Новость:<br>
          <textarea name=text_edit cols=40 rows=4>'.$mass["text"].'</textarea> <p>
          <input type=submit value="Изменить новость">
          </form>';
    exit;
}

Форма для редактирования ничем не отличается, от формы добавления. Вместо создания новой записи в таблице, мы изменяем уже существующую новость. При загрузке формы, она отображается не пустой, а с текущими значениями полей. При нажатии на кнопку "Изменить новость", новые данные заносятся в таблицу.

Удаление новости:

if ( $_GET['do']=='del' && 
     !empty($_GET['id']) && 
	 auth('delete'))
  mysql_query('DELETE FROM `news` WHERE `id` = '.$_GET['id']);

Отображение формы не требуется, так как удаление происходит при нажатии на ссылку «Удалить», которая находится рядом с новостью. Если права доступа разрешают удаление новостей, то запись с указанным id будет удалена из таблицы.

Вывод списка всех пользователей:

if ( $_GET['do'] == 'list_user' ) {
   echo '<a href=news.php>Главная страница</a><p>';
   $rez = mysql_query('SELECT login from `auth`');
   echo 'Список пользователей: <br>';
   for($i=0;$i<mysql_num_rows($rez);$i++) {
     $mass = mysql_fetch_array($rez);
     echo '<b>'.$mass["login"].'</b><br>';
   }
   exit;
}

Никаких ограничений на вывод списка пользователей нет, их может просмотреть все, кто угодно, так что функция авторизации здесь упущена. Также это может помочь при удалении пользователя, чтобы не писать какие-то замысловатые ники, можно скопировать имя из этого списка.

Удаление пользователя из системы:

if ($_GET['do'] == 'del_user' && auth('del_user') ) {
  echo '<a href=news.php>Главная страница</a><p>
        <form action=news.php method=POST>
        Имя пользователя: <br><input type=text name=del_name size=50 maxlength=50>
        <input type=submit value="Удалить">
        </form>';
  exit;
}

Форма для удаления состоит из строки для ввода имени и кнопки "Удалить" (рис. 3).

Немного сложнее будет выглядеть форма для создания нового пользователя:

if ( $_GET['do'] == 'add_user' && 
     auth('add_user') )
{
  echo '<a href=news.php>Главная страница</a><p>
        <form action=news.php method=POST>
        Имя пользователя: <br><input type=text name=add_name size=50 maxlength=50><br>
        Пароль: <br><input type=password name=add_name_pass size=50 maxlength=50><br>
        Права доступа для нового пользователя: <br>
        <input type=checkbox name=add>Добавление новостей<br>
        <input type=checkbox name=del>Удаление новостей<br>
        <input type=checkbox name=edit>Редактирование новостей<br>
        <input type=checkbox name=add_user>Добавление пользователей<br>
        <input type=checkbox name=del_user>Удаление пользователей<p>
        <input type=submit value="Добавить">
        </form>';
  exit;
}

Для создания нового пользователя в системе, нужно указать его логин, пароль, а также права доступа в системе. На рисунке 4 показана отображаемая при этом форма.

Как вы заметили, при обработке или отображении форм определенных действий, мы всегда завершаем работу скрипта с помощью команды exit. В случае, когда никаких параметров не передается, нужно просто отображать страницу с новостями и ссылками на все доступные функции скрипта:

echo '<a href="news.php?do=add" >Добавить новость</a><p>';
$rez = mysql_query('SELECT * from `news` ORDER BY id DESC');
for ( $i=0; $i<mysql_num_rows($rez); $i++ )
{
   $mass = mysql_fetch_array($rez);
   echo '<h2>'.$mass["header"].'</h2>';
   echo '<b>'.$mass["date"].'</b><p>';
   $mass["text"] = str_replace(chr(13),'<br>',htmlspecialchars($mass["text"]));
   echo $mass["text"].'<br>[<a href="news.php?do=edit&id='.$mass['id'].'">Правка</a>] ';
   echo '[<a href="news.php?do=del&id='.$mass['id'].'">Удалить</a>].<hr><br>';
}
echo '<p align=right><a href=news.php?do=add_user>Добавить пользователя</a><br>';
echo '<p align=right><a href=news.php?do=del_user>Удалить пользователя</a><br>';
echo '<p align=right><a href=news.php?do=list_user>Список пользователей</a><br>';
mysql_close($db_connect);

Заключение

Посмотреть скрипт в работе можно на рисунке 5. Оформление примитивное, но статья учит другому делу :). Главное понять принцип работы системы, а где вы его применяете, на скрипте новостей, гостевой книге или форуме, не столь важно. Безопасность всегда должна стоять не на последнем месте при написании скрипта. А если с ним будет работать большое количество людей, то к этому вопросу стоит подойти с особой тщательностью.

Источник: http://www.heel.org.ua
Автор: Сергей Парижский