I am using expressive 2 for a webservice, but when I return a JsonResponse it return the X-Powered-By →PHP/7.1.1 header, how to remove it from response?
class ProfileAction
{
/** #var UsersTable */
private $usersTable;
public function __construct( UsersTable $usersTable )
{
$this->usersTable = $usersTable;
}
public function __invoke( ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
{
// TODO: Implement __invoke() method.
return new JsonResponse(['profile'=>true]);
}
}
I just found the answer on Google just disable on php.ini expose_php=Off.
Related
I'm using Http client for making outgoing HTTP requests. I've used it many places in the project.Now project has new requirement that I have to add a new header to every outgoing
requests. I can do this by adding it to every places. But I want to know , is there any kind of trigger or event which can give me ability to modify the headers just before the request send. There is an event Illuminate\Http\Client\Events\RequestSending which is only useful for inspecting the request.
This is possible to achieve without the need of a package. You can simple do something like this in a service provider:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Client\Factory as Http;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->extend(Http::class, function ($service, $app) {
return $service->withOptions(['foo' => 'bar']);
});
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
}
}
Yes, this is possible with a fantastic package here. After installing the package you just set the default headers like,
Http::withDefaultOptions([
'headers' => [
'X-Bar-Header' => 'bar'
],
]);
But I was unfortunate, the package was not installed with my laravel 9-dev. So I had to extract the code for me. First, create a Factory class in your app\HttpClient directory,
<?php
namespace App\HttpClient;
use Illuminate\Http\Client\Factory as BaseFactory;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Arr;
class Factory extends BaseFactory
{
protected $ignoreDefaultOptions = false;
protected $defaultOptions = [];
public function ignoreDefaultOptions()
{
$this->ignoreDefaultOptions = true;
return $this;
}
public function withoutDefaultOptions($keys = null)
{
if ($keys === null) {
return $this->ignoreDefaultOptions();
}
if (func_num_args() > 1) {
$keys = func_get_args();
}
$this->defaultOptions = with($this->defaultOptions, function ($options) use ($keys) {
foreach (Arr::wrap($keys) as $key) {
Arr::forget($options, $key);
}
return $options;
});
return $this;
}
public function withDefaultOptions(array $options)
{
$this->defaultOptions = array_merge_recursive($this->defaultOptions, $options);
return $this;
}
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
if ($this->defaultOptions && ! $this->ignoreDefaultOptions) {
return tap(new PendingRequest($this), function ($request) {
$request->withOptions($this->defaultOptions)
->stub($this->stubCallbacks);
})->{$method}(...$parameters);
}
return parent::__call($method, $parameters);
}
}
Then, create a HttpServiceProver,
php artisan make:provider HttpServiceProvider
And put the following code there,
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Http\Client\Factory as BaseFactory;
use App\HttpClient\Factory;
class HttpServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* #return void
*/
public function register()
{
$this->app->bind(
BaseFactory::class,
function ($app) {
return new Factory($app->make(Dispatcher::class));
}
);
}
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
//
}
}
Now, register the newly created service provider in AppServiceProvider.php
public function register()
{
//...
app()->register(HttpServiceProvider::class);
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//....
Http::withDefaultOptions([
'headers' => [
'X-Bar-Header' => 'bar'
],
]);
}
There are other options in this package. Please check it the package link for details.
I am implementing two-factor authentication (2FA) in my Laravel 8 application.
The 2FA is applied every time the user logs in. However, I don't really feel that 2FA is necessary every time, I even find it annoying. As a solution I am thinking of applying it only when the user connects from a new device. Is there someone who has already done it or who can give me a hint of the changes that would be necessary?
I have got it. Here are the steps I have followed:
In the config file fortify.php I have added
'pipelines' => [
'login' => [
App\Actions\Fortify\RedirectIfTwoFactorAuthenticatable::class,
Laravel\Fortify\Actions\AttemptToAuthenticate::class,
Laravel\Fortify\Actions\PrepareAuthenticatedSession::class,
]
]
I have added the field two_factor_cookies to the User class.
I have customized the RedirectIfTwoFactorAuthenticatable class of
Fortify:
<?php
namespace App\Actions\Fortify;
use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable as DefaultRedirectIfTwoFactorAuthenticatable;
use Laravel\Fortify\TwoFactorAuthenticatable;
class RedirectIfTwoFactorAuthenticatable extends DefaultRedirectIfTwoFactorAuthenticatable
{
/**
* Handle the incoming request.
*
* #param \Illuminate\Http\Request $request
* #param callable $next
* #return mixed
*/
public function handle($request, $next)
{
$user = $this->validateCredentials($request);
if (optional($user)->two_factor_secret &&
in_array(TwoFactorAuthenticatable::class, class_uses_recursive($user)) &&
$this->checkIfUserDeviceHasNotCookie($user)) {
return $this->twoFactorChallengeResponse($request, $user);
}
return $next($request);
}
/**
* This checks if the user's device has the cookie stored
* in the database.
*
* #param \App\Models\User\User $user
* #return bool
*/
protected function checkIfUserDeviceHasNotCookie($user)
{
$two_factor_cookies = json_decode($user->two_factor_cookies);
if (!is_array($two_factor_cookies)){
$two_factor_cookies = [];
}
$two_factor_cookie = \Cookie::get('2fa');
return !in_array($two_factor_cookie,$two_factor_cookies);
}
}
In the FortifyServiceProvider I have added a customized TwoFactorLoginResponse.
<?php
namespace App\Providers;
use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation;
use App\Http\Responses\FailedPasswordResetLinkRequestResponse;
use App\Http\Responses\FailedPasswordResetResponse;
use App\Http\Responses\LockoutResponse;
use App\Http\Responses\LoginResponse;
use App\Http\Responses\LogoutResponse;
use App\Http\Responses\PasswordResetResponse;
use App\Http\Responses\RegisterResponse;
use App\Http\Responses\SuccessfulPasswordResetLinkRequestResponse;
use App\Http\Responses\TwoFactorLoginResponse;
use App\Http\Responses\VerifyEmail;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse as FailedPasswordResetLinkRequestResponseContract;
use Laravel\Fortify\Contracts\FailedPasswordResetResponse as FailedPasswordResetResponseContract;
use Laravel\Fortify\Contracts\LockoutResponse as LockoutResponseContract;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
use Laravel\Fortify\Contracts\LogoutResponse as LogoutResponseContract;
use Laravel\Fortify\Contracts\PasswordResetResponse as PasswordResetResponseContract;
use Laravel\Fortify\Contracts\RegisterResponse as RegisterResponseContract;
use Laravel\Fortify\Contracts\SuccessfulPasswordResetLinkRequestResponse as SuccessfulPasswordResetLinkRequestResponseContract;
use Laravel\Fortify\Contracts\TwoFactorLoginResponse as TwoFactorLoginResponseContract;
use Laravel\Fortify\Fortify;
class FortifyServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->registerResponseBindings();
}
/**
* Register the response bindings.
*
* #return void
*/
protected function registerResponseBindings()
{
$this->app->singleton(LoginResponseContract::class, LoginResponse::class);
$this->app->singleton(LogoutResponseContract::class, LogoutResponse::class);
$this->app->singleton(TwoFactorLoginResponseContract::class, TwoFactorLoginResponse::class);
$this->app->singleton(RegisterResponseContract::class, RegisterResponse::class);
$this->app->singleton(LockoutResponseContract::class, LockoutResponse::class);
$this->app->singleton(SuccessfulPasswordResetLinkRequestResponseContract::class, SuccessfulPasswordResetLinkRequestResponse::class);
$this->app->singleton(FailedPasswordResetLinkRequestResponseContract::class, FailedPasswordResetLinkRequestResponse::class);
$this->app->singleton(PasswordResetResponseContract::class, PasswordResetResponse::class);
$this->app->singleton(FailedPasswordResetResponseContract::class, FailedPasswordResetResponse::class);
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Fortify::ignoreRoutes();
Fortify::loginView(function () {
return view('auth.login');
});
Fortify::twoFactorChallengeView('auth.two-factor-challenge');
Fortify::confirmPasswordView(function (Request $request) {
if ($request->ajax()) {
return view('auth.confirm-password-form');
} else {
return view('auth.confirm-password');
}
});
Fortify::requestPasswordResetLinkView(function () {
return view('auth.forgot-password');
});
Fortify::resetPasswordView(function ($request) {
return view('auth.reset-password', ['request' => $request,'token' => $request->route('token')]);
});
Fortify::registerView(function () {
return view('auth.register');
});
Fortify::verifyEmailView(function () {
return view('auth.verify');
});
Fortify::createUsersUsing(CreateNewUser::class);
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
/*RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by($request->email.$request->ip());
});*/
RateLimiter::for('two-factor', function (Request $request) {
return Limit::perMinute(5)->by($request->session()->get('login.id'));
});
}
}
Finally, the TwoFactorLoginResponse:
<?php
namespace App\Http\Responses;
use Illuminate\Http\JsonResponse;
use Laravel\Fortify\Contracts\TwoFactorLoginResponse as TwoFactorLoginResponseContract;
class TwoFactorLoginResponse implements TwoFactorLoginResponseContract
{
/**
* Create an HTTP response that represents the object.
*
* #param \Illuminate\Http\Request $request
* #return \Symfony\Component\HttpFoundation\Response
*/
public function toResponse($request)
{
$user = \Auth::user();
$this->storeCookieIfNotInDB($user);
$role = $user->role;
if ($request->wantsJson()) {
return new JsonResponse('', 204);
}
if ($role == "0") {
return redirect()->route('user.home');
} else {
return redirect()->route('admin.home');
}
}
/**
* Store the cookie if it is not in the database.
*
* #param \App\Models\User\User $user
* #return void
*/
protected function storeCookieIfNotInDB($user)
{
$two_factor_cookies = json_decode($user->two_factor_cookies);
if (!is_array($two_factor_cookies)){
$two_factor_cookies = [];
}
$two_factor_cookie = \Cookie::get('2fa');
if (!in_array($two_factor_cookie,$two_factor_cookies)) {
$two_factor_cookie = md5(now());
$two_factor_cookies[] = $two_factor_cookie;
if (count($two_factor_cookies) > 3) {
array_shift($two_factor_cookies);
}
$user->two_factor_cookies = json_encode($two_factor_cookies);
$user->save();
$lifetime = 60 * 24 * 365; //one year
\Cookie::queue('2fa',$two_factor_cookie,$lifetime);
}
}
}
Upon login, it will look for the cookie 2fa. If its content is stored in the database, it will not be necessary to enter the code again. To prevent unlimited cookie content from being saved in the DB you can add a maximum limit (I have set it 3).
Thanks to Maarten Veerman for the inital help.
According to this line: https://github.com/laravel/fortify/blob/82c99b6999f7e89f402cfd7eb4074e619382b3b7/src/Http/Controllers/AuthenticatedSessionController.php#L80
you can create a pipelines.login entry in your fortify config file.
The solution would be to:
create the config entry
copy the pipeline setup in the above file, line 84.
create a custom AttemptToAuthenticate class, make sure the pipeline config points to your new class.
make the new class extend the default fortify AttemptToAuthenticate class.
overwrite the handle function, add your logic in the new function, where you check for a cookie on the device.
I am new with laravel so please don't be harsh.
I am bulding a simple web which connects to an external API(several endpoints) via Guzzle,fetching some data,cleaning them and storing them.
At the moment -and from one endpoint- i have something the following Job:
public function handle(Client $client)
{
try {
$request= $client->request('GET', 'https://api.url./something', [
'headers' => [
'X-RapidAPI-Key'=> env("FOOTBALL_API_KEY"),
'Accept' => 'application/json'
]
]);
$request = json_decode($request->getBody()->getContents(), true);
foreach ($request as $array=>$val) {
foreach ($val['leagues'] as $id) {
League::firstOrCreate(collect($id)->except(['coverage'])->toArray());
}
}
} catch (GuzzleException $e) {
};
}
Therefore i would like some code recommendations, how can i make my code better from design point of view.
My thoughts are:
a)Bind Guzzle as service provider.
b)use a design pattern for implementing calls to endpoints.URI builder maybe?
Any assistance will be appreciated.
May the force be with you.
Detailed feedback
Some pointers specific to the provided code itself:
A guzzle client request returns a response, which does not match the name of the parameter you assign it to
Calls to json_decode can fail in which case they'll return null. In terms of defensive programming it's good to check for those fail cases
Your case makes some assumptions about the data in the response. It's best to check if the response is in the actual format you expect before using it.
You catch all GuzzleExceptions, but do nothing in those cases. I think you could improve this by either:
Logging the exception
Throwing another exception which you will catch at a class, calling the handle() method
Both of the options above
You could choose to inject the api key, rather than fetching it directly via the env() method. This will prevent issues described in the warning block here
General feedback
It feels like your code is mixing responsibilities, which is considered bad practice. The handle() method now does the following:
Send API requests
Decode API requests
Validate API responses
Parse API responses
Create models
You could consider moving some or all of these to separate classes, like so:
ApiClient which is responsible for sending out requests
ResponseDecoder which is responsible for turning a response into \stdClass
ResponseValidator which is responsible for checking if the response has the expected data structure
RepsonseParser which is responsible for turning the response \stdClass into collections
LeagueFactory which is responsible for turning collections into League models
One could argue that the first four classes should be put into a single class called ApiClient. That's purely up to you.
So in the end you would come up with something like this:
<?php
namespace App\Example;
use Psr\Log\LoggerInterface;
class LeagueApiHandler
{
/**
* #var ApiClient
*/
private $apiClient;
/**
* #var ResponseDecoder
*/
private $decoder;
/**
* #var ResponseValidator
*/
private $validator;
/**
* #var ResponseParser
*/
private $parser;
/**
* #var LeagueFactory
*/
private $factory;
/**
* #var LoggerInterface
*/
private $logger;
public function __construct(
ApiClient $apiClient,
ResponseDecoder $decoder,
ResponseValidator $validator,
ResponseParser $parser,
LeagueFactory $factory,
LoggerInterface $logger
) {
$this->apiClient = $apiClient;
$this->decoder = $decoder;
$this->validator = $validator;
$this->parser = $parser;
$this->factory = $factory;
$this->logger = $logger;
}
public function handle()
{
try {
$response = $this->apiClient->send();
} catch (\RuntimeException $e) {
$this->logger->error('Unable to send api request', $e->getMessage());
return;
};
try {
$decodedResponse = $this->decoder->decode($response);
} catch (\RuntimeException $e) {
$this->logger->error('Unable to decode api response');
return;
};
if (!$this->validator->isValid($decodedResponse)) {
$this->logger->error('Unable to decode api response');
return;
}
$collections = $this->parser->toCollection($decodedResponse);
foreach ($collections as $collection) {
$this->factory->create($collection);
}
}
}
namespace App\Example;
use GuzzleHttp\Client;
class ApiClient
{
/**
* #var Client
*/
private $client;
/**
* #var string
*/
private $apiKey;
public function __construct(Client $client, string $apiKey)
{
$this->client = $client;
$this->apiKey = $apiKey;
}
public function send()
{
try {
return $this->client->request('GET', 'https://api.url./something', [
'headers' => [
'X-RapidAPI-Key' => $this->apiKey,
'Accept' => 'application/json'
]
]);
} catch (GuzzleException $e) {
throw new \RuntimeException('Unable to send request to api', 0, $e);
};
}
}
namespace App\Example;
use Psr\Http\Message\ResponseInterface;
class ResponseDecoder
{
public function decode(ResponseInterface $response): \stdClass
{
$response = json_decode($response->getBody()->getContents(), true);
if ($response === null) {
throw new \RuntimeException('Unable to decode api response');
}
return $response;
}
}
namespace App\Example;
class ResponseValidator
{
public function isValid(\stdClass $response): bool
{
if (is_array($response) === false) {
return false;
}
foreach ($response as $array) {
if (!isset($array['leagues'])) {
return false;
}
}
return true;
}
}
namespace App\Example;
use Illuminate\Support\Collection;
class ResponseParser
{
/**
* #param \stdClass $response
* #return Collection[]
*/
public function toCollection(\stdClass $response): array
{
$collections = [];
foreach ($response as $array => $val) {
foreach ($val['leagues'] as $id) {
$collections[] = collect($id)->except(['coverage'])->toArray();
}
}
return $collections;
}
}
namespace App\Example;
use Illuminate\Support\Collection;
class LeagueFactory
{
public function create(Collection $collection): void
{
League::firstOrCreate($collection);
}
}
I create a request in App\Http\Requests\Web in which it shows me the error.
Class 'App\Http\Requests\Web\WebRequest' not found
Here is the code of my Request CreateBucket.php:
<?php
namespace App\Http\Requests\Web;
class CreateBucket extends WebRequest
{
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'bucket_name' => 'required|string|string|max:30',
'bucket_type' => 'required|string|string|max:30',
'bucket_image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg',
];
}
}
And Here is my code of Bucket Controller:
<?php
namespace App\Http\Controllers\Web;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Requests\Web\CreateBucket;
use App\Bucket;
class BucketController extends Controller
{
public function index(Request $request)
{
$buckets = Bucket::orderBy('id','ASC')->paginate(10);
return view('buckets.index',compact('buckets',$buckets))
->with('i',($request->input('page',1) - 1) * 10);
}
public function create()
{
return view('buckets.create');
}
public function store(CreateBucket $request)
{
if($request->hasFile('bucket_image')) {
$bucket_image = $request->file('bucket_image');
$bucket_image_name = time().'.'.$bucket_image->getClientOriginalExtension();
$path = public_path('Storage/BucketImages');
$bucket_image->move($path, $bucket_image_name);
$bucket_image = 'Storage/BucketImages/'.$bucket_image_name;
} else {
$bucket_image = NULL;
}
$category = Category::create([
'bucket_name' => $request->input('bucket_name'),
'bucket_image'=> $bucket_image,
'bucket_type' => $request->input('bucket_type'),
]);
return redirect()->route('buckets.index')
->with('success','Bucket created successfully');
}
Please Help me to resolve this error. Thanks.
My WebRequest.php is missing in Requests folder that why he gave me this Error.
Here is the WebRequest.php file I created and my issue is resolve.
<?php
namespace App\Http\Requests\Web;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
class WebRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
//
];
}
}
Does anybody know how to add extra data on a collection?
The doc says much about how to add extra data on an item which translates into decorating the ItemNormalizer service, and it works pretty well.
But I’m struggling in finding out which normalizer to decorate when it comes to add some data on a collection of entities. The extra data could be anything: the current user logged in, a detailed pager, some debug parameters, ... that are not related to a specific entity, but rather on the request itself.
The only working solution for now is to hook on a Kernel event but that's definitely not the code I like to write:
use ApiPlatform\Core\EventListener\EventPriorities;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
final class SerializeListener implements EventSubscriberInterface
{
/**
* #var Security
*/
private $security;
/**
* #var NormalizerInterface
*/
private $normalizer;
public function __construct(
Security $security,
NormalizerInterface $normalizer
) {
$this->security = $security;
$this->normalizer = $normalizer;
}
public function addCurrentUser(GetResponseForControllerResultEvent $event)
{
$request = $event->getRequest();
if ($request->attributes->has('_api_respond')) {
$serialized = $event->getControllerResult();
$data = json_decode($serialized, true);
$data['hydra:user'] = $this->normalizer->normalize(
$this->security->getUser(),
$request->attributes->get('_format'),
$request->attributes->get('_api_normalization_context')
);
$event->setControllerResult(json_encode($data));
}
}
/**
* #inheritDoc
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => [
'addCurrentUser',
EventPriorities::POST_SERIALIZE,
],
];
}
}
Any ideas?
Thank you,
Ben
Alright, I finally managed to do this.
namespace App\Api;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
final class ApiCollectionNormalizer implements NormalizerInterface, NormalizerAwareInterface
{
/**
* #var NormalizerInterface|NormalizerAwareInterface
*/
private $decorated;
public function __construct(NormalizerInterface $decorated)
{
if (!$decorated instanceof NormalizerAwareInterface) {
throw new \InvalidArgumentException(
sprintf('The decorated normalizer must implement the %s.', NormalizerAwareInterface::class)
);
}
$this->decorated = $decorated;
}
/**
* #inheritdoc
*/
public function normalize($object, $format = null, array $context = [])
{
$data = $this->decorated->normalize($object, $format, $context);
if ('collection' === $context['operation_type'] && 'get' === $context['collection_operation_name']) {
$data['hydra:meta'] = ['foo' => 'bar'];
}
return $data;
}
/**
* #inheritdoc
*/
public function supportsNormalization($data, $format = null)
{
return $this->decorated->supportsNormalization($data, $format);
}
/**
* #inheritdoc
*/
public function setNormalizer(NormalizerInterface $normalizer)
{
$this->decorated->setNormalizer($normalizer);
}
}
# config/services.yaml
services:
App\Api\ApiCollectionNormalizer:
decorates: 'api_platform.hydra.normalizer.collection'
arguments: [ '#App\Api\ApiCollectionNormalizer.inner' ]
Keep it for the records :)