I've inherited a project, which uses the Kohana MVC framework and it's Kohan_auth class to register someone. When submiting registration it submits a form post to the class below. I don't see where the ->register is used or what instance means or how to debug or solve this. Please help.
Auth::instance()->register($_POST, TRUE);
does not enter data and just redirects back to the registration page with no error messages. How can I debug this, unit tests also don't work as they require older phpunit versions.
Auth::instance() seems to go to this code
* #package Useradmin/Auth
* #author Gabriel R. Giannattasio
*/
abstract class Useradmin_Auth extends Kohana_Auth {
/**
* Singleton pattern
*
* #return Auth
*/
public static function instance()
{
if ( ! isset(Auth::$_instance))
{
// Load the configuration for this type
$config = Kohana::$config->load('auth');
if ( ! $type = $config->get('driver'))
{
$type = 'file';
}
// Set the session class name
$class = 'Auth_'.ucfirst($type);
$config->set("useradmin", Kohana::$config->load('useradmin.auth') );
// Create a new session instance
Auth::$_instance = new $class($config);
}
return Auth::$_instance;
}
}
which extends this
abstract class Kohana_Auth {
// Auth instances
protected static $_instance;
/**
* Singleton pattern
*
* #return Auth
*/
public static function instance()
{
if ( ! isset(Auth::$_instance))
{
// Load the configuration for this type
$config = Kohana::$config->load('auth');
if ( ! $type = $config->get('driver'))
{
$type = 'file';
}
// Set the session class name
$class = 'Auth_'.ucfirst($type);
// Create a new session instance
Auth::$_instance = new $class($config);
}
return Auth::$_instance;
}
protected $_session;
protected $_config;
/**
* Loads Session and configuration options.
*
* #return void
*/
public function __construct($config = array())
{
// Save the config in the object
$this->_config = $config;
$this->_session = Session::instance($this->_config['session_type']);
}
abstract protected function _login($username, $password, $remember);
abstract public function password($username);
abstract public function check_password($password);
/**
* Gets the currently logged in user from the session.
* Returns NULL if no user is currently logged in.
*
* #return mixed
*/
public function get_user($default = NULL)
{
return $this->_session->get($this->_config['session_key'], $default);
}
/**
* Attempt to log in a user by using an ORM object and plain-text password.
*
* #param string username to log in
* #param string password to check against
* #param boolean enable autologin
* #return boolean
*/
public function login($username, $password, $remember = FALSE)
{
if (empty($password))
return FALSE;
return $this->_login($username, $password, $remember);
}
/**
* Log out a user by removing the related session variables.
*
* #param boolean completely destroy the session
* #param boolean remove all tokens for user
* #return boolean
*/
public function logout($destroy = FALSE, $logout_all = FALSE)
{
if ($destroy === TRUE)
{
// Destroy the session completely
$this->_session->destroy();
}
else
{
// Remove the user from the session
$this->_session->delete($this->_config['session_key']);
// Regenerate session_id
$this->_session->regenerate();
}
// Double check
return ! $this->logged_in();
}
/**
* Check if there is an active session. Optionally allows checking for a
* specific role.
*
* #param string role name
* #return mixed
*/
public function logged_in($role = NULL)
{
return ($this->get_user() !== NULL);
}
/**
* Creates a hashed hmac password from a plaintext password. This
* method is deprecated, [Auth::hash] should be used instead.
*
* #deprecated
* #param string plaintext password
*/
public function hash_password($password)
{
return $this->hash($password);
}
/**
* Perform a hmac hash, using the configured method.
*
* #param string string to hash
* #return string
*/
public function hash($str)
{
if ( ! $this->_config['hash_key'])
throw new Kohana_Exception('A valid hash key must be set in your auth config.');
return hash_hmac($this->_config['hash_method'], $str, $this->_config['hash_key']);
}
protected function complete_login($user)
{
// Regenerate session_id
$this->_session->regenerate();
// Store username in session
$this->_session->set($this->_config['session_key'], $user);
return TRUE;
}
} // End Auth
Enable logging in application/bootstrap.php, php.ini and use:
Kohana::$log->add(Log::MESSAGE, $msg);
or
Kohana::$log->add(Log::MESSAGE, Kohana_Exception::text($e), Array(),Array('exception'=>$e));
see Log::MESSAGE
BTW: take my Log class:
<?php defined('SYSPATH') OR die('No direct script access.');
class Log extends Kohana_Log {
public function add($level, $message, array $values = NULL, array $additional = NULL){
if(strpos($message,'~') == FALSE) {
$message .= ' ~ ';
}
if(!isset($_SERVER['REQUEST_METHOD']))
$message .= " !!! TASK";
else
$message .= " !!! ($_SERVER[REQUEST_METHOD])//$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
$message = strtr($message,"\r\n\t",' ');
if(count($_POST) > 0){
if(isset($_POST['csrf']))
unset($_POST['csrf']);
if(isset($_POST['f_pass']))
unset($_POST['f_pass']);
if(isset($_POST['password']))
$_POST['password'] = 'strlen() = '.strlen($_POST['password']);
if(isset($_POST['password2']))
$_POST['password2'] = 'strlen() = '.strlen($_POST['password2']).', pass==pass2: '.($_POST['password2'] == $_POST['password']?'tak':'nie');
$message .= '??'.http_build_query($_POST);
}
if (isset($additional['exception'])){
if($additional['exception'] instanceof HTTP_Exception_301)
return false;
if($additional['exception'] instanceof HTTP_Exception_302)
return false;
if($additional['exception'] instanceof ORM_Validation_Exception){
$message .= "\n".print_r($additional['exception']->errors('models'),TRUE)."\n";
}
if($additional['exception'] instanceof GuzzleHttp\Exception\RequestException) {
parent::add(self::DEBUG, 'HTTP request error [ 1 ]: :url'."\r\n".':body',[':url'=>$additional['exception']->getRequest()->getUrl(), ':body'=>$additional['exception']->getRequest()->getBody()]);
}
if($additional['exception'] instanceof GuzzleHttp\Exception\BadResponseException) {
$_body = $additional['exception']->getResponse()->getBody();
parent::add(self::DEBUG, 'HTTP reponse error [ 2 ]: :err', [':err'=> $_body]);
}
}
return parent::add($level, $message, $values, $additional);
}
public function add_exception($e, $error_level = Log::ERROR){
return $this->add($error_level, Kohana_Exception::text($e), Array(),Array('exception'=>$e));
}
public static function catch_ex($ex, $error_level = Log::ERROR){
return Kohana::$log->add_exception($ex, $error_level);
}
public static function msg($msg, $error_level = Log::ERROR) {
return Kohana::$log->add($error_level, $msg);
}
}
Related
I am building out an interface to create a self-serve way for people to manage their Cognito IDP, but am struggling to pull through the actual data. I know I've got the foundations here but there's something I'm missing. I really need a little direction to help me get back on track.
Where it's failing is in the onPreSetData but I feel like I'm not 100% on all of the code, it's passing null through within the onPreSetData in my loginSettingsType on:
$cognitoAPIFacade = new CognitoAPIFacade();
To try work around this I was hard coding the variables but not going too well haha
it takes a bit of following but I'm certain I'm 99% there and just need a little help to get fully over the line
I'm not the most experienced coder so this is all new learning for me, any help you can provide is hugely appreciated.
I've tried adding comments to the code where it felt good to, I can clarify any areas as needed
My controller is below:
public function editAction(string $customerId, Admin $user, Request $request): Response
{
$customer = $this->validateCustomer($customerId, $user);
$twoFactorOmniLoginFacade = new TwoFactorOmniLoginFacade();
$cognitoAPIFacade = new CognitoAPIFacade();
$cognitoCustomerUserpoolIDP = new CognitoCustomerUserPool();
$formData = new LoginSettingsFormData($customer, $twoFactorOmniLoginFacade, $cognitoAPIFacade, $cognitoCustomerUserpoolIDP);
$form = $this->createForm(LoginSettingsType::class, $formData);
$form->handleRequest($request);
return $this->render('customer/login_settings.twig', [
'title' => $customer->getName(),
'javascript_action' => $request->attributes->get('_route'),
'form' => $form->createView(),
'page' => 'customer_login_settings',
] + $this->getTemplateParams($user, $customer));
}
/**
* #Route("", name="customer_login_settings_put", methods={"PUT"})
*
* #param Request $request
* #param Admin $user
* #param string $customerId
*
* #return Response
*
* #throws Exception
*/
public function putAction(Request $request, Admin $user, string $customerId): Response
{
$customer = $this->validateCustomer($customerId, $user);
$customerCognitoSettingsRepo = $this->getManager()->getRepository(CognitoCustomerUserPool::class);
$userPool = $customerCognitoSettingsRepo->getByCustomerID($customer->getCustomerUuid());
$twoFactorOmniLoginFacade = new TwoFactorOmniLoginFacade();
$cognitoAPIFacade = new CognitoAPIFacade();
$cognitoCustomerUserpoolIDP = new CognitoCustomerUserPool();
$formData = new LoginSettingsFormData($customer, $twoFactorOmniLoginFacade, $cognitoAPIFacade, $cognitoCustomerUserpoolIDP);
$form = $this->createForm(LoginSettingsType::class, $formData);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$formData->saveChanges($customer);
$this->addFlash('success', 'Changes saved successfully');
} else {
$this->addFlash('error', 'There was an error saving changes');
}
return $this->redirectToRoute('customer_login_settings_edit', ['customer_id' => $customerId]);
}
Login settings type:
public function buildForm(FormBuilderInterface $builder, array $options) {
---
form $builder adds
---
$builder->addEventListener(FormEvents::PRE_SET_DATA, [$this, 'onPreSetData']);
}
public function onPreSetData(FormEvent $event, LoggerInterface $logger): void
{
$twoFactorOmniLoginFacade = new TwoFactorOmniLoginFacade();
// Both of these below are returning empty when I use VSCode breakpoints
$cognitoAPIFacade = new CognitoAPIFacade();
$cognitoCustomerUserpoolIDP = new CognitoCustomerUserPool();
$formData = $event->getData();
$form = $event->getForm();
// TODO: I was working on this but unsure where to go from here
// $cognitoCustomerUserpoolIDP = $formData->getCognitoCustomerUserpoolIDP();
if (!$formData) {
return;
}
$idpsettings = $cognitoAPIFacade->getCognitoIDPSettings($logger, $cognitoCustomerUserpoolIDP);
$customer = $formData->getCustomer();
$form = $this->createForm(LoginSettingsType::class, $formData)
---
$Builder adds
---
}
Key script from Facade I made:
public function getCognitoIDPSettings(LoggerInterface $logger, string $cognitoCustomerUserpoolIDP): array
{
$body = json_encode([
'cognito_customer_user_pool_idp_id' => $cognitoCustomerUserpoolIDP,
]);
$url = COGNITO_API_INVOKE_URL . '/idp/get';
$response = $this->postFunction($logger, $url, $body, 'application/json');
$this->checkResponseStatus($response);
$body = json_decode($response->getContent(), true);
if (JSON_ERROR_NONE !== json_last_error()) {
throw new JsonException("failed decoding response from cognito api");
}
return $body;
}
I separated out my form data as it was quite large:
class LoginSettingsFormData
{
private $customer;
private $twoFactorOmniLoginFacade;
private $cognitoAPIFacade;
private $cognitoCustomerUserpoolIDP;
private $ignoreUsageDeactivate;
private $twoFactorLoginApplication;
private $whitelistedIps = [];
/**
* Constructor for the class.
*
* #param CustomerEntity $customer Customer Entity object
* #param TwoFactorOmniLoginFacade $twoFactorOmniLoginFacade Two-factor omni login facade object
* #param CognitoAPIFacade $cognitoAPIFacade Cognito API facade object
* #param CognitoCustomerUserPool $cognitoCustomerUserpoolIDP Cognito customer user pool object
*/
public function __construct(
CustomerEntity $customer,
TwoFactorOmniLoginFacade $twoFactorOmniLoginFacade,
CognitoAPIFacade $cognitoAPIFacade,
CognitoCustomerUserPool $cognitoCustomerUserpoolIDP)
{
$this->customer = $customer;
$this->twoFactorOmniLoginFacade = $twoFactorOmniLoginFacade;
$this->cognitoAPIFacade = $cognitoAPIFacade;
$this->cognitoCustomerUserpoolIDP = $cognitoCustomerUserpoolIDP;
$this->ignoreUsageDeactivate = $customer->getLoginSettings()->isIgnoreUsageDeactivate();
$this->twoFactorLoginApplication = CustomerSetting::findByCustomerUuid($customer->getId())->twoFactorLoginApplication;
$this->whitelistedIps = $customer->getLoginWhitelist();
}
/**
* Submit changes made to the loggin settings
*
* #param array $formData Array of form data
* #param LoggerInterface $logger Logger to log errors
*/
public function submit(array $formData, LoggerInterface $logger)
{
// Check if idp is set
if (null !== $this->idp && 'none' === $formData['idp_type']) {
// Delete the Cognito IDP settings with the provided logger and idp
$this->cognitoAPIFacade->deleteCognitoIDPSettings($logger, $this->idp);
// exit early
return;
}
if ('none' !== $formData['idp_type']) {
// Create a user pool with the provided form data and logger
// TODO:: consider handle exceptions?
$this->cognitoAPIFacade->createUserPool($logger, $formData);
}
}
/**
* Update the identity provider for the loggin settings
*
* #param CognitoAPIFacade $cognitoAPIFacade
* #param CognitoAPIFacade $userpool
* #param Logger $logger Logger to log errors
*/
public function updateIdentityProvider(CognitoAPIFacade $cognitoAPIFacade, $userpool, Logger $logger): void
{
// Assign the user pool value from the cognitoAPIFacade
$userPool = $cognitoAPIFacade->getUserPool();
// log error message if the user pool is null
if (null === $userPool) {
$logger->error("User pool is null, please check the configuration");
return;
}
// Store provider for quick reference
$idp = $userPool->getIdentityProvider();
// If the selected identityprovider is null
if (null === $idp) {
// Check if the form data specifies to set the identityprovider to "none"
if ($this->idpType !== 'none' && $this->active) {
// Otherwise, create a new identityprovider using the form data
$cognitoAPIFacade->createUserPool($logger, $this);
}
return;
}
// Check if the form data specifies to set the identityprovider to "none"
if ($this->idpType === 'none') {
// If so, delete the existing identityprovider settings
$cognitoAPIFacade->deleteCognitoIDPSettings($logger, $idp);
return;
}
// If the form data specifies a different identity provider than the current one
if ($idp->getCognitoIDPSettings() !== $this->idpType
&& ($this->idpType === "SAML" || $this->idpType === "Google")
&& $idp->getCognitoIDPSettings() === "none") {
// Delete the existing identityprovider settings and create a new user pool using the form data
$cognitoAPIFacade->deleteCognitoIDPSettings($logger, $idp);
$cognitoAPIFacade->createUserPool($logger, $this);
}
}
/**
* Save changes made to the object to the database
*
* #return bool Returns true if the changes were saved successfully, false otherwise
*/
public function saveChanges(CustomerEntity $customer)
{
// Set the ignore usage deactivate flag in the customer's login settings
$this->customer->getLoginSettings()->setIgnoreUsageDeactivate($this->ignoreUsageDeactivate);
// Clear the current whitelist of IP addresses for the customer's login settings
$this->customer->getLoginWhitelist()->clear();
// Add each IP address to the whitelist in the customer's login settings
foreach ($this->whitelistedIps as $ip) {
$this->customer->getLoginWhitelist()->add($ip);
}
// Save the changes to the customer object
$this->customer->save();
}
/**
* Set whether to ignore usage deactivation
*
* #param bool $ignoreUsageDeactivate true to ignore usage deactivation, false otherwise
*/
public function setIgnoreUsageDeactivate(bool $ignoreUsageDeactivate)
{
$this->ignoreUsageDeactivate = $ignoreUsageDeactivate;
}
/**
* Bool check whether usage deactivation is ignored
*
* #return bool true if usage deactivation is ignored, false otherwise
*/
public function isIgnoreUsageDeactivate(): bool
{
return $this->ignoreUsageDeactivate;
}
/**
* Set the two factor login application
*
* #param mixed $twoFactorLoginApplication the two factor login application
*/
public function setTwoFactorLoginApplication($twoFactorLoginApplication)
{
$this->twoFactorLoginApplication = $twoFactorLoginApplication;
}
/**
* Get the two factor login application
*
* #return mixed the two factor login application
*/
public function getTwoFactorLoginApplication()
{
return $this->twoFactorLoginApplication;
}
/**
* Set the customer entity
*
* #param CustomerEntity $customer the customer entity
*/
public function setCustomer(CustomerEntity $customer)
{
$this->customer = $customer;
}
/**
* Get the customer entity
*
* #return CustomerEntity the customer entity
*/
public function getCustomer(): CustomerEntity
{
return $this->customer;
}
/**
* Set the whitelisted IP addresses
*
* #param array $whitelistedIps the array of whitelisted IP addresses
*/
public function setWhitelistedIps(array $whitelistedIps)
{
$this->whitelistedIps = $whitelistedIps;
}
/**
* Get the array of whitelisted IP addresses
*
* #return array the array of whitelisted IP addresses
*/
public function getWhitelistedIps(): array
{
return $this->whitelistedIps;
}
/**
* Get the two factor applications
*
* #return mixed the two factor applications
*/
public function getTwoFactorApplications()
{
return $this->twoFactorOmniLoginFacade->findTwoFactorApplications($this->customer);
}
/**
* Get the two factor login setting
*
* #return mixed the two factor login setting
*/
public function getTwoFactorLoginSetting()
{
return $this->twoFactorOmniLoginFacade->findTwoFactorLoginSetting($this->customer->getId());
}
/**
* Get the Cognito IDP settings
*
* #return mixed the Cognito IDP settings
*/
public function getCognitoIDPSettings()
{
return $this->cognitoAPIFacade->getCognitoIDPSettings($this->cognitoCustomerUserpoolIDP);
}
I’m creating an authentication / login system using Slim 3 PHP on the back-end and Angular on the front-end and I’m trying to understand the ‘domain object’ and ‘data mapper’ part of a model layer within an MVC structure. I’ve read a lot of useful answers on various questions such as this, from which I understand the model should be comprised of ‘domain objects’, ‘data mappers’ and ‘services’.
However I’m not exactly sure what how this should be structured in the context of a user being able to register and log in to a website.
From my understanding I could have a user 'domain object' that has properties such as username and password. It could also have methods such as register or log in to represent business logic.
Would I then have a service class that creates a new instance of a user object, in which I would pass the form data into the object? So now my user object instance would have set username and password values?
Now i'm not sure how this objects property data would be inserted into the database. Would I use the user objects register method to insert the data into the database by passing in the username and password as parameters?
Apparently the service should be where the domain object and the data mapper interact, but i'm not sure how this would work if the register method is in the user domain object.
I was hoping someone could show me some code examples of what should be in the service class and how the interaction between the domain object and data mapper might work in the context of a user registering and logging in.
Note I don't want to use any frameworks, I want to try and implement a proper MVC structure manually as I feel i'd learn more.
So far I have this structure for registering a user:
I have an AuthenticationController with the method registerUser to allow a user to create an account:
class AuthenticationController
{
protected $authenticationService;
public function __construct(AuthenticationService $authenticationService)
{
$this->authenticationService = $authenticationService;
}
public function registerUser($request, $response)
{
$this->authenticationService->registerUser($request, $response);
}
}
I then have the AuthenticationService class with the registerUser method:
class AuthenticationService
{
protected $database;
public function __construct(PDO $database)
{
$this->database = $database;
}
public function registerUser ($request, $response)
{
$strings = $request→getParsedBody(); // will be sanitised / validated later
$username = $strings['username'];
$password = $strings['password'];
$email = "temp random email";
$stmt = $this->database->prepare("INSERT INTO users (email, username, password) values (:email, :username, :password)");
$stmt->bindParam(':email', $email);
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $password);
$stmt->execute();
}
}
Later on I intend to put the SQL into an AuthenticationRepository and the PDO logic into it’s own class. This AuthenticationService method will also make sure the user details are sanitised using PHP’s built in functions.
I’m not sure if the proposed PDO database class or AuthenticationRepository would count as a data mapper or not.
The registration would be performed by the service.
The service could "directly" use a data mapper, in order to "transfer" the entity to/from the database. Though, additionally, a repository can be implemented. The service would see it and communicate with it as with a collection of one or more entities.
Since a service is part of the model layer (domain model), it should know nothing about any request or response objects. The controller should extract the needed values from the request and pass them as arguments to the service methods. A response can be sent back by the controller, or the view, depending on which MVC variation you are trying to implement.
You say "I intend to put the [...] PDO logic into it's own class". You really don't need to implement a wrapper for the PDO extension.
Here a registration example. I didn't test it at all. For more details see the resources list at the end of this answer. Maybe begin with the last one, which - I just realized - is the answer to a question of yours.
Used file system structure:
a) Extended "MyApp/UI":
b) Extended "MyApp/Domain":
The controller:
<?php
namespace MyApp\UI\Web\Controller\Users;
use Psr\Http\Message\ServerRequestInterface;
use MyApp\Domain\Model\Users\Exception\InvalidData;
use MyApp\Domain\Service\Users\Exception\FailedRegistration;
use MyApp\Domain\Service\Users\Registration as RegistrationService;
class Registration {
private $registration;
public function __construct(RegistrationService $registration) {
$this->registration = $registration;
}
public function register(ServerRequestInterface $request) {
$username = $request->getParsedBody()['username'];
$password = $request->getParsedBody()['password'];
$email = $request->getParsedBody()['email'];
try {
$user = $this->registration->register($username, $password, $email);
} catch (InvalidData $exc) {
// Write the exception message to a flash messenger, for example,
// in order to be read and displayed by the specific view component.
var_dump($exc->getMessage());
} catch (FailedRegistration $exc) {
// Write the exception message to the flash messenger.
var_dump($exc->getMessage());
}
// In the view component, if no exception messages are found in the flash messenger, display a success message.
var_dump('Successfully registered.');
}
}
The service:
<?php
namespace MyApp\Domain\Service\Users;
use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
use MyApp\Domain\Service\Users\Exception\UserExists;
use MyApp\Domain\Model\Users\UserCollection as UserCollectionInterface;
class Registration {
/**
* User collection, e.g. user repository.
*
* #var UserCollectionInterface
*/
private $userCollection;
public function __construct(UserCollectionInterface $userCollection) {
$this->userCollection = $userCollection;
}
/**
* Register user.
*
* #param string $username Username.
* #param string $password Password.
* #param string $email Email.
* #return User User.
*/
public function register(string $username, string $password, string $email) {
$user = $this->createUser($username, $password, $email);
return $this->storeUser($user);
}
/**
* Create user.
*
* #param string $username Username.
* #param string $password Password.
* #param string $email Email.
* #return User User.
*/
private function createUser(string $username, string $password, string $email) {
// Create the object values (containing specific validation).
$email = new Email($email);
$password = new Password($password);
// Create the entity (e.g. the domain object).
$user = new User();
$user->setUsername($username);
$user->setEmail($email);
$user->setPassword($password);
return $user;
}
/**
* Store user.
*
* #param User $user User.
* #return User User.
*/
private function storeUser(User $user) {
// Check if user already exists.
if ($this->userCollection->exists($user)) {
throw new UserExists();
}
return $this->userCollection->store($user);
}
}
The exception thrown when trying to register an already existing user:
<?php
namespace MyApp\Domain\Service\Users\Exception;
use MyApp\Domain\Service\Users\Exception\FailedRegistration;
class UserExists extends FailedRegistration {
public function __construct(\Exception $previous = null) {
$message = 'User already exists.';
$code = 123;
parent::__construct($message, $code, $previous);
}
}
<?php
namespace MyApp\Domain\Service\Users\Exception;
abstract class FailedRegistration extends \Exception {
public function __construct(string $message, int $code = 0, \Exception $previous = null) {
$message = 'Registration failed: ' . $message;
parent::__construct($message, $code, $previous);
}
}
The domain object (entity):
<?php
namespace MyApp\Domain\Model\Users;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
/**
* User entity (e.g. domain object).
*/
class User {
private $id;
private $username;
private $email;
private $password;
public function getId() {
return $this->id;
}
public function setId(int id) {
$this->id = $id;
return $this;
}
public function getUsername() {
return $this->username;
}
public function setUsername(string $username) {
$this->username = $username;
return $this;
}
public function getEmail() {
return $this->email;
}
public function setEmail(Email $email) {
$this->email = $email;
return $this;
}
public function getPassword() {
return $this->password;
}
public function setPassword(Password $password) {
$this->password = $password;
return $this;
}
}
The value objects used by the entity:
<?php
namespace MyApp\Domain\Model\Users;
use MyApp\Domain\Model\Users\Exception\InvalidEmail;
/**
* Email object value.
*/
class Email {
private $email;
public function __construct(string $email) {
if (!$this->isValid($email)) {
throw new InvalidEmail();
}
$this->email = $email;
}
private function isValid(string $email) {
return (isEmpty($email) || !isWellFormed($email)) ? false : true;
}
private function isEmpty(string $email) {
return empty($email) ? true : false;
}
private function isWellFormed(string $email) {
return !filter_var($email, FILTER_VALIDATE_EMAIL) ? false : true;
}
public function __toString() {
return $this->email;
}
}
<?php
namespace MyApp\Domain\Model\Users;
use MyApp\Domain\Model\Users\Exception\InvalidPassword;
/**
* Password object value.
*/
class Password {
private const MIN_LENGTH = 8;
private $password;
public function __construct(string $password) {
if (!$this->isValid($password)) {
throw new InvalidPassword();
}
$this->password = $password;
}
private function isValid(string $password) {
return (isEmpty($password) || isTooShort($password)) ? false : true;
}
private function isEmpty(string $password) {
return empty($password) ? true : false;
}
private function isTooShort(string $password) {
return strlen($password) < self::MIN_LENGTH ? true : false;
}
public function __toString() {
return $this->password;
}
}
The exceptions thrown by the value objects:
<?php
namespace MyApp\Domain\Model\Users\Exception;
use MyApp\Domain\Model\Users\Exception\InvalidData;
class InvalidEmail extends InvalidData {
public function __construct(\Exception $previous = null) {
$message = 'The email address is not valid.';
$code = 123402;
parent::__construct($message, $code, $previous);
}
}
<?php
namespace MyApp\Domain\Model\Users\Exception;
use MyApp\Domain\Model\Users\Exception\InvalidData;
class InvalidPassword extends InvalidData {
public function __construct(\Exception $previous = null) {
$message = 'The password is not valid.';
$code = 123401;
parent::__construct($message, $code, $previous);
}
}
<?php
namespace MyApp\Domain\Model\Users\Exception;
abstract class InvalidData extends \LogicException {
public function __construct(string $message, int $code = 0, \Exception $previous = null) {
$message = 'Invalid data: ' . $message;
parent::__construct($message, $code, $previous);
}
}
The repository interface:
<?php
namespace MyApp\Domain\Model\Users;
use MyApp\Domain\Model\Users\User;
/**
* User collection, e.g. user repository.
*/
interface UserCollection {
/**
* Find a user by id.
*
* #param int $id User id.
* #return User|null User.
*/
public function findById(int $id);
/**
* Find all users.
*
* #return User[] User list.
*/
public function findAll();
/**
* Check if the given user exists.
*
* #param User $user User
* #return bool True if user exists, false otherwise.
*/
public function exists(User $user);
/**
* Store a user.
*
* #param User $user User
* #return User User.
*/
public function store(User $user);
}
The repository:
<?php
namespace MyApp\Domain\Infrastructure\Repository\Users;
use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Infrastructure\Mapper\Users\UserMapper;
use MyApp\Domain\Model\Users\UserCollection as UserCollectionInterface;
/**
* User collection, e.g. user repository.
*/
class UserCollection implements UserCollectionInterface {
private $userMapper;
public function __construct(UserMapper $userMapper) {
$this->userMapper = $userMapper;
}
/**
* Find a user by id.
*
* #param int $id User id.
* #return User|null User.
*/
public function findById(int $id) {
return $this->userMapper->fetchUserById($id);
}
/**
* Find all users.
*
* #return User[] User list.
*/
public function findAll() {
return $this->userMapper->fetchAllUsers();
}
/**
* Check if the given user exists.
*
* #param User $user User
* #return bool True if user exists, false otherwise.
*/
public function exists(User $user) {
return $this->userMapper->userExists($user);
}
/**
* Store a user.
*
* #param User $user User
* #return User User.
*/
public function store(User $user) {
return $this->userMapper->saveUser($user);
}
}
The data mapper interface:
<?php
namespace MyApp\Domain\Infrastructure\Mapper\Users;
use MyApp\Domain\Model\Users\User;
/**
* User mapper.
*/
interface UserMapper {
/**
* Fetch a user by id.
*
* #param int $id User id.
* #return User|null User.
*/
public function fetchUserById(int $id);
/**
* Fetch all users.
*
* #return User[] User list.
*/
public function fetchAllUsers();
/**
* Check if the given user exists.
*
* #param User $user User.
* #return bool True if the user exists, false otherwise.
*/
public function userExists(User $user);
/**
* Save a user.
*
* #param User $user User.
* #return User User.
*/
public function saveUser(User $user);
}
The data mapper:
<?php
namespace MyApp\Domain\Infrastructure\Mapper\Users;
use PDO;
use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
use MyApp\Domain\Infrastructure\Mapper\Users\UserMapper;
/**
* PDO user mapper.
*/
class PdoUserMapper implements UserMapper {
/**
* Database connection.
*
* #var PDO
*/
private $connection;
public function __construct(PDO $connection) {
$this->connection = $connection;
}
/**
* Fetch a user by id.
*
* Note: PDOStatement::fetch returns FALSE if no record is found.
*
* #param int $id User id.
* #return User|null User.
*/
public function fetchUserById(int $id) {
$sql = 'SELECT * FROM users WHERE id = :id LIMIT 1';
$statement = $this->connection->prepare($sql);
$statement->execute([
'id' => $id,
]);
$record = $statement->fetch(PDO::FETCH_ASSOC);
return ($record === false) ? null : $this->convertRecordToUser($record);
}
/**
* Fetch all users.
*
* #return User[] User list.
*/
public function fetchAllUsers() {
$sql = 'SELECT * FROM users';
$statement = $this->connection->prepare($sql);
$statement->execute();
$recordset = $statement->fetchAll(PDO::FETCH_ASSOC);
return $this->convertRecordsetToUserList($recordset);
}
/**
* Check if the given user exists.
*
* Note: PDOStatement::fetch returns FALSE if no record is found.
*
* #param User $user User.
* #return bool True if the user exists, false otherwise.
*/
public function userExists(User $user) {
$sql = 'SELECT COUNT(*) as cnt FROM users WHERE username = :username';
$statement = $this->connection->prepare($sql);
$statement->execute([
':username' => $user->getUsername(),
]);
$record = $statement->fetch(PDO::FETCH_ASSOC);
return ($record['cnt'] > 0) ? true : false;
}
/**
* Save a user.
*
* #param User $user User.
* #return User User.
*/
public function saveUser(User $user) {
$id = $user->getId();
if (!isset($id)) {
return $this->insertUser($user);
}
return $this->updateUser($user);
}
/**
* Insert a user.
*
* #param User $user User.
* #return User User.
*/
private function insertUser(User $user) {
$sql = 'INSERT INTO users (
username,
password,
email
) VALUES (
:username,
:password,
:email
)';
$statement = $this->connection->prepare($sql);
$statement->execute([
':username' => $user->getUsername(),
':password' => (string) $user->getPassword(),
':email' => (string) $user->getEmail(),
]);
$user->setId($this->connection->lastInsertId());
return $user;
}
/**
* Update a user.
*
* #param User $user User.
* #return User User.
*/
private function updateUser(User $user) {
$sql = 'UPDATE users
SET
username = :username,
password = :password,
email = :email
WHERE id = :id';
$statement = $this->connection->prepare($sql);
$statement->execute([
':id' => $user->getId(),
':username' => $user->getUsername(),
':password' => (string) $user->getPassword(),
':email' => (string) $user->getEmail(),
]);
return $user;
}
/**
* Convert a record to a user.
*
* #param array $record Record data.
* #return User User.
*/
private function convertRecordToUser(array $record) {
$user = $this->createUser(
$record['id'],
$record['username'],
$record['password'],
$record['email']
);
return $user;
}
/**
* Convert a recordset to a list of users.
*
* #param array $recordset Recordset data.
* #return User[] User list.
*/
private function convertRecordsetToUserList(array $recordset) {
$users = [];
foreach ($recordset as $record) {
$users[] = $this->convertRecordToUser($record);
}
return $users;
}
/**
* Create user.
*
* #param int $id User id.
* #param string $username Username.
* #param string $password Password.
* #param string $email Email.
* #return User User.
*/
private function createUser(int $id, string $username, string $password, string $email) {
$user = new User();
$user
->setId($id)
->setUsername($username)
->setPassword(new Password($password))
->setEmail(new Email($email))
;
return $user;
}
}
Resources:
Keynote: Architecture the Lost Years
Sandro Mancuso : An introduction to interaction-driven design
Unbreakable Domain Models
An older answer of mine, for some explanations.
I try to login in my websites using Google API, in local it's working good.
but on server it's given error. error is -
"Got An Error - Hybridauth Library not compatible with installed PECL
OAuth extension. Please disable it."
I had go for get right answer, do r&d and finally I solve my issue.
for it we need to update auth.php (lib. file)
<?php
/**
* HybridAuth
* http://hybridauth.sourceforge.net | http://github.com/hybridauth/hybridauth
* (c) 2009-2015, HybridAuth authors | http://hybridauth.sourceforge.net/licenses.html
*/
/**
* Hybrid_Auth class
*
* Hybrid_Auth class provide a simple way to authenticate users via OpenID and OAuth.
*
* Generally, Hybrid_Auth is the only class you should instanciate and use throughout your application.
*/
class Hybrid_Auth {
public static $version = "2.7.0-dev";
/**
* Configuration array
* #var array
*/
public static $config = array();
/**
* Auth cache
* #var Hybrid_Storage
*/
public static $store = null;
/**
* Error pool
* #var Hybrid_Error
*/
public static $error = null;
/**
* Logger
* #var Hybrid_Logger
*/
public static $logger = null;
/**
* Try to start a new session of none then initialize Hybrid_Auth
*
* Hybrid_Auth constructor will require either a valid config array or
* a path for a configuration file as parameter. To know more please
* refer to the Configuration section:
* http://hybridauth.sourceforge.net/userguide/Configuration.html
*
* #param array $config Configuration array or path to a configratuion file
*/
function __construct($config) {
Hybrid_Auth::initialize($config);
}
/**
* Try to initialize Hybrid_Auth with given $config hash or file
*
* #param array $config Configuration array or path to a configratuion file
* #return void
* #throws Exception
*/
public static function initialize($config) {
if (!is_array($config) && !file_exists($config)) {
throw new Exception("Hybriauth config does not exist on the given path.", 1);
}
if (!is_array($config)) {
$config = include $config;
}
// build some need'd paths
$config["path_base"] = realpath(dirname(__FILE__)) . "/";
$config["path_libraries"] = $config["path_base"] . "thirdparty/";
$config["path_resources"] = $config["path_base"] . "resources/";
$config["path_providers"] = $config["path_base"] . "Providers/";
// reset debug mode
if (!isset($config["debug_mode"])) {
$config["debug_mode"] = false;
$config["debug_file"] = null;
}
# load hybridauth required files, a autoload is on the way...
require_once $config["path_base"] . "Error.php";
//require_once $config["path_base"] . "Exception.php";
require_once $config["path_base"] . "Logger.php";
require_once $config["path_base"] . "Provider_Adapter.php";
require_once $config["path_base"] . "Provider_Model.php";
require_once $config["path_base"] . "Provider_Model_OpenID.php";
require_once $config["path_base"] . "Provider_Model_OAuth1.php";
require_once $config["path_base"] . "Provider_Model_OAuth2.php";
require_once $config["path_base"] . "User.php";
require_once $config["path_base"] . "User_Profile.php";
require_once $config["path_base"] . "User_Contact.php";
require_once $config["path_base"] . "User_Activity.php";
if (!class_exists("Hybrid_Storage", false)) {
require_once $config["path_base"] . "Storage.php";
}
// hash given config
Hybrid_Auth::$config = $config;
// instance of log mng
Hybrid_Auth::$logger = new Hybrid_Logger();
// instance of errors mng
Hybrid_Auth::$error = new Hybrid_Error();
// start session storage mng
Hybrid_Auth::$store = new Hybrid_Storage();
Hybrid_Logger::info("Enter Hybrid_Auth::initialize()");
Hybrid_Logger::info("Hybrid_Auth::initialize(). PHP version: " . PHP_VERSION);
Hybrid_Logger::info("Hybrid_Auth::initialize(). Hybrid_Auth version: " . Hybrid_Auth::$version);
Hybrid_Logger::info("Hybrid_Auth::initialize(). Hybrid_Auth called from: " . Hybrid_Auth::getCurrentUrl());
// PHP Curl extension [http://www.php.net/manual/en/intro.curl.php]
if (!function_exists('curl_init')) {
Hybrid_Logger::error('Hybridauth Library needs the CURL PHP extension.');
throw new Exception('Hybridauth Library needs the CURL PHP extension.');
}
// PHP JSON extension [http://php.net/manual/en/book.json.php]
if (!function_exists('json_decode')) {
Hybrid_Logger::error('Hybridauth Library needs the JSON PHP extension.');
throw new Exception('Hybridauth Library needs the JSON PHP extension.');
}
// session.name
if (session_name() != "PHPSESSID") {
Hybrid_Logger::info('PHP session.name diff from default PHPSESSID. http://php.net/manual/en/session.configuration.php#ini.session.name.');
}
// safe_mode is on
if (ini_get('safe_mode')) {
Hybrid_Logger::info('PHP safe_mode is on. http://php.net/safe-mode.');
}
// open basedir is on
if (ini_get('open_basedir')) {
Hybrid_Logger::info('PHP open_basedir is on. http://php.net/open-basedir.');
}
Hybrid_Logger::debug("Hybrid_Auth initialize. dump used config: ", serialize($config));
Hybrid_Logger::debug("Hybrid_Auth initialize. dump current session: ", Hybrid_Auth::storage()->getSessionData());
Hybrid_Logger::info("Hybrid_Auth initialize: check if any error is stored on the endpoint...");
if (Hybrid_Error::hasError()) {
$m = Hybrid_Error::getErrorMessage();
$c = Hybrid_Error::getErrorCode();
$p = Hybrid_Error::getErrorPrevious();
Hybrid_Logger::error("Hybrid_Auth initialize: A stored Error found, Throw an new Exception and delete it from the store: Error#$c, '$m'");
Hybrid_Error::clearError();
// try to provide the previous if any
// Exception::getPrevious (PHP 5 >= 5.3.0) http://php.net/manual/en/exception.getprevious.php
if (version_compare(PHP_VERSION, '5.3.0', '>=') && ($p instanceof Exception)) {
throw new Exception($m, $c, $p);
} else {
throw new Exception($m, $c);
}
}
Hybrid_Logger::info("Hybrid_Auth initialize: no error found. initialization succeed.");
}
/**
* Hybrid storage system accessor
*
* Users sessions are stored using HybridAuth storage system ( HybridAuth 2.0 handle PHP Session only) and can be accessed directly by
* Hybrid_Auth::storage()->get($key) to retrieves the data for the given key, or calling
* Hybrid_Auth::storage()->set($key, $value) to store the key => $value set.
*
* #return Hybrid_Storage
*/
public static function storage() {
return Hybrid_Auth::$store;
}
/**
* Get hybridauth session data
* #return string|null
*/
function getSessionData() {
return Hybrid_Auth::storage()->getSessionData();
}
/**
* Restore hybridauth session data
*
* #param string $sessiondata Serialized session data
* #retun void
*/
function restoreSessionData($sessiondata = null) {
Hybrid_Auth::storage()->restoreSessionData($sessiondata);
}
/**
* Try to authenticate the user with a given provider.
*
* If the user is already connected we just return and instance of provider adapter,
* ELSE, try to authenticate and authorize the user with the provider.
*
* $params is generally an array with required info in order for this provider and HybridAuth to work,
* like :
* hauth_return_to: URL to call back after authentication is done
* openid_identifier: The OpenID identity provider identifier
* google_service: can be "Users" for Google user accounts service or "Apps" for Google hosted Apps
*
* #param string $providerId ID of the provider
* #param array $params Params
* #return
*/
public static function authenticate($providerId, $params = null) {
Hybrid_Logger::info("Enter Hybrid_Auth::authenticate( $providerId )");
if (!Hybrid_Auth::storage()->get("hauth_session.$providerId.is_logged_in")) {
// if user not connected to $providerId then try setup a new adapter and start the login process for this provider
Hybrid_Logger::info("Hybrid_Auth::authenticate( $providerId ), User not connected to the provider. Try to authenticate..");
$provider_adapter = Hybrid_Auth::setup($providerId, $params);
$provider_adapter->login();
} else {
// else, then return the adapter instance for the given provider
Hybrid_Logger::info("Hybrid_Auth::authenticate( $providerId ), User is already connected to this provider. Return the adapter instance.");
return Hybrid_Auth::getAdapter($providerId);
}
}
/**
* Return the adapter instance for an authenticated provider
*
* #param string $providerId ID of the provider
* #return Hybrid_Provider_Adapter
*/
public static function getAdapter($providerId = null) {
Hybrid_Logger::info("Enter Hybrid_Auth::getAdapter( $providerId )");
return Hybrid_Auth::setup($providerId);
}
/**
* Setup an adapter for a given provider
*
* #param string $providerId ID of the provider
* #param array $params Adapter params
* #return Hybrid_Provider_Adapter
*/
public static function setup($providerId, $params = null) {
Hybrid_Logger::debug("Enter Hybrid_Auth::setup( $providerId )", $params);
if (!$params) {
$params = Hybrid_Auth::storage()->get("hauth_session.$providerId.id_provider_params");
Hybrid_Logger::debug("Hybrid_Auth::setup( $providerId ), no params given. Trying to get the stored for this provider.", $params);
}
if (!$params) {
$params = array();
Hybrid_Logger::info("Hybrid_Auth::setup( $providerId ), no stored params found for this provider. Initialize a new one for new session");
}
if (is_array($params) && !isset($params["hauth_return_to"])) {
$params["hauth_return_to"] = Hybrid_Auth::getCurrentUrl();
Hybrid_Logger::debug("Hybrid_Auth::setup( $providerId ). HybridAuth Callback URL set to: ", $params["hauth_return_to"]);
}
# instantiate a new IDProvider Adapter
$provider = new Hybrid_Provider_Adapter();
$provider->factory($providerId, $params);
return $provider;
}
/**
* Check if the current user is connected to a given provider
*
* #param string $providerId ID of the provider
* #return bool
*/
public static function isConnectedWith($providerId) {
return (bool) Hybrid_Auth::storage()->get("hauth_session.{$providerId}.is_logged_in");
}
/**
* Return array listing all authenticated providers
* #return array
*/
public static function getConnectedProviders() {
$idps = array();
foreach (Hybrid_Auth::$config["providers"] as $idpid => $params) {
if (Hybrid_Auth::isConnectedWith($idpid)) {
$idps[] = $idpid;
}
}
return $idps;
}
/**
* Return array listing all enabled providers as well as a flag if you are connected
*
* <code>
* array(
* 'Facebook' => array(
* 'connected' => true
* )
* )
* </code>
* #return array
*/
public static function getProviders() {
$idps = array();
foreach (Hybrid_Auth::$config["providers"] as $idpid => $params) {
if ($params['enabled']) {
$idps[$idpid] = array('connected' => false);
if (Hybrid_Auth::isConnectedWith($idpid)) {
$idps[$idpid]['connected'] = true;
}
}
}
return $idps;
}
/**
* A generic function to logout all connected provider at once
* #return void
*/
public static function logoutAllProviders() {
$idps = Hybrid_Auth::getConnectedProviders();
foreach ($idps as $idp) {
$adapter = Hybrid_Auth::getAdapter($idp);
$adapter->logout();
}
}
/**
* Utility function, redirect to a given URL with php header or using javascript location.href
*
* #param string $url URL to redirect to
* #param string $mode PHP|JS
*/
public static function redirect($url, $mode = "PHP") {
Hybrid_Logger::info("Enter Hybrid_Auth::redirect( $url, $mode )");
// Ensure session is saved before sending response, see https://github.com/symfony/symfony/pull/12341
if ((PHP_VERSION_ID >= 50400 && PHP_SESSION_ACTIVE === session_status()) || (PHP_VERSION_ID < 50400 && isset($_SESSION) && session_id())) {
session_write_close();
}
if ($mode == "PHP") {
header("Location: $url");
} elseif ($mode == "JS") {
echo '<html>';
echo '<head>';
echo '<script type="text/javascript">';
echo 'function redirect(){ window.top.location.href="' . $url . '"; }';
echo '</script>';
echo '</head>';
echo '<body onload="redirect()">';
echo 'Redirecting, please wait...';
echo '</body>';
echo '</html>';
}
die();
}
/**
* Utility function, return the current url
*
* #param bool $request_uri true to get $_SERVER['REQUEST_URI'], false for $_SERVER['PHP_SELF']
* #return string
*/
public static function getCurrentUrl($request_uri = true) {
if (php_sapi_name() == 'cli') {
return '';
}
$protocol = 'http://';
if ((isset($_SERVER['HTTPS']) && ( $_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1 ))
|| (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'))
{
$protocol = 'https://';
}
$url = $protocol . $_SERVER['HTTP_HOST'];
if ($request_uri) {
$url .= $_SERVER['REQUEST_URI'];
} else {
$url .= $_SERVER['PHP_SELF'];
}
// return current url
return $url;
}
}
I need to add Captcha to my login page, I am using GregwarCaptchaBundle and FosUserBundle.
For the moment I have get show the captcha on the login using the following code:
<?php
/*
* This file is part of the FOSUserBundle package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\UserBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Gregwar\Captcha\CaptchaBuilder;
class SecurityController extends Controller
{
public function loginAction(Request $request)
{
$builtCaptcha = new CaptchaBuilder();
$builtCaptcha->build();
$builtCaptcha->save('captcha.jpg');
/** #var $session \Symfony\Component\HttpFoundation\Session\Session */
$session = $request->getSession();
if (class_exists('\Symfony\Component\Security\Core\Security')) {
$authErrorKey = Security::AUTHENTICATION_ERROR;
$lastUsernameKey = Security::LAST_USERNAME;
} else {
// BC for SF < 2.6
$authErrorKey = SecurityContextInterface::AUTHENTICATION_ERROR;
$lastUsernameKey = SecurityContextInterface::LAST_USERNAME;
}
// get the error if any (works with forward and redirect -- see below)
if ($request->attributes->has($authErrorKey)) {
$error = $request->attributes->get($authErrorKey);
} elseif (null !== $session && $session->has($authErrorKey)) {
$error = $session->get($authErrorKey);
$session->remove($authErrorKey);
} else {
$error = null;
}
if (!$error instanceof AuthenticationException) {
$error = null; // The value does not come from the security component.
}
// last username entered by the user
$lastUsername = (null === $session) ? '' : $session->get($lastUsernameKey);
if ($this->has('security.csrf.token_manager')) {
$csrfToken = $this->get('security.csrf.token_manager')->getToken('authenticate')->getValue();
} else {
// BC for SF < 2.4
$csrfToken = $this->has('form.csrf_provider')
? $this->get('form.csrf_provider')->generateCsrfToken('authenticate')
: null;
}
$t = $request->get('_captcha');
if($t!=$builtCaptcha){
echo 'error';
}
var_dump($t);
return $this->renderLogin(array(
'last_username' => $lastUsername,
'error' => $error,
'csrf_token' => $csrfToken,
'captcha' => $builtCaptcha,
));
}
/**
* Renders the login template with the given parameters. Overwrite this function in
* an extended controller to provide additional data for the login template.
*
* #param array $data
*
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function renderLogin(array $data)
{
return $this->render('FOSUserBundle:Security:login.html.twig', $data);
}
public function checkAction($builtCaptcha)
{
return $this->redirect($this->generateUrl('fos_user_login'));
}
throw new \RuntimeException('You must configure the check path to be handled by the firewall using form_login in your security firewall configuration.');
}
public function logoutAction()
{
throw new \RuntimeException('You must activate the logout in your security firewall configuration.');
}
}
And I overrided the login and register template as the documentation explain (the registration is working fine with the captcha)
The problem is that I don't know how to validate the captcha's code.
I guess that I should do it into the checkAction()
But I am not sure, I am very caught with this problem.
If someone could help me I would be very grateful, Thanks in advance.
I had the same problem when trying to implement Google ReCaptcha into a simple login-form with database provider. This is a working solution based on a listener triggered by SecurityEvents::INTERACTIVE_LOGIN. This event is fired after verification of credentials but before redirecting to default_target_path defined in security.yml.
Note: The example is mixed with some other functionality, because I am also capturing failed login attempts.
<?php
namespace ExampleBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Core\AuthenticationEvents;
use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent;
use Symfony\Component\Security\Core\Event\AuthenticationEvent;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;
use ExampleBundle\Google\GoogleReCaptcha;
use Doctrine\ORM\EntityManager;
use ExampleBundle\Entity\User;
/**
* Class AuthenticationAttemptListener
* #package ExampleBundle\EventListener
*/
class AuthenticationAttemptListener implements EventSubscriberInterface
{
/**
* #var EntityManager
*/
private $entityManager;
/**
* #var GoogleReCaptcha
*/
private $googleReCaptcha;
/**
* #var integer
*/
private $maxFailedLoginAttempts;
/**
* AuthenticationAttemptListener constructor.
*
* #param EntityManager $entityManager
* #param GoogleReCaptcha $googleReCaptcha
* #param integer $maxFailedLoginAttempts
*/
public function __construct(EntityManager $entityManager, GoogleReCaptcha $googleReCaptcha, $maxFailedLoginAttempts)
{
$this->entityManager = $entityManager;
$this->googleReCaptcha = $googleReCaptcha;
$this->maxFailedLoginAttempts = $maxFailedLoginAttempts;
}
/**
* #return array
*/
public static function getSubscribedEvents()
{
return array(
AuthenticationEvents::AUTHENTICATION_FAILURE => 'onAuthenticationFailure',
AuthenticationEvents::AUTHENTICATION_SUCCESS => 'onAuthenticationSuccess',
SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin'
);
}
/**
* Count failed login attempts and save to database on existing usernames
*
* #param AuthenticationFailureEvent $event
*/
public function onAuthenticationFailure(AuthenticationFailureEvent $event)
{
if ($event->getAuthenticationException() instanceof BadCredentialsException) {
$databaseUser = $this->searchUserinDatabase($event);
// increase failed attempt counter or lock-up account
if ($databaseUser !== null) {
if (!$databaseUser->isEnabled()) {
throw new CustomUserMessageAuthenticationException('user_deactivated');
} else {
$databaseUser->increaseFailedLoginAttempts();
$databaseUser->setFailedLoginLastTimestamp(new \DateTime());
if ($databaseUser->getFailedLoginAttempts() == $this->maxFailedLoginAttempts) {
$databaseUser->setIsActive(0);
}
$this->entityManager->persist($databaseUser);
$this->entityManager->flush();
}
}
}
}
/**
* #param AuthenticationSuccessEvent $event
*/
public function onAuthenticationSuccess(AuthenticationEvent $event)
{
// Attention: Event will be thrown on every request if you have session-based authentication!
// Reset of attempt counter may occur if user is logged in while brute force attack is running
}
/**
* Check incoming google recaptcha token and reset attempt-counter on success or throw exception
*
* #param InteractiveLoginEvent $event
*/
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$reCaptchaResponse = $event->getRequest()->get('g-recaptcha-response');
$captchaRequest = $this->googleReCaptcha->checkReCaptcha($reCaptchaResponse);
if (is_array($captchaRequest) && $captchaRequest['success'] === true) {
// reset attempt counter because of successful login and positive recaptcha response
$databaseUser = $this->searchUserinDatabase($event);
if ($databaseUser !== null && $databaseUser->isEnabled()) {
$databaseUser->setFailedLoginAttempts(null);
$databaseUser->setFailedLoginLastTimestamp(null);
$this->entityManager->persist($databaseUser);
$this->entityManager->flush();
}
} else {
// on all other recaptcha related errors throw exception
throw new CustomUserMessageAuthenticationException('recaptcha_error');
}
}
/**
* Retrieve user from database
*
* #param AuthenticationFailureEvent|AuthenticationEvent $event
*
* #return User|null
*/
private function searchUserinDatabase($event)
{
$token = $event->getAuthenticationToken();
$username = $token->getUsername();
$databaseUser = null;
if (!$token instanceof AnonymousToken) {
// get user from database
$databaseUser = $this->entityManager->getRepository($entity)->findOneBy(array(
"username" => $username
));
}
return $databaseUser;
}
}
Hope that helps ...
I am looking for a way to extend our default logging class without making changes to the whole application or library. We have a number of places where we write logs. E.g:
App_Log::getInstance()->write(
$name,
$type,
"LOGOUT",
$url
);
Auth_Log
<?php
class App_Auth_Log {
/**
* Singleton instance
*
* Marked only as protected to allow extension of the class. To extend,
* simply override {#link getInstance()}.
*
* #var App_Auth_Log
*/
protected static $_instance = null;
/**
* Auth logging enabled flag.
*
* #var boolean
*/
protected $_enabled = false;
/**
* If flag is true then cleanup will not remove login records.
*
* #var boolean
*/
protected $_keepLoginRecords = false;
/**
* Class constructor.
*/
public function __construct() {
if(App_Front::getInstance()->hasParam("withAuthLog"))
$this->_enabled = true;
if(App_Front::getInstance()->hasParam("withKeepLoginRecords"))
$this->_keepLoginRecords = true;
$this->cleanup();
}
/**
* Singleton instance
*
* #return App_Auth_Log
*/
public static function getInstance() {
if (is_null(self::$_instance))
self::$_instance = new self();
return self::$_instance;
}
/**
* Write new auth log record with given details. if succesful then method
* returns true otherwise returns false.
*
* #param string $class
* #param string $ident
* #param string $action
* #param string $url
* #param string $ipaddr
* #return boolean
* #throws Exception
*/
public function write($class,$ident,$action,$url,$ipaddr=null) {
if($this->isEnabled()) {
$db = App_Db_Connections::getInstance()->getConnection();
try {
// if address not specificed get remote addr
$ipaddr = ($ipaddr == null) ? $_SERVER['REMOTE_ADDR'] : $ipaddr;
// manual insert so we can take advantage of insert delayed
$stmnt = "INSERT INTO accesslogs
VALUES('',NOW(),'$class','$ident','$action','$url','$ipaddr')";
// execute insert
$db->query($stmnt);
} catch (Exception $e) {
throw $e;
}
return true;
}
return false;
}
/**
* Cleanup old accesslog records. Cached to run once a day.
*
* #return boolean - returns true if run false if not.
*/
public function cleanup() {
$cache = App_Cache::getInstance()->newObject(86400);
if($this->isEnabled()) {
if (!$res = $cache->load(App_Cache::getCacheName(__CLASS__. "cleanup"))) {
// add cache
$db = App_Db_Connections::getInstance()->getConnection();
try {
$where = $db->quoteInto("DATEDIFF(NOW(),accesslog_datetime) > ?", 6);
$and = ($this->_keepLoginRecords) ? " AND accesslog_action != 'LOGIN'" : "";
$db->query("DELETE LOW_PRIORITY FROM accesslogs WHERE $where $and");
} catch (Exception $e) {
throw $e;
}
$cache->save($res,App_Cache::getCacheName(__CLASS__. "cleanup"));
} // end cache
}
return;
}
/**
* Returns boolean check if auth log enabled.
*
* #return boolean
*/
public function isEnabled() {
return ($this->_enabled) ? true : false;
}
/**
* Enabled disable the auth log process.
*
* #param boolean $boolean
* #return App_Auth_Log
*/
public function setEnabled($boolean) {
$this->_enabled = ($boolean) ? true : false;
return $this;
}
}
?>
This is the default behaviour of the core code. But for this specific project I need to be able to extend/overwrite the write method, e.g with extra parameters.
Q: How can I make changes to this App_Auth_Log class so that its backwards compatible with previous projects that call App_Log::getInstance()->write?
How I think it should work(but dont know how to do it).
If App_Front::getInstance()->hasParam("withAuthLog") passes a custom class name e.g: My_Custom_Auth_Log which overwrites the original write method. Just not sure how to modify the singleton part
You have no choice. You have to modify your App_Log code, because everything statically makes calls to it. For minimal changes, you could extend the App_Log class and make App_Log::getInstance return an instance of that child class; but that's pretty messy, since a parent should never know about its children.
Maybe you can prevent the default implementation of App_Log to be loaded and load a different implementation of it from a different file. That's pretty messy too though.
This is exactly (one of) the reason(s) why singletons and static calls are very frowned upon. See How Not To Kill Your Testability Using Statics.