glastree_on_gitea
This commit is contained in:
+54
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Routing\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Routing\Registrar;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
|
||||
class SubstituteBindings
|
||||
{
|
||||
/**
|
||||
* The router instance.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Routing\Registrar
|
||||
*/
|
||||
protected $router;
|
||||
|
||||
/**
|
||||
* Create a new bindings substitutor.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Routing\Registrar $router
|
||||
*/
|
||||
public function __construct(Registrar $router)
|
||||
{
|
||||
$this->router = $router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$route = $request->route();
|
||||
|
||||
try {
|
||||
$this->router->substituteBindings($route);
|
||||
$this->router->substituteImplicitBindings($route);
|
||||
} catch (ModelNotFoundException $exception) {
|
||||
if ($route->getMissing()) {
|
||||
return $route->getMissing()($request, $exception);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
+355
@@ -0,0 +1,355 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Routing\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Cache\RateLimiter;
|
||||
use Illuminate\Cache\RateLimiting\Unlimited;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
use Illuminate\Http\Exceptions\ThrottleRequestsException;
|
||||
use Illuminate\Routing\Exceptions\MissingRateLimiterException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\InteractsWithTime;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
use function Illuminate\Support\enum_value;
|
||||
|
||||
class ThrottleRequests
|
||||
{
|
||||
use InteractsWithTime;
|
||||
|
||||
/**
|
||||
* The rate limiter instance.
|
||||
*
|
||||
* @var \Illuminate\Cache\RateLimiter
|
||||
*/
|
||||
protected $limiter;
|
||||
|
||||
/**
|
||||
* Indicates if the rate limiter keys should be hashed.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $shouldHashKeys = true;
|
||||
|
||||
/**
|
||||
* Create a new request throttler.
|
||||
*
|
||||
* @param \Illuminate\Cache\RateLimiter $limiter
|
||||
*/
|
||||
public function __construct(RateLimiter $limiter)
|
||||
{
|
||||
$this->limiter = $limiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the named rate limiter to use for the middleware.
|
||||
*
|
||||
* @param \UnitEnum|string $name
|
||||
* @return string
|
||||
*/
|
||||
public static function using($name)
|
||||
{
|
||||
return static::class.':'.enum_value($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the rate limiter configuration for the middleware.
|
||||
*
|
||||
* @param int $maxAttempts
|
||||
* @param int $decayMinutes
|
||||
* @param string $prefix
|
||||
* @return string
|
||||
*
|
||||
* @named-arguments-supported
|
||||
*/
|
||||
public static function with($maxAttempts = 60, $decayMinutes = 1, $prefix = '')
|
||||
{
|
||||
return static::class.':'.implode(',', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param int|string $maxAttempts
|
||||
* @param float|int $decayMinutes
|
||||
* @param string $prefix
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*
|
||||
* @throws \Illuminate\Http\Exceptions\ThrottleRequestsException
|
||||
* @throws \Illuminate\Routing\Exceptions\MissingRateLimiterException
|
||||
*/
|
||||
public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1, $prefix = '')
|
||||
{
|
||||
if (is_string($maxAttempts)
|
||||
&& func_num_args() === 3
|
||||
&& ! is_null($limiter = $this->limiter->limiter($maxAttempts))) {
|
||||
return $this->handleRequestUsingNamedLimiter($request, $next, $maxAttempts, $limiter);
|
||||
}
|
||||
|
||||
return $this->handleRequest(
|
||||
$request,
|
||||
$next,
|
||||
[
|
||||
(object) [
|
||||
'key' => $prefix.$this->resolveRequestSignature($request),
|
||||
'maxAttempts' => $this->resolveMaxAttempts($request, $maxAttempts),
|
||||
'decaySeconds' => 60 * $decayMinutes,
|
||||
'afterCallback' => null,
|
||||
'responseCallback' => null,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param string $limiterName
|
||||
* @param \Closure $limiter
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*
|
||||
* @throws \Illuminate\Http\Exceptions\ThrottleRequestsException
|
||||
*/
|
||||
protected function handleRequestUsingNamedLimiter($request, Closure $next, $limiterName, Closure $limiter)
|
||||
{
|
||||
$limiterResponse = $limiter($request);
|
||||
|
||||
if ($limiterResponse instanceof Response) {
|
||||
return $limiterResponse;
|
||||
} elseif ($limiterResponse instanceof Unlimited) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return $this->handleRequest(
|
||||
$request,
|
||||
$next,
|
||||
Collection::wrap($limiterResponse)->map(function ($limit) use ($limiterName) {
|
||||
return (object) [
|
||||
'key' => self::$shouldHashKeys ? md5($limiterName.$limit->key) : $limiterName.':'.$limit->key,
|
||||
'maxAttempts' => $limit->maxAttempts,
|
||||
'decaySeconds' => $limit->decaySeconds,
|
||||
'afterCallback' => $limit->afterCallback,
|
||||
'responseCallback' => $limit->responseCallback,
|
||||
];
|
||||
})->all()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param array $limits
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*
|
||||
* @throws \Illuminate\Http\Exceptions\ThrottleRequestsException
|
||||
*/
|
||||
protected function handleRequest($request, Closure $next, array $limits)
|
||||
{
|
||||
foreach ($limits as $limit) {
|
||||
if ($this->limiter->tooManyAttempts($limit->key, $limit->maxAttempts)) {
|
||||
throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($limits as $limit) {
|
||||
if (! $limit->afterCallback) {
|
||||
$this->limiter->hit($limit->key, $limit->decaySeconds);
|
||||
}
|
||||
}
|
||||
|
||||
$response = $next($request);
|
||||
|
||||
foreach ($limits as $limit) {
|
||||
if ($limit->afterCallback && ($limit->afterCallback)($response)) {
|
||||
$this->limiter->hit($limit->key, $limit->decaySeconds);
|
||||
}
|
||||
|
||||
$response = $this->addHeaders(
|
||||
$response,
|
||||
$limit->maxAttempts,
|
||||
$this->calculateRemainingAttempts($limit->key, $limit->maxAttempts)
|
||||
);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the number of attempts if the user is authenticated or not.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int|string $maxAttempts
|
||||
* @return int
|
||||
*
|
||||
* @throws \Illuminate\Routing\Exceptions\MissingRateLimiterException
|
||||
*/
|
||||
protected function resolveMaxAttempts($request, $maxAttempts)
|
||||
{
|
||||
if (str_contains($maxAttempts, '|')) {
|
||||
$maxAttempts = explode('|', $maxAttempts, 2)[$request->user() ? 1 : 0];
|
||||
}
|
||||
|
||||
if (! is_numeric($maxAttempts) &&
|
||||
$request->user()?->hasAttribute($maxAttempts)
|
||||
) {
|
||||
$maxAttempts = $request->user()->{$maxAttempts};
|
||||
}
|
||||
|
||||
// If we still don't have a numeric value, there was no matching rate limiter...
|
||||
if (! is_numeric($maxAttempts)) {
|
||||
is_null($request->user())
|
||||
? throw MissingRateLimiterException::forLimiter($maxAttempts)
|
||||
: throw MissingRateLimiterException::forLimiterAndUser($maxAttempts, get_class($request->user()));
|
||||
}
|
||||
|
||||
return (int) $maxAttempts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve request signature.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return string
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function resolveRequestSignature($request)
|
||||
{
|
||||
if ($user = $request->user()) {
|
||||
return $this->formatIdentifier($user->getAuthIdentifier());
|
||||
} elseif ($route = $request->route()) {
|
||||
return $this->formatIdentifier($route->getDomain().'|'.$request->ip());
|
||||
}
|
||||
|
||||
throw new RuntimeException('Unable to generate the request signature. Route unavailable.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a 'too many attempts' exception.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $key
|
||||
* @param int $maxAttempts
|
||||
* @param callable|null $responseCallback
|
||||
* @return \Illuminate\Http\Exceptions\ThrottleRequestsException|\Illuminate\Http\Exceptions\HttpResponseException
|
||||
*/
|
||||
protected function buildException($request, $key, $maxAttempts, $responseCallback = null)
|
||||
{
|
||||
$retryAfter = $this->getTimeUntilNextRetry($key);
|
||||
|
||||
$headers = $this->getHeaders(
|
||||
$maxAttempts,
|
||||
$this->calculateRemainingAttempts($key, $maxAttempts, $retryAfter),
|
||||
$retryAfter
|
||||
);
|
||||
|
||||
return is_callable($responseCallback)
|
||||
? new HttpResponseException($responseCallback($request, $headers))
|
||||
: new ThrottleRequestsException('Too Many Attempts.', null, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of seconds until the next retry.
|
||||
*
|
||||
* @param string $key
|
||||
* @return int
|
||||
*/
|
||||
protected function getTimeUntilNextRetry($key)
|
||||
{
|
||||
return $this->limiter->availableIn($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the limit header information to the given response.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Response $response
|
||||
* @param int $maxAttempts
|
||||
* @param int $remainingAttempts
|
||||
* @param int|null $retryAfter
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
protected function addHeaders(Response $response, $maxAttempts, $remainingAttempts, $retryAfter = null)
|
||||
{
|
||||
$response->headers->add(
|
||||
$this->getHeaders($maxAttempts, $remainingAttempts, $retryAfter, $response)
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the limit headers information.
|
||||
*
|
||||
* @param int $maxAttempts
|
||||
* @param int $remainingAttempts
|
||||
* @param int|null $retryAfter
|
||||
* @param \Symfony\Component\HttpFoundation\Response|null $response
|
||||
* @return array
|
||||
*/
|
||||
protected function getHeaders($maxAttempts,
|
||||
$remainingAttempts,
|
||||
$retryAfter = null,
|
||||
?Response $response = null)
|
||||
{
|
||||
if ($response &&
|
||||
! is_null($response->headers->get('X-RateLimit-Remaining')) &&
|
||||
(int) $response->headers->get('X-RateLimit-Remaining') <= (int) $remainingAttempts) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$headers = [
|
||||
'X-RateLimit-Limit' => $maxAttempts,
|
||||
'X-RateLimit-Remaining' => $remainingAttempts,
|
||||
];
|
||||
|
||||
if (! is_null($retryAfter)) {
|
||||
$headers['Retry-After'] = $retryAfter;
|
||||
$headers['X-RateLimit-Reset'] = $this->availableAt($retryAfter);
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of remaining attempts.
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $maxAttempts
|
||||
* @param int|null $retryAfter
|
||||
* @return int
|
||||
*/
|
||||
protected function calculateRemainingAttempts($key, $maxAttempts, $retryAfter = null)
|
||||
{
|
||||
return is_null($retryAfter) ? $this->limiter->retriesLeft($key, $maxAttempts) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given identifier based on the configured hashing settings.
|
||||
*
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
private function formatIdentifier($value)
|
||||
{
|
||||
return self::$shouldHashKeys ? sha1($value) : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether rate limiter keys should be hashed.
|
||||
*
|
||||
* @param bool $shouldHashKeys
|
||||
* @return void
|
||||
*/
|
||||
public static function shouldHashKeys(bool $shouldHashKeys = true)
|
||||
{
|
||||
self::$shouldHashKeys = $shouldHashKeys;
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Routing\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Cache\RateLimiter;
|
||||
use Illuminate\Contracts\Redis\Factory as Redis;
|
||||
use Illuminate\Redis\Limiters\DurationLimiter;
|
||||
|
||||
class ThrottleRequestsWithRedis extends ThrottleRequests
|
||||
{
|
||||
/**
|
||||
* The Redis factory implementation.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Redis\Factory
|
||||
*/
|
||||
protected $redis;
|
||||
|
||||
/**
|
||||
* The timestamp of the end of the current duration by key.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $decaysAt = [];
|
||||
|
||||
/**
|
||||
* The number of remaining slots by key.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $remaining = [];
|
||||
|
||||
/**
|
||||
* Create a new request throttler.
|
||||
*
|
||||
* @param \Illuminate\Cache\RateLimiter $limiter
|
||||
* @param \Illuminate\Contracts\Redis\Factory $redis
|
||||
*/
|
||||
public function __construct(RateLimiter $limiter, Redis $redis)
|
||||
{
|
||||
parent::__construct($limiter);
|
||||
|
||||
$this->redis = $redis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param array $limits
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*
|
||||
* @throws \Illuminate\Http\Exceptions\ThrottleRequestsException
|
||||
*/
|
||||
protected function handleRequest($request, Closure $next, array $limits)
|
||||
{
|
||||
foreach ($limits as $limit) {
|
||||
if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decaySeconds)) {
|
||||
throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback);
|
||||
}
|
||||
|
||||
if (! $limit->afterCallback) {
|
||||
$this->hit($limit->key, $limit->maxAttempts, $limit->decaySeconds);
|
||||
}
|
||||
}
|
||||
|
||||
$response = $next($request);
|
||||
|
||||
foreach ($limits as $limit) {
|
||||
if ($limit->afterCallback && ($limit->afterCallback)($response)) {
|
||||
$this->hit($limit->key, $limit->maxAttempts, $limit->decaySeconds);
|
||||
}
|
||||
|
||||
$response = $this->addHeaders(
|
||||
$response,
|
||||
$limit->maxAttempts,
|
||||
$this->calculateRemainingAttempts($limit->key, $limit->maxAttempts)
|
||||
);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given key has been "accessed" too many times.
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $maxAttempts
|
||||
* @param int $decaySeconds
|
||||
* @return bool
|
||||
*/
|
||||
protected function tooManyAttempts($key, $maxAttempts, $decaySeconds)
|
||||
{
|
||||
$limiter = new DurationLimiter(
|
||||
$this->getRedisConnection(), $key, $maxAttempts, $decaySeconds
|
||||
);
|
||||
|
||||
return tap($limiter->tooManyAttempts(), function () use ($key, $limiter) {
|
||||
[$this->decaysAt[$key], $this->remaining[$key]] = [
|
||||
$limiter->decaysAt, $limiter->remaining,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the counter for the given key.
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $maxAttempts
|
||||
* @param int $decaySeconds
|
||||
* @return void
|
||||
*/
|
||||
protected function hit($key, $maxAttempts, $decaySeconds)
|
||||
{
|
||||
$limiter = new DurationLimiter(
|
||||
$this->getRedisConnection(), $key, $maxAttempts, $decaySeconds
|
||||
);
|
||||
|
||||
$limiter->acquire();
|
||||
|
||||
[$this->decaysAt[$key], $this->remaining[$key]] = [
|
||||
$limiter->decaysAt, $limiter->remaining,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of remaining attempts.
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $maxAttempts
|
||||
* @param int|null $retryAfter
|
||||
* @return int
|
||||
*/
|
||||
protected function calculateRemainingAttempts($key, $maxAttempts, $retryAfter = null)
|
||||
{
|
||||
return is_null($retryAfter) ? $this->remaining[$key] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of seconds until the lock is released.
|
||||
*
|
||||
* @param string $key
|
||||
* @return int
|
||||
*/
|
||||
protected function getTimeUntilNextRetry($key)
|
||||
{
|
||||
return $this->decaysAt[$key] - $this->currentTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Redis connection that should be used for throttling.
|
||||
*
|
||||
* @return \Illuminate\Redis\Connections\Connection
|
||||
*/
|
||||
protected function getRedisConnection()
|
||||
{
|
||||
return $this->redis->connection();
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Routing\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Routing\Exceptions\InvalidSignatureException;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class ValidateSignature
|
||||
{
|
||||
/**
|
||||
* The names of the parameters that should be ignored.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $ignore = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* The globally ignored parameters.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $neverValidate = [];
|
||||
|
||||
/**
|
||||
* Specify that the URL signature is for a relative URL.
|
||||
*
|
||||
* @param array|string $ignore
|
||||
* @return string
|
||||
*/
|
||||
public static function relative($ignore = [])
|
||||
{
|
||||
$ignore = Arr::wrap($ignore);
|
||||
|
||||
return static::class.':'.implode(',', empty($ignore) ? ['relative'] : ['relative', ...$ignore]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that the URL signature is for an absolute URL.
|
||||
*
|
||||
* @param array|string $ignore
|
||||
* @return class-string
|
||||
*/
|
||||
public static function absolute($ignore = [])
|
||||
{
|
||||
$ignore = Arr::wrap($ignore);
|
||||
|
||||
return empty($ignore)
|
||||
? static::class
|
||||
: static::class.':'.implode(',', $ignore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param array|null $args
|
||||
* @return \Illuminate\Http\Response
|
||||
*
|
||||
* @throws \Illuminate\Routing\Exceptions\InvalidSignatureException
|
||||
*/
|
||||
public function handle($request, Closure $next, ...$args)
|
||||
{
|
||||
[$relative, $ignore] = $this->parseArguments($args);
|
||||
|
||||
if ($request->hasValidSignatureWhileIgnoring($ignore, ! $relative)) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
throw new InvalidSignatureException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the additional arguments given to the middleware.
|
||||
*
|
||||
* @param array $args
|
||||
* @return array
|
||||
*/
|
||||
protected function parseArguments(array $args)
|
||||
{
|
||||
$relative = ! empty($args) && $args[0] === 'relative';
|
||||
|
||||
if ($relative) {
|
||||
array_shift($args);
|
||||
}
|
||||
|
||||
$ignore = array_merge(
|
||||
property_exists($this, 'except') ? $this->except : $this->ignore,
|
||||
$args
|
||||
);
|
||||
|
||||
return [$relative, array_merge($ignore, static::$neverValidate)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the given parameters should be ignored during signature validation.
|
||||
*
|
||||
* @param array|string $parameters
|
||||
* @return void
|
||||
*/
|
||||
public static function except($parameters)
|
||||
{
|
||||
static::$neverValidate = array_values(array_unique(
|
||||
array_merge(static::$neverValidate, Arr::wrap($parameters))
|
||||
));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user