Skip to content
This repository has been archived by the owner on Dec 6, 2023. It is now read-only.

[WIP]Implement authenticateToken #10

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 202 additions & 0 deletions Authentication/Auth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/

namespace phpBB\SessionsAuthBundle\Authentication;

/**
* Permission/Auth class
*
* This is a copy based on the original phpBB Auth class. It has been modified to work with
* Doctrine entities for the ACL_OPTIONS_TABLE, and all unneeded stuff has been removed.
*
* This version does not support recreating the user_permissions field in the user table.
* If there is no user_permissions field, it will result in a exception.
*
*/
class Auth
{
private $acl = array();
private $cache = array();
private $aclOptions = array();
private $aclForumIds = false;

/**
* Init permissions
* @param $userPermissions string
* @throws \Exception
*/
public function acl($userPermissions)
{
$this->acl = array();
$this->cache = array();
$this->aclOptions = array();
$this->aclForumIds = false;

if (($this->aclOptions = $cache->get('_acl_options')) === false)
{
$sql = 'SELECT auth_option_id, auth_option, is_global, is_local
FROM ' . ACL_OPTIONS_TABLE . '
ORDER BY auth_option_id';
$result = $db->sql_query($sql);

$global = 0;
$local = 0;
$this->aclOptions = array();
while ($row = $db->sql_fetchrow($result))
{
if ($row['is_global'])
{
$this->aclOptions['global'][$row['auth_option']] = $global++;
}

if ($row['is_local'])
{
$this->aclOptions['local'][$row['auth_option']] = $local++;
}

$this->aclOptions['id'][$row['auth_option']] = (int) $row['auth_option_id'];
$this->aclOptions['option'][(int) $row['auth_option_id']] = $row['auth_option'];
}
$db->sql_freeresult($result);

$cache->put('_acl_options', $this->aclOptions);
}

if (!trim($userPermissions))
{
throw new \Exception('We require user_permissions set by phpBB.');
}

// Fill ACL array
$this->_fill_acl($userPermissions);

// Verify bitstring length with options provided...
$renew = false;
$global_length = sizeof($this->aclOptions['global']);
$local_length = sizeof($this->aclOptions['local']);

// Specify comparing length (bitstring is padded to 31 bits)
$global_length = ($global_length % 31) ? ($global_length - ($global_length % 31) + 31) : $global_length;
$local_length = ($local_length % 31) ? ($local_length - ($local_length % 31) + 31) : $local_length;

// You thought we are finished now? Noooo... now compare them.
foreach ($this->acl as $forum_id => $bitstring)
{
if (($forum_id && strlen($bitstring) != $local_length) || (!$forum_id && strlen($bitstring) != $global_length))
{
$renew = true;
break;
}
}

// If a bitstring within the list does not match the options, we have a user with incorrect permissions set and need to renew them
if ($renew)
{
throw new \Exception('Renewing is not supported.');
}

return;
}

/**
* Fill ACL array with relevant bitstrings from user_permissions column
* @param $userPermissions
*/
private function _fill_acl($userPermissions)
{
$seq_cache = array();
$this->acl = array();
$userPermissions = explode("\n", $userPermissions);

foreach ($userPermissions as $f => $seq)
{
if ($seq)
{
$i = 0;

if (!isset($this->acl[$f]))
{
$this->acl[$f] = '';
}

while ($subseq = substr($seq, $i, 6))
{
if (isset($seq_cache[$subseq]))
{
$converted = $seq_cache[$subseq];
}
else
{
$result = str_pad(base_convert($subseq, 36, 2), 31, 0, STR_PAD_LEFT);
$converted = $result;
$seq_cache[$subseq] = $result;
}

// We put the original bitstring into the acl array
$this->acl[$f] .= $converted;
$i += 6;
}
}
}
}

/**
* Look up an option
* if the option is prefixed with !, then the result becomes negated
*
* If a forum id is specified the local option will be combined with a global option if one exist.
* If a forum id is not specified, only the global option will be checked.
* @param $opt string option
* @param int $forumId int forum_id
* @return bool
*/
public function aclGet($opt, $forumId = 0)
{
$negate = false;

if (strpos($opt, '!') === 0)
{
$negate = true;
$opt = substr($opt, 1);
}

if (!isset($this->cache[$forumId][$opt]))
{
// We combine the global/local option with an OR because some options are global and local.
// If the user has the global permission the local one is true too and vice versa
$this->cache[$forumId][$opt] = false;

// Is this option a global permission setting?
if (isset($this->aclOptions['global'][$opt]))
{
if (isset($this->acl[0]))
{
$this->cache[$forumId][$opt] = $this->acl[0][$this->aclOptions['global'][$opt]];
}
}

// Is this option a local permission setting?
// But if we check for a global option only, we won't combine the options...
if ($forumId != 0 && isset($this->aclOptions['local'][$opt]))
{
if (isset($this->acl[$forumId]) && isset($this->acl[$forumId][$this->aclOptions['local'][$opt]]))
{
$this->cache[$forumId][$opt] |= $this->acl[$forumId][$this->aclOptions['local'][$opt]];
}
}
}

// Founder always has all global options set to true...
return ($negate) ? !$this->cache[$forumId][$opt] : $this->cache[$forumId][$opt];
}
}
147 changes: 136 additions & 11 deletions Authentication/phpBBSessionAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
namespace phpBB\SessionsAuthBundle\Authentication;


use Doctrine\ORM\EntityManager;
use phpBB\SessionsAuthBundle\Authentication\Provider\phpBBUserProvider;
use phpBB\SessionsAuthBundle\Entity\Session;
use phpBB\SessionsAuthBundle\Tokens\phpBBToken;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\SimplePreAuthenticatorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
Expand All @@ -22,36 +25,119 @@

class phpBBSessionAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface
{
const ANONYMOUS = 1;

/** @var string */
private $cookieName;

/** @var string */
private $cookiename;
private $boardUrl;

/** @var string */
private $boardurl;
private $loginPage;

/** @var RequestStack */
private $requestStack;

/** @var ContainerInterface */
private $container;

/** @var string */
private $loginpage;
private $dbConnection;

/**
* @param $cookiename string
* @param $boardurl string
* @param $loginpage string
* @param $requestStack RequestStack
* @param ContainerInterface $container
*/
public function __construct($cookiename, $boardurl, $loginpage)
public function __construct($cookiename, $boardurl, $loginpage, $dbconnection,
RequestStack $requestStack, ContainerInterface $container)
{
$this->cookiename = $cookiename;
$this->boardurl = $boardurl;
$this->loginpage = $loginpage;

$this->cookieName = $cookiename;
$this->boardUrl = $boardurl;
$this->loginPage = $loginpage;
$this->dbConnection = $dbconnection;
$this->requestStack = $requestStack;
$this->container = $container;
}

/**
* @param TokenInterface $token
* @param UserProviderInterface $userProvider
* @param $providerKey
* @return null|phpBBToken
*/
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
// TODO: Implement authenticateToken() method.
if (!$userProvider instanceof phpBBUserProvider)
{
throw new \InvalidArgumentException(
sprintf(
'The user provider must be an instance of phpBBUserProvider (%s was given).',
get_class($userProvider)
)
);
}

$sessionId = $this->requestStack->getCurrentRequest()->cookies->get($this->cookieName . '_sid');
$userId = $this->requestStack->getCurrentRequest()->cookies->get($this->cookieName . '_u');

if (empty($sessionId))
{
return null; // We can't authenticate if no SID is available.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't support SID in url?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nicofuma We might support it, however is there a chance that there is actually a SID in the URL in a symfony page?
I suppose symfony won't keep it after the first page.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's right, so forgot what I said ^

}

/** @var EntityManager $em */
$em = $this->container->get('doctrine')->getManager($this->dbConnection);

/** @var Session $session */
$session = $em->getRepository('phpbbSessionsAuthBundle:Session')->findById($sessionId);


if (!$session ||
$session->getUser() == null ||
($session->getUser() != null && $session->getUser()->getId() == self::ANONYMOUS) ||
$session->getUser()->getId() != $userId)
{
return null;
}

$userIp = $this->requestStack->getCurrentRequest()->getClientIp();

if (strpos($userIp, ':') !== false && strpos($session->getIp(), ':') !== false)
{
$s_ip = $this->shortIpv6($session->getIp(), 3);
$u_ip = $this->shortIpv6($userIp, 3);
}
else
{
$s_ip = implode('.', array_slice(explode('.', $session->getIp()), 0, 3));
$u_ip = implode('.', array_slice(explode('.', $userIp), 0, 3));
}

// Assume session length of 3600
if ($u_ip === $s_ip && $session->getTime() < time() - 3600 + 60)
{
// We have a valid user, which is not the guest user.

$roles = array();

if ($session->getUser()->isBot()) {
$roles[] = 'ROLE_BOT';
}
else
{

}

$token = new phpBBToken($session->getUser(), $providerKey, $roles);

return $token;
}
return null;

}

/**
Expand Down Expand Up @@ -83,7 +169,46 @@ public function createToken(Request $request, $providerKey)
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
return new RedirectResponse($this->boardurl . $this->loginpage);
return new RedirectResponse($this->boardUrl . $this->loginPage);
}

/**
* Returns the first block of the specified IPv6 address and as many additional
* ones as specified in the length paramater.
* If length is zero, then an empty string is returned.
* If length is greater than 3 the complete IP will be returned
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* @param $ip
* @param $length
* @return mixed|string
*/
private function shortIpv6($ip, $length)
{
if ($length < 1)
{
return '';
}

// extend IPv6 addresses
$blocks = substr_count($ip, ':') + 1;
if ($blocks < 9)
{
$ip = str_replace('::', ':' . str_repeat('0000:', 9 - $blocks), $ip);
}
if ($ip[0] == ':')
{
$ip = '0000' . $ip;
}
if ($length < 4)
{
$ip = implode(':', array_slice(explode(':', $ip), 0, 1 + $length));
}

return $ip;
}

}

Loading