Adding Captcha to Symfony2 Login - php

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 ...

Related

Dynamically display AWS Cognito in PHP & Twig

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);
}

SAML IDP in Laravel - Logging out

I have a Laravel application where users can access a third party application via my application (acting as an Identity Provider).
The logging in was achieved by taking inspiration from: https://www.lightsaml.com/LightSAML-Core/Cookbook/How-to-make-Response/
<?php
namespace App\Http\Controllers\Saml;
use App\Http\Controllers\Controller;
use CodeGreenCreative\SamlIdp\Traits\PerformsSingleSignOn;
class LoginController extends Controller
{
use PerformsSingleSignOn;
/**
* Initialise a new constructotr instance.
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('verified');
$this->middleware('investor.eligible');
$this->init();
}
/**
* Display the login form for accessing SAML
*
* #return void
*/
public function showLoginForm()
{
return view('user.custodian');
}
/**
* Attempt to log into a given Service Provider.
*
* #return void
*/
public function login()
{
$this->setDestination();
return $this->samlResponse();
}
/**
* Send a SAML message to the intended Service Provider with security assertions and other bearer assertions.
* In this case we're explicitly sending email and name id
*
* #link https://www.lightsaml.com/LightSAML-Core/Cookbook/How-to-make-Response/
*
* #return void
*/
public function samlResponse()
{
// Create a new SAML Response instance
$this->response = new \LightSaml\Model\Protocol\Response();
// Create a response object that we'll send later.
$this->response
->addAssertion($assertion = new \LightSaml\Model\Assertion\Assertion())
->setStatus(
new \LightSaml\Model\Protocol\Status(
new \LightSaml\Model\Protocol\StatusCode(
\LightSaml\SamlConstants::STATUS_SUCCESS
)
)
)
->setID(\LightSaml\Helper::generateID())
->setIssueInstant(new \DateTime())
->setDestination($this->destination)
->setIssuer(new \LightSaml\Model\Assertion\Issuer($this->issuer));
// Build out our Assertion instance
$assertion
->setId(\LightSaml\Helper::generateID())
->setIssueInstant(new \DateTime())
->setIssuer(new \LightSaml\Model\Assertion\Issuer($this->issuer))
->setSignature(new \LightSaml\Model\XmlDSig\SignatureWriter($this->certificate, $this->private_key))
->setSubject(
(new \LightSaml\Model\Assertion\Subject())
->setNameID(new \LightSaml\Model\Assertion\NameID(
auth()->user()->email,
\LightSaml\SamlConstants::NAME_ID_FORMAT_EMAIL
))
->addSubjectConfirmation(
(new \LightSaml\Model\Assertion\SubjectConfirmation())
->setMethod(\LightSaml\SamlConstants::CONFIRMATION_METHOD_BEARER)
->setSubjectConfirmationData(
(new \LightSaml\Model\Assertion\SubjectConfirmationData())
->setNotOnOrAfter(new \DateTime('+1 MINUTE'))
->setRecipient($this->destination)
)
)
)
->setConditions(
(new \LightSaml\Model\Assertion\Conditions())
->setNotBefore(new \DateTime())
->setNotOnOrAfter(new \DateTime('+1 MINUTE'))
->addItem(
new \LightSaml\Model\Assertion\AudienceRestriction([$this->destination])
)
)
->addItem(
(new \LightSaml\Model\Assertion\AttributeStatement())
->addAttribute(new \LightSaml\Model\Assertion\Attribute(
\LightSaml\ClaimTypes::EMAIL_ADDRESS,
auth()->user()->email,
))
->addAttribute(new \LightSaml\Model\Assertion\Attribute(
\LightSaml\ClaimTypes::NAME_ID,
auth()->user()->id,
))
)
->addItem(
(new \LightSaml\Model\Assertion\AuthnStatement())
->setAuthnInstant(new \DateTime('-10 MINUTE'))
->setSessionIndex(\LightSaml\Helper::generateID())
->setAuthnContext(
(new \LightSaml\Model\Assertion\AuthnContext())
->setAuthnContextClassRef(\LightSaml\SamlConstants::AUTHN_CONTEXT_WINDOWS)
)
);
return $this->sendSAMLResponse();
}
/**
* Send a SAML response to the Service Provider and display the end result to the user.
* If this is successful it should log the user into the SP.
*
* #param \LightSaml\Model\Protocol\Response $response
*
* #return void
*/
private function sendSAMLResponse()
{
$bindingFactory = new \LightSaml\Binding\BindingFactory();
$postBinding = $bindingFactory->create(\LightSaml\SamlConstants::BINDING_SAML2_HTTP_POST);
$messageContext = new \LightSaml\Context\Profile\MessageContext();
$messageContext->setMessage($this->response)->asResponse();
$httpResponse = $postBinding->send($messageContext);
return $httpResponse->getContent();
}
/**
* Set the destination.
*
* #return void
*/
private function setDestination()
{
$destination = config('samlidp.sp.aHR0cHM6Ly9zZXJ2aWNlcy11ay5zdW5nYXJkZHguY29tL1NpbmdsZVNpZ25Pbi9TZXJ2aWNlUHJvdmlkZXI=.destination');
$this->destination = $destination;
}
}
I run into a snag however when I try to perform a SAML Single Log Out from an SP.
The steps are as follows:
SP sends a logout request
IDP responds
So I have a logout controller similar to the above.
<?php
namespace App\Http\Controllers\Saml;
use App\Http\Controllers\Controller;
use App\Jobs\SamlSlo as JobsSamlSlo;
use CodeGreenCreative\SamlIdp\Traits\PerformsSingleSignOn;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use LightSaml\Helper;
use LightSaml\Model\Assertion\Issuer;
use LightSaml\Model\Assertion\NameID;
use LightSaml\Model\Context\DeserializationContext;
use LightSaml\Model\Protocol\LogoutRequest;
use LightSaml\SamlConstants;
use Log;
class LogoutController extends Controller
{
use PerformsSingleSignOn;
private $sp;
/**
* #param [type] $sp [description]
*/
public function __construct()
{
$this->sp = config('samlidp.sp.aHR0cHM6Ly9zZXJ2aWNlcy11ay5zdW5nYXJkZHguY29tL1NpbmdsZVNpZ25Pbi9TZXJ2aWNlUHJvdmlkZXI=');
$this->init();
}
public function index()
{
$this->setDestination();
$this->setDestination();
return redirect($this->request());
}
/**
* [request description]
* #return [type] [description]
*/
public function request()
{
$this->response = (new LogoutRequest)
->setIssuer(new Issuer($this->issuer))
->setNameID((new NameID(Helper::generateID(), SamlConstants::NAME_ID_FORMAT_TRANSIENT)))
->setID(Helper::generateID())
->setIssueInstant(new \DateTime)
->setDestination($this->destination);
return $this->send(SamlConstants::BINDING_SAML2_HTTP_REDIRECT);
}
private function setDestination()
{
$destination = $this->sp['logout'];
$parsed_url = parse_url($destination);
parse_str($parsed_url['query'] ?? '', $parsed_query_params);
$parsed_query_params['idp'] = config('app.url');
$this->destination = strtok($destination, '?') . '?' . http_build_query($parsed_query_params);
Log::info($this->destination);
}
}
The main issue is that in every example I've seen, the SPs have a dedicated logging out URL.
If I set the logout URL as a route within my IDP would this flow still work?

ZF3 Dependency injection in Module.php

I'm currently migrating a ZF2 application to ZF3.
Mostly everything is going smoothly but I'm stuck on one thing.
In my Module.php, I have an ACL management using zend-permissions-acl.
class Module
{
protected $defaultLang = 'fr';
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
if (!$e->getRequest() instanceof ConsoleRequest){
$eventManager->attach(MvcEvent::EVENT_RENDER_ERROR, array($this, 'onRenderError'));
$eventManager->attach(MvcEvent::EVENT_RENDER, array($this, 'onRender'));
$eventManager->attach(MvcEvent::EVENT_FINISH, array($this, 'onFinish'));
$this->initAcl($e);
$eventManager->attach('route', array($this, 'checkAcl'));
}
}
public function checkAcl(MvcEvent $e) {
$app = $e->getApplication();
$sm = $app->getServiceManager();
$route = $e -> getRouteMatch() -> getMatchedRouteName();
$authService = $sm->get('AuthenticationService');
$jwtService = $sm->get('JwtService');
$translator = $sm->get('translator');
$identity = null;
try {
$identity = $jwtService->getIdentity($e->getRequest());
} catch(\Firebase\JWT\ExpiredException $exception) {
$response = $e->getResponse();
$response->setStatusCode(401);
return $response;
}
if(is_null($identity) && $authService->hasIdentity()) { // no header being passed on... we try to use standard validation
$authService->setJwtMode(false);
$identity = $authService->getIdentity();
}
$userRole = 'default';
$translator->setLocale($this->defaultLang);
if(!is_null($identity))
{
$userRole = $identity->getType();
//check if client or prospect
if($userRole >= User::TYPE_CLIENT)
{
$userManagementRight = UserRight::CREATE_USERS;
if($identity->hasRight($userManagementRight))
$userRole = 'userManagement';
}
$translator->setLocale($identity->getLang());
}
if (!$e->getViewModel()->acl->isAllowed($userRole, null, $route)) {
$response = $e -> getResponse();
$response->setStatusCode(403);
return $response;
}
public function initAcl(MvcEvent $e) {
//here is list of routes allowed
}
}
My issue here is that I'm still using the getServiceManager and therefore getting the deprecated warning : Usage of Zend\ServiceManager\ServiceManager::getServiceLocator is deprecated since v3.0.0;
Basically, I just need to inject dependencies into Module.php.
I guess otherwise I would have to move the checkAcl to the Controller directly and inject the ACL in them ? Not sure what is the proper way of doing this.
Any feedback on this would be greatly appreciated.
Regards,
Robert
To solve the issue you should use a Listener class and Factory. It would also help you with more separation of concerns :)
You seem quite capable of figuring stuff out, judging by your code. As such, I'm just going to give you an example of my own, so you should fill yours in with your own code (I'm also a bit lazy and do not wish to rewrite everything when I can copy/paste my code in ;) )
In your module.config.php:
'listeners' => [
// Listing class here will automatically have them "activated" as listeners
ActiveSessionListener::class,
],
'service_manager' => [
'factories' => [
// The class (might need a) Factory
ActiveSessionListener::class => ActiveSessionListenerFactory::class,
],
],
The Factory
<?php
namespace User\Factory\Listener;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityManager;
use Interop\Container\ContainerInterface;
use User\Listener\ActiveSessionListener;
use Zend\Authentication\AuthenticationService;
use Zend\ServiceManager\Factory\FactoryInterface;
class ActiveSessionListenerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
/** #var ObjectManager $entityManager */
$entityManager = $container->get(EntityManager::class);
/** #var AuthenticationService $authenticationService */
$authenticationService = $container->get(AuthenticationService::class);
return new ActiveSessionListener($authenticationService, $entityManager);
}
}
The Listener
<?php
namespace User\Listener;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityManager;
use User\Entity\User;
use Zend\Authentication\AuthenticationService;
use Zend\EventManager\Event;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
use Zend\Mvc\MvcEvent;
/**
* Class ActiveSessionListener
*
* #package User\Listener
*
* Purpose of this class is to make sure that the identity of an active session becomes managed by the EntityManager.
* A User Entity must be in a managed state in the event of any changes to the Entity itself or in relations to/from it.
*/
class ActiveSessionListener implements ListenerAggregateInterface
{
/**
* #var AuthenticationService
*/
protected $authenticationService;
/**
* #var ObjectManager|EntityManager
*/
protected $objectManager;
/**
* #var array
*/
protected $listeners = [];
/**
* CreatedByUserListener constructor.
*
* #param AuthenticationService $authenticationService
* #param ObjectManager $objectManager
*/
public function __construct(AuthenticationService $authenticationService, ObjectManager $objectManager)
{
$this->setAuthenticationService($authenticationService);
$this->setObjectManager($objectManager);
}
/**
* #param EventManagerInterface $events
*/
public function detach(EventManagerInterface $events)
{
foreach ($this->listeners as $index => $listener) {
if ($events->detach($listener)) {
unset($this->listeners[$index]);
}
}
}
/**
* #param EventManagerInterface $events
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$events->attach(MvcEvent::EVENT_ROUTE, [$this, 'haveDoctrineManagerUser'], 1000);
}
/**
* #param Event $event
*
* #throws \Doctrine\Common\Persistence\Mapping\MappingException
* #throws \Doctrine\ORM\ORMException
*/
public function haveDoctrineManagerUser(Event $event)
{
if ($this->getAuthenticationService()->hasIdentity()) {
// Get current unmanaged (by Doctrine) session User
$identity = $this->getAuthenticationService()->getIdentity();
// Merge back into a managed state
$this->getObjectManager()->merge($identity);
$this->getObjectManager()->clear();
// Get the now managed Entity & replace the unmanaged session User by the managed User
$this->getAuthenticationService()->getStorage()->write(
$this->getObjectManager()->find(User::class, $identity->getId())
);
}
}
/**
* #return AuthenticationService
*/
public function getAuthenticationService() : AuthenticationService
{
return $this->authenticationService;
}
/**
* #param AuthenticationService $authenticationService
*
* #return ActiveSessionListener
*/
public function setAuthenticationService(AuthenticationService $authenticationService) : ActiveSessionListener
{
$this->authenticationService = $authenticationService;
return $this;
}
/**
* #return ObjectManager|EntityManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
/**
* #param ObjectManager|EntityManager $objectManager
*
* #return ActiveSessionListener
*/
public function setObjectManager($objectManager)
{
$this->objectManager = $objectManager;
return $this;
}
}
The important bits:
The Listener class must implement ListenerAggregateInterface
Must be activated in the listeners key of the module configuration
That's it really. You then have the basic building blocks for a Listener.
Apart from the attach function you could take the rest and make that into an abstract class if you'd like. Would save a few lines (read: duplicate code) with multiple Listeners.
NOTE: Above example uses the normal EventManager. With a simple change to the above code you could create "generic" listeners, by attaching them to the SharedEventManager, like so:
/**
* #param EventManagerInterface $events
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$sharedManager = $events->getSharedManager();
$sharedManager->attach(SomeClass::class, EventConstantClass::SOME_STRING_CONSTANT, [$this, 'callbackFunction']);
}
public function callbackFunction (MvcEvent $event) {...}

Kohana PHP framework Kohan_auth can't debug or use

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);
}
}

Internal Client on Thruway WAMP2

I am working on a sample for Internal Client + Authentication model like the one below.
Now I need to retrieve a list of connected sessions and intercept the close event of a session from Internal Client.
I want to ask if there's any method to archive that task? I was thinking about saving that list in redis, but it means I would have to re-write Thruway\Peer\Router classes, because the needed variables are now private, we don't have access to them to extends.
File server.php
<?php
/**
* server.php
*/
require "../bootstrap.php";
require 'InternalClient.php';
require 'SimpleAuthProviderClient.php';
use Thruway\Peer\Router;
use Thruway\Transport\RatchetTransportProvider;
use React\EventLoop\Factory;
use Thruway\Manager\ManagerClient;
use Thruway\Transport\InternalClientTransportProvider;
$manager = new ManagerClient();
$loop = Factory::create();
$router = new Router($loop, $manager);
$router->addTransportProvider(new InternalClientTransportProvider($manager));
$internalTransportProvider = new InternalClientTransportProvider(new \InternalClient());
$router->addTransportProvider($internalTransportProvider);
$authMgr = new \Thruway\Authentication\AuthenticationManager();
$router->setAuthenticationManager($authMgr);
$router->addTransportProvider(new InternalClientTransportProvider($authMgr));
//Provide authentication for the realm: 'somerealm'
$authProvClient = new SimpleAuthProviderClient(["somerealm"]);
$router->addTransportProvider(new InternalClientTransportProvider($authProvClient));
$transportProvider = new RatchetTransportProvider("127.0.0.1", 9090);
$router->addTransportProvider($transportProvider);
$router->start();
File SimpleAuthProviderClient.php
<?php
/**
* SimpleAuthProviderClient.php
*/
require "../bootstrap.php";
/**
* Class SimpleAuthProviderClient
*/
class SimpleAuthProviderClient extends \Thruway\Authentication\AbstractAuthProviderClient
{
/**
* #return string
*/
public function getMethodName()
{
return 'simplysimple';
}
/**
* #param mixed $signature
* #param null $extra
* #return array
*/
public function processAuthenticate($signature, $extra = null)
{
if ($signature == "letMeIn") {
return ["SUCCESS"];
} else {
return ["FAILURE"];
}
}
}
File InternalClient.php
<?php
/**
* InternalClient.php
*/
require "../bootstrap.php";
/**
* Class InternalClient
*/
class InternalClient extends Thruway\Peer\Client
{
function __construct()
{
parent::__construct("realm1");
}
/**
* #param \Thruway\AbstractSession $session
* #param \Thruway\Transport\TransportInterface $transport
*/
public function onSessionStart($session, $transport)
{
echo "--------------- Hello from InternalClient ------------";
$this->getCallee()->register($this->session, 'com.example.getphpversion', [$this, 'getPhpVersion']);
}
function start()
{
}
/**
* #return array
*/
function getPhpVersion()
{
return [phpversion()];
}
}
For reference purposes, this question was answered on Github.
If you subscribe to the events wamp.metaevent.session.on_join and wamp.metaevent.session.on_leave you will get notified. The event returns 1 argument similar to this:
{
"realm":"realm1",
"authprovider":null,
"authid":"username",
"authrole":"none",
"authmethod":"simplysimple",
"session":6016528494456948
}

Categories