target_path redirection on successful login is broken following upgrade from Symfony 4.4.41 to 4.4.42 - php

Following an upgrade from Symfony 4.4.41 to 4.4.42 I seem to be unable to use 'target' in the following way:
/**
* Attempting to login as the specified user.
*
* #Route(
* "/login",
* name = "crmpicco_login"
* )
* #Template("#App/Register/login.html.twig")
*
* #param Request $request
* #param LoginHelper $loginHelper
*
* #return array
*/
public function loginAction(Request $request, LoginHelper $loginHelper): array
{
return $this->loginHelper($request, $loginHelper);
}
/**
* #param Request $request
* #param LoginHelper $loginHelper
*
* #return array
*/
protected function loginHelper(Request $request, LoginHelper $loginHelper): array
{
/* #var $session \Symfony\Component\HttpFoundation\Session\Session */
$session = $request->getSession();
// get the error if any (works with forward and redirect -- see below)
if ($request->attributes->has(LoginSecurity::AUTHENTICATION_ERROR)) {
$error = $request->attributes->get(LoginSecurity::AUTHENTICATION_ERROR);
} elseif (null !== $session && $session->has(LoginSecurity::AUTHENTICATION_ERROR)) {
$error = $session->get(LoginSecurity::AUTHENTICATION_ERROR);
$session->remove(LoginSecurity::AUTHENTICATION_ERROR);
} else {
$error = '';
}
$csrfToken = $this->getContainerInterface()->get('security.csrf.token_manager')->getToken('authenticate');
return [
'error' => $error,
'csrf_token' => $csrfToken,
'target' => 'crmpicco_register_details',
];
}
I can see there were some changes to the DefaultAuthenticationSuccessHandler in 4.4.42, however it's unclear how to fix this in my application.
My SuccessHandler looks like this:
public function onAuthenticationSuccess(Request $request, TokenInterface $token): RedirectResponse
{
return $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request));
}
What I am experiencing is the redirect is not respecting the "crmpicco_register_details" route I am passing through and is instead redirecting to the default "admin" route on successful login.
How do I remedy this in my application? (For now, i've reverted to 4.4.41 until I can fix this).
I created a discussion on Github (#46677) for this, but someone pointed out to me that it's more of an issue than a discussion.

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

Symfony oauth2 provider, Remove repeating code by 'stashing' new class variables within

I'm working on a big project that implements OAuth2 -> Specifically Oauth2-EveOnline
im using
PHP 8
Symfony 6
EveLabs/Oauth2
and as part of usage when creating the provider class the following code is required
$provider = new EveOnline([
'clientId' => $this->getParameter('eve.client_id'),
'clientSecret' => $this->getParameter('eve.client_secret'),
'redirectUri' => $this->getParameter('eve.redirect_uri'),
]);
What I would like to do is 'stash' this array within the EveOnline provider class so that in future all I have to call is
$provider = new EveOnline();
Unfortunate searching for an answer hasn't been fruitful, perhaps my searches are too vague, my problem is something that nobody has tried before or more likely it's simple and I am not experienced enough to see the solution
I have tried using a Service class to setup the provider but calling $this-> gets a little messy and I have been unable to work that out as $this calls the document your working on and to get .env details it should be done from the controller
I know that if I copy the provider from vendor/src to my App/src then it will override the vendor/src, if I can make changes to include my array and pull the info it needs then I can save myself passing the array on almost every page as I require the provider to manage tokens and communication with the EVE API (ESI).
Is this possible or is there a way I can move this to a Service?
When I learned how to create services I was rather happy and have created a few to handle all my API calls I just pass in my $provider and return the $response to my controller.
I was hoping to do something similar with building the provider, create a page builder service that requires 2 lines in the controller and handles setting up the provider and populating the session with info that I need to call on my pages, or use in DB/API queries.
I have already implemented a service for refreshing tokens but that is a very small inefficient feat.
App\Controller
<?php
namespace App\Controller;
use App\Service\TokenRefresh;
use Evelabs\OAuth2\Client\Provider\EveOnline;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Annotation\Route;
class Controller extends AbstractController
{
#[Route('/', name: 'page_homepage')]
public function homepage()
{
$session = new Session();
$session->start();
$provider = new EveOnline([
'clientId' => $this->getParameter('eve.client_id'),
'clientSecret' => $this->getParameter('eve.client_secret'),
'redirectUri' => $this->getParameter('eve.redirect_uri'),
]);
if($_SESSION['token']->hasExpired()){
$token = new TokenRefresh();
$token->newToken($provider);
}
if (isset($_SESSION)) {
dump($_SESSION);
}
return $this->render('homepage.html.twig');
}
#[Route('/login', name: 'page_login')]
public function login()
{
$session = new Session();
$session->start();
$scopes = [
'scope' => ['publicData', 'esi-markets.read_character_orders.v1', 'esi-markets.structure_markets.v1'] // array or string
];
$provider = new EveOnline([
'clientId' => $this->getParameter('eve.client_id'),
'clientSecret' => $this->getParameter('eve.client_secret'),
'redirectUri' => $this->getParameter('eve.redirect_uri'),
]);
if (!isset($_GET['code'])) {
// here we can set requested scopes but it is totally optional
// make sure you have them enabled on your app page at
// https://developers.eveonline.com/applications/
// If we don't have an authorization code then get one
$authUrl = $provider->getAuthorizationUrl($scopes);
$_SESSION['oauth2state'] = $provider->getState();
unset($_SESSION['token']);
header('Location: ' . $authUrl);
exit;
// Check given state against previously stored one to mitigate CSRF attack
} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
unset($_SESSION['oauth2state']);
exit('Invalid state');
} else {
// In this example we use php native $_SESSION as data store
if (!isset($_SESSION['token'])) {
// Try to get an access token (using the authorization code grant)
$_SESSION['token'] = $provider->getAccessToken('authorization_code', [
'code' => $_GET['code']
]);
} elseif ($_SESSION['token']->hasExpired()) {
$token = new TokenRefresh();
$token->newToken($provider);
}
// Optional: Now you have a token you can look up a users profile data
try {
// We got an access token, let's now get the user's details
$user = $provider->getResourceOwner($_SESSION['token']);
// Use these details to create a new profile
} catch (\Exception $e) {
// Failed to get user details
exit('Oh dear...');
}
$user = $user->toArray();
$owner = [
'character_id' => $user['CharacterID'],
'character_name' => $user['CharacterName']
];
$session->set('owner', $owner);
$_SESSION['owner'] = $owner;
dump($user);
if (isset($_SESSION)) {
dump($_SESSION);
dump($session->get('owner'));
}
return $this->render('login.html.twig');
}
}
// TODO: Remove debug session clear
#[Route('/clrsession', name: 'app_clrsession')]
public function clrsession(){
$session = new Session();
$session->invalidate();
return $this->redirectToRoute('page_homepage');
}
}
Provider\EveOnline
<?php
namespace Evelabs\OAuth2\Client\Provider;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\ResponseInterface;
class EveOnline extends AbstractProvider
{
use BearerAuthorizationTrait;
/**
* Default scopes
*
* #var array
*/
public $defaultScopes = [];
/**
* Get the string used to separate scopes.
*
* #return string
*/
protected function getScopeSeparator()
{
return ' ';
}
/**
* Returns the base URL for authorizing a client.
*
* Eg. https://oauth.service.com/authorize
*
* #return string
*/
public function getBaseAuthorizationUrl()
{
return 'https://login.eveonline.com/oauth/authorize';
}
/**
* Returns the base URL for requesting an access token.
*
* Eg. https://oauth.service.com/token
*
* #param array $params
* #return string
*/
public function getBaseAccessTokenUrl(array $params)
{
return 'https://login.eveonline.com/oauth/token';
}
/**
* Returns the URL for requesting the resource owner's details.
*
* #param AccessToken $token
* #return string
*/
public function getResourceOwnerDetailsUrl(AccessToken $token)
{
return 'https://login.eveonline.com/oauth/verify';
}
/**
* Returns the default scopes used by this provider.
*
* This should only be the scopes that are required to request the details
* of the resource owner, rather than all the available scopes.
*
* #return array
*/
protected function getDefaultScopes()
{
return $this->defaultScopes;
}
/**
* Checks a provider response for errors.
*
* #throws IdentityProviderException
* #param ResponseInterface $response
* #param array|string $data Parsed response data
* #return void
*/
protected function checkResponse(ResponseInterface $response, $data)
{
//not throwing anything for 2xx responses
if (intval(substr($response->getStatusCode(), 0, 1)) === 2) {
return;
}
$message = $this->safeRead($data, 'error_description') || $this->safeRead($data, 'message');
throw new IdentityProviderException(
$message ?: $response->getReasonPhrase(),
$response->getStatusCode(),
$response
);
}
/**
* Generates a resource owner object from a successful resource owner
* details request.
*
* #param array $response
* #param AccessToken $token
* #return ResourceOwnerInterface
*/
protected function createResourceOwner(array $response, AccessToken $token)
{
return new EveOnlineResourceOwner($response);
}
/**
* Internal helper function to safe read from an array
* #param mixed $array
* #param string|int $key
* #return null
*/
private function safeRead($array, $key)
{
return !empty($array[$key]) ? $array[$key] : null;
}
}

How do I extend Laravel Sanctum's functionality?

I am specifically trying to get Sanctum's Guard class to look for the API token in a JSON request body if it can't find it in the Authorization header. I simply need to add an elseif after it checks for the bearer token.
So question is: What is the best way to override this method (or class) with my own, without touching the original Sanctum files?
<?php
namespace Laravel\Sanctum;
use Illuminate\Contracts\Auth\Factory as AuthFactory;
use Illuminate\Http\Request;
class Guard
{
/**
* The authentication factory implementation.
*
* #var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* The number of minutes tokens should be allowed to remain valid.
*
* #var int
*/
protected $expiration;
/**
* Create a new guard instance.
*
* #param \Illuminate\Contracts\Auth\Factory $auth
* #param int $expiration
* #return void
*/
public function __construct(AuthFactory $auth, $expiration = null)
{
$this->auth = $auth;
$this->expiration = $expiration;
}
/**
* Retrieve the authenticated user for the incoming request.
*
* #param \Illuminate\Http\Request $request
* #return mixed
*/
public function __invoke(Request $request)
{
if ($user = $this->auth->guard('web')->user()) {
return $this->supportsTokens($user)
? $user->withAccessToken(new TransientToken)
: $user;
}
if ($token = $request->bearerToken()) {
$model = Sanctum::$personalAccessTokenModel;
$accessToken = $model::where('token', hash('sha256', $token))->first();
if (! $accessToken ||
($this->expiration &&
$accessToken->created_at->lte(now()->subMinutes($this->expiration)))) {
return;
}
return $this->supportsTokens($accessToken->tokenable) ? $accessToken->tokenable->withAccessToken(
tap($accessToken->forceFill(['last_used_at' => now()]))->save()
) : null;
}
}
/**
* Determine if the tokenable model supports API tokens.
*
* #param mixed $tokenable
* #return bool
*/
protected function supportsTokens($tokenable = null)
{
return in_array(HasApiTokens::class, class_uses_recursive(
$tokenable ? get_class($tokenable) : null
));
}
}
I don't know if you've already figured out but I think you need to add an entry in your AppServiceProvider boot method and override configureGuard functionality placed in SanctumServiceProvider at line 94.
app/Providers/AppServiceProvider.php
Auth::resolved(function ($auth) {
$auth->extend('sanctum', function ($app, $name, array $config) use ($auth) {
return tap($this->createGuard($auth, $config), function ($guard) {
$this->app->refresh('request', $guard, 'setRequest');
});
});
});
You will also need to override createGuard function to specify your custom Guard class with the functionality you require.

PHP/Laravel - Extending authorizeResource to work on custom method

I have a resource controller called StreamController.php, that utilizes a policy called StreamPolicy.php.
In my controller, I have this:
//StreamController.php
/**
* Construct method.
*/
public function __construct()
{
$this->middleware('auth');
$this->authorizeResource(Stream::class, 'stream');
}
With above, all the RESTful endpoints is successfully "protected" using the policy.
However, I have added a new method to my controller, called documents(), like so:
//web.php
Route::get('streams/{stream}/documents', 'StreamController#documents');
//StreamController.php
/**
* Display the imported documents of the resource
*
* #return \Illuminate\Http\Response
*/
public function documents(Stream $stream)
{
return view('streams.documents', compact('stream'));
}
Now the problem is if I visit the URL:
example.com/streams/1 and I am not the owner of the stream, I get a 403 page - but if I go to:
example.com/streams/1/documents and I am not the owner of the stream, I can still access the page.
What am I doing wrong? How can I make so my policy also covers the documents() methods in my controller?
Edit:
This is my StreamPolicy.php file:
//StreamPolicy.php
namespace App\Policies;
use App\User;
use App\Stream;
use Illuminate\Auth\Access\HandlesAuthorization;
class StreamPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view the stream.
*
* #param \App\User $user
* #param \App\Stream $stream
* #return mixed
*/
public function view(User $user, Stream $stream)
{
return $user->id == $stream->user_id;
}
/**
* Determine whether the user can create streams.
*
* #param \App\User $user
* #return mixed
*/
public function create(User $user)
{
//
return true;
}
/**
* Determine whether the user can update the stream.
*
* #param \App\User $user
* #param \App\Stream $stream
* #return mixed
*/
public function update(User $user, Stream $stream)
{
//
return $user->id == $stream->user_id;
}
/**
* Determine whether the user can delete the stream.
*
* #param \App\User $user
* #param \App\Stream $stream
* #return mixed
*/
public function delete(User $user, Stream $stream)
{
//
return $user->id == $stream->user_id;
}
/**
* Determine whether the user can restore the stream.
*
* #param \App\User $user
* #param \App\Stream $stream
* #return mixed
*/
public function restore(User $user, Stream $stream)
{
//
}
/**
* Determine whether the user can permanently delete the stream.
*
* #param \App\User $user
* #param \App\Stream $stream
* #return mixed
*/
public function forceDelete(User $user, Stream $stream)
{
//
}
}
Controller.php uses "AuthorizesRequest" trait which defines below 2 methods:
trait AuthorizesRequests
{
/**
* Get the map of resource methods to ability names.
*
* #return array
*/
protected function resourceAbilityMap()
{
return [
'show' => 'view',
'create' => 'create',
'store' => 'create',
'edit' => 'update',
'update' => 'update',
'destroy' => 'delete',
];
}
/**
* Get the list of resource methods which do not have model parameters.
*
* #return array
*/
protected function resourceMethodsWithoutModels()
{
return ['index', 'create', 'store'];
}
You can override these 2 protected methods per controller basis because every controller extends Controller.php
class UserController extends Controller
{
public function __construct ()
{
$this->authorizeResource ( User::class, 'user' );
}
/**
* Get the map of resource methods to ability names.
*
* #return array
*/
protected function resourceAbilityMap()
{
return [
'show' => 'view',
'create' => 'create',
'store' => 'create',
'edit' => 'update',
'update' => 'update',
'destroy' => 'delete',
'customMethod'=>'customMethod',
'customMethodWithoutModel'=>'customMethodWithoutModel'
];
}
/**
* Get the list of resource methods which do not have model parameters.
*
* #return array
*/
protected function resourceMethodsWithoutModels()
{
return ['index', 'create', 'store','customMethodWithoutModel'];
}
Its Policy Class
class UserPolicy
{
/**
* Determine whether the user can custom method.
*
* #param \App\User $user
* #param \App\User $model
* #return mixed
*/
public function customMethod(User $user, User $model){
return true;
}
/**
* Determine whether the user can custom method without model.
*
* #param \App\User $user
* #return mixed
*/
public function customMethodWithoutModel(User $user){
return true;
}
I don't know exactly why is not working but I'm afraid that the authorizeResource method only handles the routes for the well-known resources end-points: view, create, update, delete and restore.
Later edit: Take a look in the docs to see which are the actions handled by the Resource Controllers https://laravel.com/docs/5.7/controllers#resource-controllers
What you should do is to explicitly set the authorization to the new route:
Route::get('streams/{stream}/documents', 'StreamController#documents')->middleware('can:documents,stream');
Of course, the documents method should exist on the StreamPolicy class.
OR
To authorize inside the StreamController.documents method:
public function documents(Stream $stream)
{
$this->authorize('documents', $stream);
return view('streams.documents', compact('stream'));
}

Laravel Redirect Me to /Home Link When Clicking on the Reset Password Link

I need to get Forgot Password functionality in my web app, so i am using laravel for this. When i click on the forgot password it shows me the a form that takes email on which i need to reset password when i click on the reset password button it sends a mail on the linked id, and when i click on the link in the mail it redirect me to the change password page that has password and confirm password field when i click on the reset password it redirects me to home link.
The issue is "first time it changes my password successfully", but when i tried for another account to reset password, when clicking on the link on the mail it redirects me to home other than the password change form and it happens for the all account now.
What is the problem that causes the above issue please explain and how to resolve this issue
Here is my password reset routes:
Route::post('password/email', 'Auth\ForgotPasswordController#sendResetLinkEmail');
Route::post('password/reset', 'Auth\PasswordController#reset')->name('password.reset');
Route::get('password/reset/{token?}', 'Auth\PasswordController#showResetForm')->name('password.request');
Here is the ResetsPasswords.php class
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Auth\Events\PasswordReset;
trait ResetsPasswords
{
use RedirectsUsers;
/**
* Display the password reset view for the given token.
*
* If no token is present, display the link request form.
*
* #param \Illuminate\Http\Request $request
* #param string|null $token
* #return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showResetForm(Request $request, $token = null)
{
return view('auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email]
);
}
/**
* Reset the given user's password.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
public function reset(Request $request)
{
$this->validate($request, $this->rules(), $this-
>validationErrorMessages());
// Here we will attempt to reset the user's password. If it is successful
we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$response = $this->broker()->reset(
$this->credentials($request), function ($user, $password) {
$this->resetPassword($user, $password);
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
return $response == Password::PASSWORD_RESET
? $this->sendResetResponse($response)
: $this->sendResetFailedResponse($request, $response);
}
/**
* Get the password reset validation rules.
*
* #return array
*/
protected function rules()
{
return [
'token' => 'required',
'email' => 'required|email',
'password' => 'required|confirmed|min:6',
];
}
/**
* Get the password reset validation error messages.
*
* #return array
*/
protected function validationErrorMessages()
{
return [];
}
/**
* Get the password reset credentials from the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function credentials(Request $request)
{
return $request->only(
'email', 'password', 'password_confirmation', 'token'
);
}
/**
* Reset the given user's password.
*
* #param \Illuminate\Contracts\Auth\CanResetPassword $user
* #param string $password
* #return void
*/
protected function resetPassword($user, $password)
{
$user->password = Hash::make($password);
$user->setRememberToken(Str::random(60));
$user->save();
event(new PasswordReset($user));
$this->guard()->login($user);
}
/**
* Get the response for a successful password reset.
*
* #param string $response
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetResponse($response)
{
return redirect($this->redirectPath())
->with('status', trans($response));
}
/**
* Get the response for a failed password reset.
*
* #param \Illuminate\Http\Request $request
* #param string $response
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetFailedResponse(Request $request, $response)
{
return redirect()->back()
->withInput($request->only('email'))
->withErrors(['email' => trans($response)]);
}
/**
* Get the broker to be used during password reset.
*
* #return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker()
{
return Password::broker();
}
/**
* Get the guard to be used during password reset.
*
* #return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard();
}
}
and Here is the SendsResetEmails.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
trait SendsPasswordResetEmails
{
/**
* Display the form to request a password reset link.
*
* #return \Illuminate\Http\Response
*/
public function showLinkRequestForm()
{
return view('auth.passwords.email');
}
/**
* Send a reset link to the given user.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
public function sendResetLinkEmail(Request $request)
{
$this->validateEmail($request);
// We will send the password reset link to this user. Once we have
attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->broker()->sendResetLink(
$request->only('email')
);
return $response == Password::RESET_LINK_SENT
? $this->sendResetLinkResponse($response)
: $this->sendResetLinkFailedResponse($request, $response);
}
/**
* Validate the email for the given request.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
protected function validateEmail(Request $request)
{
$this->validate($request, ['email' => 'required|email']);
}
/**
* Get the response for a successful password reset link.
*
* #param string $response
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetLinkResponse($response)
{
return back()->with('status', trans($response));
}
/**
* Get the response for a failed password reset link.
*
* #param \Illuminate\Http\Request $request
* #param string $response
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetLinkFailedResponse(Request $request, $response)
{
return back()->withErrors(
['email' => trans($response)]
);
}
/**
* Get the broker to be used during password reset.
*
* #return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker()
{
return Password::broker();
}
}
You'll notice the in the ResetPasswordController we have:
protected function resetPassword($user, $password)
{
$user->password = Hash::make($password);
$user->setRememberToken(Str::random(60));
$user->save();
event(new PasswordReset($user));
$this->guard()->login($user);
}
After the password is reset, it logs the user in. However, we are also using the guest middleware in the same class:
public function __construct()
{
$this->middleware('guest');
}
Therefore if you try to visit the password reset page while logged in, you will be redirected. So just log out first.

Categories