in my current Silex project I would like to realize a login via Oauth and basic login both. For OAuth, I am using a Silex extension (https://github.com/gigablah/silex-oauth). Unfortunately, now I have problems with integrating the basic login.
My thoughts are that I have to create a custom user provider which provides both OAuth and password via DB, but I don't know how to realize it really.
At this point, I have a really ugly mixture of two User providers. For me, it is logic, but it will not work. I think I am off the track, so it would be really nice, if you can give me some tips - I am trying it for a few days now...
My user provider:
<?php
namespace Core;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Doctrine\DBAL\Connection;
class UserProvider implements UserProviderInterface
{
private $conn;
private $oauth;
public function __construct($oauth = null)
{
global $app;
if($oauth) { $this->oauth = $oauth; }
$this->conn = $app['db'];
}
/*public function loadAllUsers()
{
$users = $this->conn->executeQuery('SELECT * FROM users')->fetchAll();
$users_object = array();
foreach ($users as $user) {
if (!empty($user['username'])) {
$users_object[$user['username']] = array($user['password'], $user['firstname'], $user['lastname'], explode(',', $user['roles']));
}
}
$oauth = (array)$this->oauth;
print_r($oauth->users);
if (count($oauth['users']) > 0 ) {
print_r($this->oauth);
}
if ($this->oauth) {
if (count($oauth['users'])) {
return $this->oauth;
} else {
} return $users_object;
} else {
return $users_object;
}
}*/
public function loadUserByOAuthCredentials($token)
{
return $this->oauth->loadUserByOAuthCredentials($token);
}
public function loadUserByUsername($username)
{
if ($this->oauth->loadUserByUsername($username)) {
return $this->oauth->loadUserByUsername($username);
} else {
$stmt = $this->conn->executeQuery('SELECT * FROM users WHERE username = ?', array(strtolower($username)));
if (!$user = $stmt->fetch()) {
throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
}
return new User($user['username'], $user['password'], explode(',', $user['roles']), true, true, true, true);
}
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === 'Symfony\Component\Security\Core\User\User';
}
}
Thank you in advance, if you need more info, please tell me.
thoras
Related
You can subscribe on my website. I use FOSUserBundle.
When the user subscribes, he won the role ROLE_SUBSCRIBER giving it access to new page.
I would like this role expires after a period that I recorded in the User entity.
class User extends BaseUser
{
// ...
* #ORM\Column(type="datetime")
protected $subscribeExpiration;
public function setSubscribeExpiration(\DateTime $subscribeExpiration) {
$this->subscribeExpiration = clone $subscribeExpiration;
return $this;
}
public function getSubscribeExpiration() {
return $this->subscribeExpiration;
}
// ...
}
Don't use a ROLE, but a Voter.
Then, in your voter check for the expireDate to decide if the user is a subsciber or not :
// src/AppBundle/Security/PostVoter.php
namespace AppBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use AppBundle\Entity\User;
class SubscriberVoter extends Voter
{
const IS_SUBSCRIBER = 'is_subscriber';
protected function supports($attribute, $subject)
{
if (!in_array($attribute, array(self::IS_SUBSCRIBER))) {
return false;
}
return true;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
$user = $token->getUser();
if (!$user instanceof User) {
// the user must be logged in; if not, deny access
return false;
}
// you know $subject is a Post object, thanks to supports
/** #var Post $post */
$post = $subject;
switch ($attribute) {
case self::IS_SUBSCRIBER:
$expireDate = $user->getSubscriberExpireDate();
$currendDate = new \DateTime();
return (null !== $expireDate && $expireDate > $currendDate);
}
throw new \LogicException('This code should not be reached!');
}
}
To check this 'role' :
$this->isGranted('is_subscriber');
I'm a new user of Slim framework, I've a simple Slim 3 application, with sign in and sign up validation. But I'm not really sure if this is the right/best way to set errors and check if user is logged in -In order to redirect it to his account if session user.id exists.
I used a middleware: AuthMiddleware which includes:
class AuthMiddleware
{
protected $container;
public function __construct($container)
{
$this->container = $container;
}
public function __invoke($request, $response, $next)
{
if (isset($_SESSION['user.id']) && !empty($_SESSION['user.id'])) {
return $response->withRedirect($this->container->router->pathFor('user.index'));
}
$twig = $this->container->view->getEnvironment();
if (isset($_SESSION['validation'])) {
$twig->addGlobal('errors', $_SESSION['validation']['errors']);
$twig->addGlobal('values', $_SESSION['validation']['values']);
unset($_SESSION['validation']);
}
if (isset($_SESSION['auth.signup.success'])) {
$twig->addGlobal('auth_signup_success', $_SESSION['auth.signup.success']);
unset($_SESSION['auth.signup.success']);
}
if (isset($_SESSION['auth.signin.failed'])) {
$twig->addGlobal('auth_signin_failed', $_SESSION['auth.signin.failed']);
unset($_SESSION['auth.signin.failed']);
}
$response = $next($request, $response);
return $response;
}
}
And I used Twig for my views.
Session validation assigned in the validator.php which includes:
class Validator
{
protected $errors = [];
protected $values = [];
public function validate($request, $rules)
{
foreach ($rules as $field => $rule) {
$this->values[$field] = $request->getParam($field);
try {
$rule->setName(ucfirst($field))->assert($request->getParam($field));
} catch (NestedValidationException $e) {
$this->errors[$field] = $e->getMessages()[0];
}
}
if ($this->failed()) {
$_SESSION['validation'] = [
'errors' => $this->errors,
'values' => $this->values,
];
}
return $this;
}
public function failed()
{
return !empty($this->errors);
}
}
Using Respect\Validation. Also, is this the right use of Middlewares?
Thanks in advance.
try creating a separate file for the methods, and calling it from the middleware:
<?php
class AuthMiddleware extends Middleware {
public function __invoke($request, $response, $next) {
if (!$this->container->auth->check()) {
$this->container->flash->addMessage('danger', 'Please sign in to continue.');
return $response->withRedirect($this->container->router->pathFor('auth.signin'));
}
$response = $next($request, $response);
return $response;
}
}
while the Auth class would have those methods to check:
<?php
public function check () {
return isset($_SESSION['user']);
}
public function user() {
if (isset($_SESSION['user'])) {
return User::find($_SESSION['user'])->first();
} else {
return false;
}
}
Don't forget to include the Auth Class within your $app:
<?php
$container['auth'] = function ($container) {
return new \App\Auth\Auth();
};
I've started a Silex project a week ago and still getting some issues within the service-container. Although being quite simple.
Here is what happens to me:
$app->post('/', function (Request $request) use ($app) {
$success = (new \Malendar\Application\Service\User\LoginUserService($app['user_repository'], $app['session']))->execute($request);
if ($success) {
return $app->redirect($app["url_generator"]->generate("calendar"));
} else {
return new Response($app['twig']->render('login.html', ['formError' => true]), 400);
}});
I've created a LoginUserService class that given my user respository and the session service I'm able to login the user, that means, compare to database and checking that both username and password are in the system. That works perfectly but the issue comes with the session provider. Here is the class code:
class LoginUserService implements ApplicationServiceInterface
{
private $userRepository;
private $session;
public function __construct(UserCaseRepository $userRepository, Session $session)
{
$this->userRepository = $userRepository;
$this->session = $session;
}
public function execute($request = null)
{
// TODO: Implement execute() method.
$userName = $request->get('user');
$password = $request->get('password');
$user = $this->userRepository->findByUsername($userName);
var_dump($user);
if (!empty($user) && $user->validate($password)) {
$this->session->start();
$this->session->set('id', $user->getUserId());
$this->session->set('username', $user->getName());
$this->session->set('email', $user->getEmail());
$this->session->save();
return true;
} else {
return false;
}
}
}
$this->session which I believe gets the app['session'] do not set the value of username, email and id, they remain null, and I can assure you that all data is well provided.
On the other hand, If I'm doing it outside the class, it works and the username it is set:
$app->post('/', function (Request $request) use ($app) {
$success = (new \Malendar\Application\Service\User\LoginUserService($app['user_repository'], $app['session']))->execute($request);
$app['session']->set('username', 'Pedro');
But of course it would like to pursue the usage of my loginService what do I am missing?
Thank you beforehand =)
I want to change the templating of the registration form in my project by extending the new twig layout. However it does not change. It doesnt show any errors, but I am still getting the original view of the form. I did everything I found in documentation, but it still wont change, why?
1) I extended the userBundle.
2) I made created the ApplicationSonataUserBundle and did this:
class ApplicationSonataUserBundle extends Bundle
{
/**
* {#inheritdoc}
*/
public function getParent()
{
return 'SonataUserBundle';
}
}
I made my new controller and overwrited the old one(I only changed the rendered layout):
<?php
namespace Application\Sonata\UserBundle\Controller;
use Sonata\UserBundle\Controller\RegistrationFOSUser1Controller as BaseController;
class Registration1Controller extends BaseController
{
public function registerAction()
{
$user = $this->container->get('security.context')->getToken()->getUser();
if ($user instanceof UserInterface) {
$this->container->get('session')->getFlashBag()->set('sonata_user_error', 'sonata_user_already_authenticated');
$url = $this->container->get('router')->generate('sonata_user_profile_show');
return new RedirectResponse($url);
}
$form = $this->container->get('sonata.user.registration.form');
$formHandler = $this->container->get('sonata.user.registration.form.handler');
$confirmationEnabled = $this->container->getParameter('fos_user.registration.confirmation.enabled');
$process = $formHandler->process($confirmationEnabled);
if ($process) {
$user = $form->getData();
$authUser = false;
if ($confirmationEnabled) {
$this->container->get('session')->set('fos_user_send_confirmation_email/email', $user->getEmail());
$url = $this->container->get('router')->generate('fos_user_registration_check_email');
} else {
$authUser = true;
$route = $this->container->get('session')->get('sonata_basket_delivery_redirect');
if (null !== $route) {
$this->container->get('session')->remove('sonata_basket_delivery_redirect');
$url = $this->container->get('router')->generate($route);
} else {
$url = $this->container->get('session')->get('sonata_user_redirect_url');
}
}
$this->setFlash('fos_user_success', 'registration.flash.user_created');
$response = new RedirectResponse($url);
if ($authUser) {
$this->authenticateUser($user, $response);
}
return $response;
}
$this->container->get('session')->set('sonata_user_redirect_url', $this->container->get('request')->headers->get('referer'));
return $this->container->get('templating')->renderResponse('MpShopBundle:Frontend:registration.html.'.$this->getEngine(), array(
'form' => $form->createView(),
));
}
}
3) I added new Application\Sonata\UserBundle\ApplicationSonataUserBundle(), to the AppKernel.
Did I miss anything? What can be the problem?
UPDATE :
Now I am getting this error: Compile Error: Namespace declaration statement has to be the very first statement in the script. but my namespace is the first statement isnt it?
I use the framework Silex, especially SecurityServiceProvider.
I have to create my own User class (because my salt is the username => with the default class the salt is null) :
<?php
namespace Adh\Security;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
class User implements AdvancedUserInterface {
private $username;
private $password;
public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}
public function getRoles()
{
return array();
}
public function getPassword()
{
return $this->password;
}
public function getSalt()
{
return $this->username;
}
...
}
Until this, no problem. Now, I have to create a custom UserProvider to retrieve my user from MySQL :
<?php
namespace Adh\Security;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Doctrine\DBAL\Connection;
class UserProvider implements UserProviderInterface
{
private $conn;
public function __construct(Connection $conn)
{
$this->conn = $conn;
}
public function loadUserByUsername($username)
{
$stmt = $this->conn->executeQuery('SELECT * FROM account WHERE username like ?', array($username));
if (!$user = $stmt->fetch()) {
throw new UsernameNotFoundException(sprintf('Le nom d\'utilisateur "%s" n\'existe pas', $username));
}
return new \Adh\Security\User($user['username'], $user['sha_pass_hash']);
}
...
}
And to register the security provider :
$app->register(new Silex\Provider\SecurityServiceProvider(), array(
'security.firewalls' => array(
'user' => array(
'pattern' => '^/user',
'form' => array('login_path' => '/connexion', 'check_path' => '/user'),
'users' => $app->share(function () use ($app) {
return new Adh\Security\UserProvider($app['db']);
})
)
)
));
$app['security.encoder_factory'] = $app->share(function ($app) {
return new EncoderFactory(
array('Adh\Security\User' => new Adh\Security\PasswordEncoder())
);
});
It works, except when the authentification is positive (the username and password match) I've this exception :
RuntimeException: There is no user provider for user
"Adh\Security\User".
How to set my UserProvider for my User class ?
Thank's
I found the solution. To create my provider I followed this example : http://silex.sensiolabs.org/doc/providers/security.html#defining-a-custom-user-provider
In the refreshUser method:
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
This is correct for the default User class: I have my own User class so the exception is raised.
The condition becomes :
if (!$user instanceof \Adh\Security\User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
Your function
loadUserByUsername()
does not return any role. By default a Symfony\Component\Security\Core\User\User record is returned with the roles of the user as third parameter. At least any user must have one role.
Sample:
use Symfony\Component\Security\Core\User\User;
public function loadUserByUsername($username)
{
$frameworkUser = new FrameworkUser($this->app);
if (false === ($user = $frameworkUser->selectUser($username))) {
throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
}
return new User($user['username'], $user['password'], $user['roles'], true, true, true, true);
}