Класс работы с .htpass файлом

  • Автор темы konstantin_18
  • Дата начала

konstantin_18

Guest
Класс работы с .htpass файлом

Я недаавно встретился с проблемой: надо было сделать аутентификацию для 10 юзеров в отдельный каталог. Я сделал через .haccess, всем понравилось, но получился БОЛЬШОЙ облом: хостер не предоставлял доступа к утилите htpasswd чтобы пароли юзеров менять или добавлять кого-то. Кроме этого даже если бы такое предоставлялось, то эта утилита не умеет удалять юзеров из файла. Это надо было делать ручками.
Я написал класс. Извините, работаю не на наших, поэтому все comments по английски. Если многим надо будет, я переведу, но... пока облом, честно говоря.
Короче, вот класс:
PHP:
<?php
/**
*@author Konstantin Mirin <[email protected]><[email protected]>
*@deprec This class allows to work with .htpass file like htpasswd command does. I wrote 
*it because some hosters do not provide access to this command (In my practice I had several 
*ones).
*And at the same time, apache authorisation is very convenient if you want to grant
*access to some part of the site only for several persons and using of database is not 
*needed (or allowed)
*/	
	/**
	*Class allows to work with apache user's file easily
	*/
	class ApacheAuth
	{
		/**
		*file with passwords
		*/
		var $_file;
		/**
		*users array
		*/
		var $_users;
		/**
		*array of user's logins
		*/
		var $_logins;
		/**
		*number of users
		*/
		var $_usersnum;
		
		/**
		*Constructor of the class. It sets path to the .passwd file and reads
		*the file into array
		*/		
		function ApacheAuth($path = null)
		{
			$this->_file = $path;
			$this->_ReadPasswordFile();
		}
		
		/**
		*function reads the password file to the array
		*/
		function _ReadPasswordFile()
		{
			if (file_exists($this->_file))//if there is any file
			{
				$users = file_get_contents($this->_file);
				//it takes it's contents
				$users = explode("\n", $users);
				//explode()s it by new line symbol
				$this->_usersnum = sizeof($users)-1;
				//calcualtes the number of linew with users
				for ($i = 0; $i < $this->_usersnum; $i++)
				//cycles throught them
				{
					$user = explode(':', $users[$i]);
					//divides each line into login and crypted password
					$this->_users[$i] = array(
					'login' => $user[0],//ligin
					'pass' => substr($user[1],2),//password itself
					'salt' => substr($user[1],0,2));//salt for password
					//writes all data to the users array
					$this->_logins[$i] = $user[0];
					//and wrutes login to the logins array. 
					//It serves for quick search of the user
				}
			}
			else
			{
				//if there is no any file, we set variables to avoid 
				//any possible errors due to wrong data type
				$this->_logins = array();
				$this->_users = array();
				$this->_usersnum = 0;
			}
		}
		
		/**
		*function returns true if there is any user with such login and false
		*if there is none
		*/
		function IsRegistered($login)
		{
			if (in_array($login, $this->_logins)) return true;
			return false;
		}
		
		/**
		*function simply creates random password of the defined length
		*/
		function _CreateRandomPassword($n)
		{
			$chars = "abcdefghijkmnopqrstuvwxyz023456789";
			srand((double)microtime()*1000000);
			$i = 0;
			$pass = '' ;
			while ($i <= $n-1)
			{
				$num = rand() % 33;
				$tmp = substr($chars, $num, 1);
				$pass = $pass . $tmp;
				$i++;
			}
			return $pass;
		}
		
		/**
		*function encryptes password using standart UNIX DES algorythm,
		* which is standart for htpasswd utility
		*@param string $pass It is password than needs to be encrypted
		*@return string $salt it is the salt with which pass war encrypted
		*@return string $pass it is the password without salt
		*Variables are returned in the form of the assisiative array
		*/
		function _CryptPass($pass)
		{
			$salt = $this->_CreateRandomPassword(2);
			$pass = substr(crypt($pass, $salt),2);
			return array('salt'=>$salt,'pass'=>$pass);
		}
		
		/**
		*Function checks, if the password is valid. 
		*@param integer $id It is the index of user in users array
		*@param string $pass It is password that needs to be checked
		*@return boolean $result It is the result of check
		*/
		function _CheckPass($id, $pass)
		{
			$oldpass = $this->_users[$id]['pass'];
			$salt = $this->_users[$id]['salt'];
			echo 'CRYPT()ED PASS'.crypt($pass, $salt).'<br>';
			if ($salt.$oldpass === crypt($pass, $salt)) return true;
			return false;
		}
		
		/**
		*Function adds user to the file if this user is not there currently
		*/
		function AddUser($login, $pass)
		{
			if (!$this->IsRegistered($login))//we check, if it is registered
			{
				$pass = $this->_CryptPass($pass);//we crypt() password
				$user = array(
						'login' => $login,
						'pass' => $pass['pass'],
						'salt' => $pass['salt']
						);
				array_push($this->_users, $user);
				//and write appropriate data to the array of users
				array_push($this->_logins, $login);
				//and array of logins
				$this->_usersnum +=1;
				//and increase number of users in the list
			}
			else return false;
			return true;
		}
		
		/**
		*Function finds user
		*@param string $login This is login of user to be found
		*@return integer $result This is the index for the element in
		* the users array containing data about this user
		*/
		function _FindUser($login)
		{
			return array_search($login, $this->_logins);
		}
				
		/**
		*Function compares two users to define, who of them must be first.
		*if uses strcmp function for comparison
		*/
		function _CMPUsers($a, $b)
		{
			return strcmp($a['login'], $b['login']);
		}
		
		/**
		*function sorts array of users and array of logins
		*/
		function SortUsers()
		{
			usort($this->_users, array('ApacheAuth', '_CMPUsers'));
			sort($this->_logins);
		}
		
		/**
		*Funscions changes user's data
		*@param string $login it is the login of the user
		*@param string $oldpass it is the olda pass for the user
		*@param string $newpass it is the new password for the user
		*/
		function ChangeUser($login, $oldpass, $newpass)
		{
			$id = $this->_FindUser($login);
			//we find user in the array
			if ($this->_CheckPass($id, $oldpass))
			//if old password is valid....
			//if you don't want this check to be performed, 
			//simply delete if condition and $oldapass parameter from function
			{
				$pass = $this->_CryptPass($newpass);
				//function crypts password
				$this->_users[$id]['salt'] = $pass['salt'];
				$this->_users[$id]['pass'] = $pass['pass'];
				//and we write it to the array
			}
			else return false;
			return true;
		}
		
		/**
		*Function deletes user
		*@param string $login it is the login of the user that will be deleted
		*/
		function DeleteUser($login)
		{
			$id = $this->_FindUser($login);
			unset($this->_users[$id]);
			unset($this->_logins[$id]);
			$this->_usersnum -=1;
		}
		
		/**
		*Function commits all changes that were done to the user's list. 
		*it writes the users array to the .htpass file
		*If doesn't write lines with empty user or password. 
		*/
		function Commit()
		{
			$this->SortUsers();
			for ($i = 0; $i < $this->_usersnum; $i++)
			{
				$user = $this->_users[$i];
				if (empty($user['login'])||empty($user['pass'])) continue;
				$usersfile .= $user['login'].':'.$user['salt'].$user['pass']."\n";
			}
			$fl = fopen($this->_file,'w+');
			fwrite($fl, $usersfile);
			fclose($fl);
			unset($this->_logins);
			unset($this->_users);
		}
		
		/**
		 * Function returns the list of users. It returns logins, not all array because all
		 * array is useless, passwords are crypted...
		 */
		function GetUsers()
		{
			return $this->_logins;
		}
		
		/**
		 * Function returns the list of users with theri all data.
		 */
		function GetAllUsersData()
		{
			return $this->_users;
		}
		
		/**
		 * Returns the number of users.
		 */
		function GetUsersNum()
		{
			return $this->_usersnum;
		}
	}
?>
Ниже простенький менеджер, который использует этот класс в своей работе. Не используется только сортировка:
PHP:
<?php
	include('lib/apacheauth.inc.php');
	$auth = new ApacheAuth('/home/kmirin/.htpass');
	$todo = $_GET['todo'];
	if (empty($todo)) $todo = $_POST['todo'];
	switch ($todo)
	{
		case 'adduser':
			{
				$auth->AddUser($_POST['login'], $_POST['pass']);
				$auth->Commit();
				break;
			}
		case 'dispadd':
			{
				echo
				'
				<form action="manager.php" method="POST">
				<input type="hidden" name="todo" value="adduser">
				<input type="text" name="login"><br>
				<input type="text" name="pass"><br>
				<input type="submit">
				</form>
				';
				break;
			}
		case 'changeuser':
			{
				$auth->ChangeUser($_POST['login'], $_POST['pass'], $_POST['newpass']);
				$auth->Commit();
				break;
			}
		case 'dispedit':
			{
				echo
				'
				<form action="manager.php" method="POST">
				<input type="hidden" name="todo" value="changeuser">
				<input type="text" name="login"><br>
				<input type="text" name="pass"><br>
				<input type="text" name="newpass"><br>
				<input type="submit">
				</form>
				';
				break;
			}
		case 'delete':
			{
				$auth->DeleteUser($_POST['login']);
				$users = $auth->GetUsers();
				$auth->Commit();
				break;
			}
		default:
			{
				$users = $auth->GetUsers();
				echo '<table border=1>';
				for ($i = 0; $i < sizeof($users); $i++)
				{
					echo "
					<tr>
						<td>"
							.$users[$i]['login'].
						"</td>
						<td>"
							.$users[$i]['salt'].
						"</td>
						<td>"	
							.$users[$i]['pass'].
						"</td>
						<td>	
							<a href=\"manager.php?todo=dispedit\">edit</a><br>
							<a href=\"manager.php?todo=dispadd\">add</a><br>
							<a href=\"manager.php?todo=delete\">delete</a>
						</td>
					</tr>
					";
				}
				echo '</table><br><a href="manager.php?todo=dispadd">add</a>';
				
			}
	}
?>
Поскольку я не могу назвать себя крутым профи, принимаютсявсе комментарии, критика и т.д. Единственная просьба - обосновано и культурно :)))
Хочу чтобы оценили как конкретный код так и правильность подхода к решению этой задачи.
Всем спасибо.

-~{}~ 22.09.05 22:28:

PS. Просьба при использовании этого класса в своем коде поставить ссылочку (в комментариях конечно) на меня. И указать сайтик x-planet.ru

-~{}~ 22.09.05 22:30:

PPS. Тема создана после обсуждения:
http://phpclub.ru/talk/showthread.php?s=&threadid=73297&rand=10
 

ForJest

- свежая кровь
Тема закрыта.

konstantin_18
Ну нужно постить в форум весь код. Пости только ссылки на архив, если уж так хочется выложить.
и покопай в следующий раз хорошенько http://pear.php.net/
 

konstantin_18

Guest
Да, там есть класс который работает с фалом паролей. С многими типами файлов...
Вот он:
http://pear.php.net/package/File_Passwd
Но свой вариант работы я нахожу более удобным для ЭТОЙ задачи, когда требуется всего лишь дать доступ к отдельной директории и обеспечить управление пользователями. Хотя, может потому что это МОЕ решение....

PS. Класс я подправил.
 

konstantin_18

Guest
Объясняю.
Для того чтобы закрыть доступ, надо написать так:
AuthUserFile /path/to/.htpass
AuthGroupFile /dev/null
AuthName "Admin"
AuthType Basic
require valid-user

Файл паролей формируется так:
1) С помощью стандартной утилиты:
htpasswd -b /path/to/.htpass login password
Если надо создать файл паролей и добавить в него юзера, то так:
htpasswd -bc /path/to/.htpass login password
добавляется ключ "c" - create
ключ b говорит о том, что пароль надо брать из этой же строки. Если не добавить этот ключ, то будет приглашение ввести пароль.
Дальше.
Редактирование делается тотчно так же. Только надо указать логин сущетсвующего юзера. Пароль будет изменен.
При команде такого вида пароль шифруется с помощью UNIX DES алгоритма.
Возможность удаления, выбора всех юзеров утилита не позволяет. Кроме этого, не везде разрешено ей прозоваться.
2)Поэтому предлагаю второй вариат - мой класс для работы с файлом паролей.
Подробнее смотри топик:
http://phpclub.ru/talk/showthread.php?s=&postid=522330

-~{}~ 23.09.05 17:08:

А, забыл добавить. Для использования команды - стандартные функции php для выполнения команд:
exec();
и подобные

-~{}~ 23.09.05 17:16:

А вот узнать кто есь кто -
$_SERVER['REMOTE_USER']
и дальше как напишешь...
 

ForJest

- свежая кровь
konstantin_18
Ну так и ищи людей, которым нужно решать ЭТУ задачу. С и предлагай и СВОЁ решение. САМ ищи и САМ предлагай. ГДЕ-НИБУДЬ ЕЩЁ...
 

konstantin_18

Guest
Хорошо, когда я даю линк на этот топик в форму в схожих темах - это нормльно, верно? Я предлагаю людям это решение.
 

Frol

Новичок
есть форум "ищу готовое решение".
подпишись на него. :)
 

Фанат

oncle terrible
Команда форума
Костя.
у меня к тебе большая просьба НЕ ФЛУДИТЬ.
За флуд тебя задушат вообще без разговоров.

-~{}~ 23.09.05 19:55:

Наслаждайся своей рекламой в своём топике и молись, чтобы никто больше твоих археологических раскопок не видел.
 
Сверху