Upgrade framework

This commit is contained in:
2023-11-14 16:54:35 +01:00
parent 1648a5cd42
commit 4fcf6fffcc
10548 changed files with 693138 additions and 466698 deletions

View File

@@ -3,8 +3,112 @@
namespace Illuminate\Auth\Access;
use Exception;
use Throwable;
class AuthorizationException extends Exception
{
//
/**
* The response from the gate.
*
* @var \Illuminate\Auth\Access\Response
*/
protected $response;
/**
* The HTTP response status code.
*
* @var int|null
*/
protected $status;
/**
* Create a new authorization exception instance.
*
* @param string|null $message
* @param mixed $code
* @param \Throwable|null $previous
* @return void
*/
public function __construct($message = null, $code = null, Throwable $previous = null)
{
parent::__construct($message ?? 'This action is unauthorized.', 0, $previous);
$this->code = $code ?: 0;
}
/**
* Get the response from the gate.
*
* @return \Illuminate\Auth\Access\Response
*/
public function response()
{
return $this->response;
}
/**
* Set the response from the gate.
*
* @param \Illuminate\Auth\Access\Response $response
* @return $this
*/
public function setResponse($response)
{
$this->response = $response;
return $this;
}
/**
* Set the HTTP response status code.
*
* @param int|null $status
* @return $this
*/
public function withStatus($status)
{
$this->status = $status;
return $this;
}
/**
* Set the HTTP response status code to 404.
*
* @return $this
*/
public function asNotFound()
{
return $this->withStatus(404);
}
/**
* Determine if the HTTP status code has been set.
*
* @return bool
*/
public function hasStatus()
{
return $this->status !== null;
}
/**
* Get the HTTP status code.
*
* @return int|null
*/
public function status()
{
return $this->status;
}
/**
* Create a deny response object from this exception.
*
* @return \Illuminate\Auth\Access\Response
*/
public function toResponse()
{
return Response::deny($this->message, $this->code)->withStatus($this->status);
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Illuminate\Auth\Access\Events;
class GateEvaluated
{
/**
* The authenticatable model.
*
* @var \Illuminate\Contracts\Auth\Authenticatable|null
*/
public $user;
/**
* The ability being evaluated.
*
* @var string
*/
public $ability;
/**
* The result of the evaluation.
*
* @var bool|null
*/
public $result;
/**
* The arguments given during evaluation.
*
* @var array
*/
public $arguments;
/**
* Create a new event instance.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param string $ability
* @param bool|null $result
* @param array $arguments
* @return void
*/
public function __construct($user, $ability, $result, $arguments)
{
$this->user = $user;
$this->ability = $ability;
$this->result = $result;
$this->arguments = $arguments;
}
}

View File

@@ -2,10 +2,18 @@
namespace Illuminate\Auth\Access;
use Closure;
use Exception;
use Illuminate\Auth\Access\Events\GateEvaluated;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use InvalidArgumentException;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use ReflectionClass;
use ReflectionFunction;
class Gate implements GateContract
{
@@ -53,6 +61,20 @@ class Gate implements GateContract
*/
protected $afterCallbacks = [];
/**
* All of the defined abilities using class@method notation.
*
* @var array
*/
protected $stringCallbacks = [];
/**
* The callback to be used to guess policy names.
*
* @var callable|null
*/
protected $guessPolicyNamesUsingCallback;
/**
* Create a new gate instance.
*
@@ -62,10 +84,12 @@ class Gate implements GateContract
* @param array $policies
* @param array $beforeCallbacks
* @param array $afterCallbacks
* @param callable|null $guessPolicyNamesUsingCallback
* @return void
*/
public function __construct(Container $container, callable $userResolver, array $abilities = [],
array $policies = [], array $beforeCallbacks = [], array $afterCallbacks = [])
array $policies = [], array $beforeCallbacks = [], array $afterCallbacks = [],
callable $guessPolicyNamesUsingCallback = null)
{
$this->policies = $policies;
$this->container = $container;
@@ -73,36 +97,109 @@ class Gate implements GateContract
$this->userResolver = $userResolver;
$this->afterCallbacks = $afterCallbacks;
$this->beforeCallbacks = $beforeCallbacks;
$this->guessPolicyNamesUsingCallback = $guessPolicyNamesUsingCallback;
}
/**
* Determine if a given ability has been defined.
*
* @param string $ability
* @param string|array $ability
* @return bool
*/
public function has($ability)
{
return isset($this->abilities[$ability]);
$abilities = is_array($ability) ? $ability : func_get_args();
foreach ($abilities as $ability) {
if (! isset($this->abilities[$ability])) {
return false;
}
}
return true;
}
/**
* Perform an on-demand authorization check. Throw an authorization exception if the condition or callback is false.
*
* @param \Illuminate\Auth\Access\Response|\Closure|bool $condition
* @param string|null $message
* @param string|null $code
* @return \Illuminate\Auth\Access\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function allowIf($condition, $message = null, $code = null)
{
return $this->authorizeOnDemand($condition, $message, $code, true);
}
/**
* Perform an on-demand authorization check. Throw an authorization exception if the condition or callback is true.
*
* @param \Illuminate\Auth\Access\Response|\Closure|bool $condition
* @param string|null $message
* @param string|null $code
* @return \Illuminate\Auth\Access\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function denyIf($condition, $message = null, $code = null)
{
return $this->authorizeOnDemand($condition, $message, $code, false);
}
/**
* Authorize a given condition or callback.
*
* @param \Illuminate\Auth\Access\Response|\Closure|bool $condition
* @param string|null $message
* @param string|null $code
* @param bool $allowWhenResponseIs
* @return \Illuminate\Auth\Access\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
protected function authorizeOnDemand($condition, $message, $code, $allowWhenResponseIs)
{
$user = $this->resolveUser();
if ($condition instanceof Closure) {
$response = $this->canBeCalledWithUser($user, $condition)
? $condition($user)
: new Response(false, $message, $code);
} else {
$response = $condition;
}
return with($response instanceof Response ? $response : new Response(
(bool) $response === $allowWhenResponseIs, $message, $code
))->authorize();
}
/**
* Define a new ability.
*
* @param string $ability
* @param callable|string $callback
* @param callable|array|string $callback
* @return $this
*
* @throws \InvalidArgumentException
*/
public function define($ability, $callback)
{
if (is_array($callback) && isset($callback[0]) && is_string($callback[0])) {
$callback = $callback[0].'@'.$callback[1];
}
if (is_callable($callback)) {
$this->abilities[$ability] = $callback;
} elseif (is_string($callback) && Str::contains($callback, '@')) {
$this->abilities[$ability] = $this->buildAbilityCallback($callback);
} elseif (is_string($callback)) {
$this->stringCallbacks[$ability] = $callback;
$this->abilities[$ability] = $this->buildAbilityCallback($ability, $callback);
} else {
throw new InvalidArgumentException("Callback must be a callable or a 'Class@method' string.");
throw new InvalidArgumentException("Callback must be a callable, callback array, or a 'Class@method' string.");
}
return $this;
@@ -113,13 +210,14 @@ class Gate implements GateContract
*
* @param string $name
* @param string $class
* @param array $abilities
* @param array|null $abilities
* @return $this
*/
public function resource($name, $class, array $abilities = null)
{
$abilities = $abilities ?: [
'view' => 'view',
'viewAny' => 'viewAny',
'view' => 'view',
'create' => 'create',
'update' => 'update',
'delete' => 'delete',
@@ -135,15 +233,36 @@ class Gate implements GateContract
/**
* Create the ability callback for a callback string.
*
* @param string $ability
* @param string $callback
* @return \Closure
*/
protected function buildAbilityCallback($callback)
protected function buildAbilityCallback($ability, $callback)
{
return function () use ($callback) {
list($class, $method) = Str::parseCallback($callback);
return function () use ($ability, $callback) {
if (str_contains($callback, '@')) {
[$class, $method] = Str::parseCallback($callback);
} else {
$class = $callback;
}
return $this->resolvePolicy($class)->{$method}(...func_get_args());
$policy = $this->resolvePolicy($class);
$arguments = func_get_args();
$user = array_shift($arguments);
$result = $this->callPolicyBefore(
$policy, $user, $ability, $arguments
);
if (! is_null($result)) {
return $result;
}
return isset($method)
? $policy->{$method}(...func_get_args())
: $policy(...func_get_args());
};
}
@@ -212,19 +331,41 @@ class Gate implements GateContract
}
/**
* Determine if the given ability should be granted for the current user.
* Determine if all of the given abilities should be granted for the current user.
*
* @param string $ability
* @param iterable|string $abilities
* @param array|mixed $arguments
* @return bool
*/
public function check($ability, $arguments = [])
public function check($abilities, $arguments = [])
{
try {
return (bool) $this->raw($ability, $arguments);
} catch (AuthorizationException $e) {
return false;
}
return collect($abilities)->every(
fn ($ability) => $this->inspect($ability, $arguments)->allowed()
);
}
/**
* Determine if any one of the given abilities should be granted for the current user.
*
* @param iterable|string $abilities
* @param array|mixed $arguments
* @return bool
*/
public function any($abilities, $arguments = [])
{
return collect($abilities)->contains(fn ($ability) => $this->check($ability, $arguments));
}
/**
* Determine if all of the given abilities should be denied for the current user.
*
* @param iterable|string $abilities
* @param array|mixed $arguments
* @return bool
*/
public function none($abilities, $arguments = [])
{
return ! $this->any($abilities, $arguments);
}
/**
@@ -238,13 +379,29 @@ class Gate implements GateContract
*/
public function authorize($ability, $arguments = [])
{
$result = $this->raw($ability, $arguments);
return $this->inspect($ability, $arguments)->authorize();
}
if ($result instanceof Response) {
return $result;
/**
* Inspect the user for the given ability.
*
* @param string $ability
* @param array|mixed $arguments
* @return \Illuminate\Auth\Access\Response
*/
public function inspect($ability, $arguments = [])
{
try {
$result = $this->raw($ability, $arguments);
if ($result instanceof Response) {
return $result;
}
return $result ? Response::allow() : Response::deny();
} catch (AuthorizationException $e) {
return $e->toResponse();
}
return $result ? $this->allow() : $this->deny();
}
/**
@@ -253,14 +410,14 @@ class Gate implements GateContract
* @param string $ability
* @param array|mixed $arguments
* @return mixed
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
protected function raw($ability, $arguments = [])
public function raw($ability, $arguments = [])
{
if (! $user = $this->resolveUser()) {
return false;
}
$arguments = Arr::wrap($arguments);
$arguments = array_wrap($arguments);
$user = $this->resolveUser();
// First we will call the "before" callbacks for the Gate. If any of these give
// back a non-null response, we will immediately return that result in order
@@ -276,17 +433,97 @@ class Gate implements GateContract
// After calling the authorization callback, we will call the "after" callbacks
// that are registered with the Gate, which allows a developer to do logging
// if that is required for this application. Then we'll return the result.
$this->callAfterCallbacks(
return tap($this->callAfterCallbacks(
$user, $ability, $arguments, $result
);
), function ($result) use ($user, $ability, $arguments) {
$this->dispatchGateEvaluatedEvent($user, $ability, $arguments, $result);
});
}
return $result;
/**
* Determine whether the callback/method can be called with the given user.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param \Closure|string|array $class
* @param string|null $method
* @return bool
*/
protected function canBeCalledWithUser($user, $class, $method = null)
{
if (! is_null($user)) {
return true;
}
if (! is_null($method)) {
return $this->methodAllowsGuests($class, $method);
}
if (is_array($class)) {
$className = is_string($class[0]) ? $class[0] : get_class($class[0]);
return $this->methodAllowsGuests($className, $class[1]);
}
return $this->callbackAllowsGuests($class);
}
/**
* Determine if the given class method allows guests.
*
* @param string $class
* @param string $method
* @return bool
*/
protected function methodAllowsGuests($class, $method)
{
try {
$reflection = new ReflectionClass($class);
$method = $reflection->getMethod($method);
} catch (Exception $e) {
return false;
}
if ($method) {
$parameters = $method->getParameters();
return isset($parameters[0]) && $this->parameterAllowsGuests($parameters[0]);
}
return false;
}
/**
* Determine if the callback allows guests.
*
* @param callable $callback
* @return bool
*
* @throws \ReflectionException
*/
protected function callbackAllowsGuests($callback)
{
$parameters = (new ReflectionFunction($callback))->getParameters();
return isset($parameters[0]) && $this->parameterAllowsGuests($parameters[0]);
}
/**
* Determine if the given parameter allows guests.
*
* @param \ReflectionParameter $parameter
* @return bool
*/
protected function parameterAllowsGuests($parameter)
{
return ($parameter->hasType() && $parameter->allowsNull()) ||
($parameter->isDefaultValueAvailable() && is_null($parameter->getDefaultValue()));
}
/**
* Resolve and call the appropriate authorization callback.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param string $ability
* @param array $arguments
* @return bool
@@ -301,17 +538,19 @@ class Gate implements GateContract
/**
* Call all of the before callbacks and return if a result is given.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param string $ability
* @param array $arguments
* @return bool|null
*/
protected function callBeforeCallbacks($user, $ability, array $arguments)
{
$arguments = array_merge([$user, $ability], [$arguments]);
foreach ($this->beforeCallbacks as $before) {
if (! is_null($result = $before(...$arguments))) {
if (! $this->canBeCalledWithUser($user, $before)) {
continue;
}
if (! is_null($result = $before($user, $ability, $arguments))) {
return $result;
}
}
@@ -324,39 +563,72 @@ class Gate implements GateContract
* @param string $ability
* @param array $arguments
* @param bool $result
* @return void
* @return bool|null
*/
protected function callAfterCallbacks($user, $ability, array $arguments, $result)
{
$arguments = array_merge([$user, $ability, $result], [$arguments]);
foreach ($this->afterCallbacks as $after) {
$after(...$arguments);
if (! $this->canBeCalledWithUser($user, $after)) {
continue;
}
$afterResult = $after($user, $ability, $result, $arguments);
$result ??= $afterResult;
}
return $result;
}
/**
* Dispatch a gate evaluation event.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param string $ability
* @param array $arguments
* @param bool|null $result
* @return void
*/
protected function dispatchGateEvaluatedEvent($user, $ability, array $arguments, $result)
{
if ($this->container->bound(Dispatcher::class)) {
$this->container->make(Dispatcher::class)->dispatch(
new GateEvaluated($user, $ability, $result, $arguments)
);
}
}
/**
* Resolve the callable for the given ability and arguments.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param string $ability
* @param array $arguments
* @return callable
*/
protected function resolveAuthCallback($user, $ability, array $arguments)
{
if (isset($arguments[0])) {
if (! is_null($policy = $this->getPolicyFor($arguments[0]))) {
return $this->resolvePolicyCallback($user, $ability, $arguments, $policy);
if (isset($arguments[0]) &&
! is_null($policy = $this->getPolicyFor($arguments[0])) &&
$callback = $this->resolvePolicyCallback($user, $ability, $arguments, $policy)) {
return $callback;
}
if (isset($this->stringCallbacks[$ability])) {
[$class, $method] = Str::parseCallback($this->stringCallbacks[$ability]);
if ($this->canBeCalledWithUser($user, $class, $method ?: '__invoke')) {
return $this->abilities[$ability];
}
}
if (isset($this->abilities[$ability])) {
if (isset($this->abilities[$ability]) &&
$this->canBeCalledWithUser($user, $this->abilities[$ability])) {
return $this->abilities[$ability];
}
return function () {
return false;
//
};
}
@@ -373,13 +645,19 @@ class Gate implements GateContract
}
if (! is_string($class)) {
return null;
return;
}
if (isset($this->policies[$class])) {
return $this->resolvePolicy($this->policies[$class]);
}
foreach ($this->guessPolicyName($class) as $guessedPolicy) {
if (class_exists($guessedPolicy)) {
return $this->resolvePolicy($guessedPolicy);
}
}
foreach ($this->policies as $expected => $policy) {
if (is_subclass_of($class, $expected)) {
return $this->resolvePolicy($policy);
@@ -387,11 +665,51 @@ class Gate implements GateContract
}
}
/**
* Guess the policy name for the given class.
*
* @param string $class
* @return array
*/
protected function guessPolicyName($class)
{
if ($this->guessPolicyNamesUsingCallback) {
return Arr::wrap(call_user_func($this->guessPolicyNamesUsingCallback, $class));
}
$classDirname = str_replace('/', '\\', dirname(str_replace('\\', '/', $class)));
$classDirnameSegments = explode('\\', $classDirname);
return Arr::wrap(Collection::times(count($classDirnameSegments), function ($index) use ($class, $classDirnameSegments) {
$classDirname = implode('\\', array_slice($classDirnameSegments, 0, $index));
return $classDirname.'\\Policies\\'.class_basename($class).'Policy';
})->reverse()->values()->first(function ($class) {
return class_exists($class);
}) ?: [$classDirname.'\\Policies\\'.class_basename($class).'Policy']);
}
/**
* Specify a callback to be used to guess policy names.
*
* @param callable $callback
* @return $this
*/
public function guessPolicyNamesUsing(callable $callback)
{
$this->guessPolicyNamesUsingCallback = $callback;
return $this;
}
/**
* Build a policy class instance of the given type.
*
* @param object|string $class
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function resolvePolicy($class)
{
@@ -405,10 +723,14 @@ class Gate implements GateContract
* @param string $ability
* @param array $arguments
* @param mixed $policy
* @return callable
* @return bool|callable
*/
protected function resolvePolicyCallback($user, $ability, array $arguments, $policy)
{
if (! is_callable([$policy, $this->formatAbilityToMethod($ability)])) {
return false;
}
return function () use ($user, $ability, $arguments, $policy) {
// This callback will be responsible for calling the policy's before method and
// running this policy method if necessary. This is used to when objects are
@@ -424,18 +746,9 @@ class Gate implements GateContract
return $result;
}
$ability = $this->formatAbilityToMethod($ability);
$method = $this->formatAbilityToMethod($ability);
// If this first argument is a string, that means they are passing a class name
// to the policy. We will remove the first argument from this argument array
// because this policy already knows what type of models it can authorize.
if (isset($arguments[0]) && is_string($arguments[0])) {
array_shift($arguments);
}
return is_callable([$policy, $ability])
? $policy->{$ability}($user, ...$arguments)
: false;
return $this->callPolicyMethod($policy, $method, $user, $arguments);
};
}
@@ -450,11 +763,42 @@ class Gate implements GateContract
*/
protected function callPolicyBefore($policy, $user, $ability, $arguments)
{
if (method_exists($policy, 'before')) {
if (! method_exists($policy, 'before')) {
return;
}
if ($this->canBeCalledWithUser($user, $policy, 'before')) {
return $policy->before($user, $ability, ...$arguments);
}
}
/**
* Call the appropriate method on the given policy.
*
* @param mixed $policy
* @param string $method
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param array $arguments
* @return mixed
*/
protected function callPolicyMethod($policy, $method, $user, array $arguments)
{
// If this first argument is a string, that means they are passing a class name
// to the policy. We will remove the first argument from this argument array
// because this policy already knows what type of models it can authorize.
if (isset($arguments[0]) && is_string($arguments[0])) {
array_shift($arguments);
}
if (! is_callable([$policy, $method])) {
return;
}
if ($this->canBeCalledWithUser($user, $policy, $method)) {
return $policy->{$method}($user, ...$arguments);
}
}
/**
* Format the policy ability into a method name.
*
@@ -463,7 +807,7 @@ class Gate implements GateContract
*/
protected function formatAbilityToMethod($ability)
{
return strpos($ability, '-') !== false ? Str::camel($ability) : $ability;
return str_contains($ability, '-') ? Str::camel($ability) : $ability;
}
/**
@@ -474,13 +818,12 @@ class Gate implements GateContract
*/
public function forUser($user)
{
$callback = function () use ($user) {
return $user;
};
$callback = fn () => $user;
return new static(
$this->container, $callback, $this->abilities,
$this->policies, $this->beforeCallbacks, $this->afterCallbacks
$this->policies, $this->beforeCallbacks, $this->afterCallbacks,
$this->guessPolicyNamesUsingCallback
);
}
@@ -503,4 +846,27 @@ class Gate implements GateContract
{
return $this->abilities;
}
/**
* Get all of the defined policies.
*
* @return array
*/
public function policies()
{
return $this->policies;
}
/**
* Set the container instance used by the gate.
*
* @param \Illuminate\Contracts\Container\Container $container
* @return $this
*/
public function setContainer(Container $container)
{
$this->container = $container;
return $this;
}
}

View File

@@ -8,23 +8,48 @@ trait HandlesAuthorization
* Create a new access response.
*
* @param string|null $message
* @param mixed $code
* @return \Illuminate\Auth\Access\Response
*/
protected function allow($message = null)
protected function allow($message = null, $code = null)
{
return new Response($message);
return Response::allow($message, $code);
}
/**
* Throws an unauthorized exception.
*
* @param string $message
* @return void
*
* @throws \Illuminate\Auth\Access\AuthorizationException
* @param string|null $message
* @param mixed|null $code
* @return \Illuminate\Auth\Access\Response
*/
protected function deny($message = 'This action is unauthorized.')
protected function deny($message = null, $code = null)
{
throw new AuthorizationException($message);
return Response::deny($message, $code);
}
/**
* Deny with a HTTP status code.
*
* @param int $status
* @param ?string $message
* @param ?int $code
* @return \Illuminate\Auth\Access\Response
*/
public function denyWithStatus($status, $message = null, $code = null)
{
return Response::denyWithStatus($status, $message, $code);
}
/**
* Deny with a 404 HTTP status code.
*
* @param ?string $message
* @param ?int $code
* @return \Illuminate\Auth\Access\Response
*/
public function denyAsNotFound($message = null, $code = null)
{
return Response::denyWithStatus(404, $message, $code);
}
}

View File

@@ -2,8 +2,17 @@
namespace Illuminate\Auth\Access;
class Response
use Illuminate\Contracts\Support\Arrayable;
class Response implements Arrayable
{
/**
* Indicates whether the response was allowed.
*
* @var bool
*/
protected $allowed;
/**
* The response message.
*
@@ -11,16 +20,104 @@ class Response
*/
protected $message;
/**
* The response code.
*
* @var mixed
*/
protected $code;
/**
* The HTTP response status code.
*
* @var int|null
*/
protected $status;
/**
* Create a new response.
*
* @param string|null $message
* @param bool $allowed
* @param string $message
* @param mixed $code
* @return void
*/
public function __construct($message = null)
public function __construct($allowed, $message = '', $code = null)
{
$this->code = $code;
$this->allowed = $allowed;
$this->message = $message;
}
/**
* Create a new "allow" Response.
*
* @param string|null $message
* @param mixed $code
* @return \Illuminate\Auth\Access\Response
*/
public static function allow($message = null, $code = null)
{
return new static(true, $message, $code);
}
/**
* Create a new "deny" Response.
*
* @param string|null $message
* @param mixed $code
* @return \Illuminate\Auth\Access\Response
*/
public static function deny($message = null, $code = null)
{
return new static(false, $message, $code);
}
/**
* Create a new "deny" Response with a HTTP status code.
*
* @param int $status
* @param string|null $message
* @param mixed $code
* @return \Illuminate\Auth\Access\Response
*/
public static function denyWithStatus($status, $message = null, $code = null)
{
return static::deny($message, $code)->withStatus($status);
}
/**
* Create a new "deny" Response with a 404 HTTP status code.
*
* @param string|null $message
* @param mixed $code
* @return \Illuminate\Auth\Access\Response
*/
public static function denyAsNotFound($message = null, $code = null)
{
return static::denyWithStatus(404, $message, $code);
}
/**
* Determine if the response was allowed.
*
* @return bool
*/
public function allowed()
{
return $this->allowed;
}
/**
* Determine if the response was denied.
*
* @return bool
*/
public function denied()
{
return ! $this->allowed();
}
/**
* Get the response message.
*
@@ -31,6 +128,81 @@ class Response
return $this->message;
}
/**
* Get the response code / reason.
*
* @return mixed
*/
public function code()
{
return $this->code;
}
/**
* Throw authorization exception if response was denied.
*
* @return \Illuminate\Auth\Access\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function authorize()
{
if ($this->denied()) {
throw (new AuthorizationException($this->message(), $this->code()))
->setResponse($this)
->withStatus($this->status);
}
return $this;
}
/**
* Set the HTTP response status code.
*
* @param null|int $status
* @return $this
*/
public function withStatus($status)
{
$this->status = $status;
return $this;
}
/**
* Set the HTTP response status code to 404.
*
* @return $this
*/
public function asNotFound()
{
return $this->withStatus(404);
}
/**
* Get the HTTP status code.
*
* @return int|null
*/
public function status()
{
return $this->status;
}
/**
* Convert the response to an array.
*
* @return array
*/
public function toArray()
{
return [
'allowed' => $this->allowed(),
'message' => $this->message(),
'code' => $this->code(),
];
}
/**
* Get the string representation of the message.
*
@@ -38,6 +210,6 @@ class Response
*/
public function __toString()
{
return $this->message();
return (string) $this->message();
}
}

View File

@@ -3,9 +3,13 @@
namespace Illuminate\Auth;
use Closure;
use InvalidArgumentException;
use Illuminate\Contracts\Auth\Factory as FactoryContract;
use InvalidArgumentException;
/**
* @mixin \Illuminate\Contracts\Auth\Guard
* @mixin \Illuminate\Contracts\Auth\StatefulGuard
*/
class AuthManager implements FactoryContract
{
use CreatesUserProviders;
@@ -13,7 +17,7 @@ class AuthManager implements FactoryContract
/**
* The application instance.
*
* @var \Illuminate\Foundation\Application
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
@@ -43,31 +47,27 @@ class AuthManager implements FactoryContract
/**
* Create a new Auth manager instance.
*
* @param \Illuminate\Foundation\Application $app
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function __construct($app)
{
$this->app = $app;
$this->userResolver = function ($guard = null) {
return $this->guard($guard)->user();
};
$this->userResolver = fn ($guard = null) => $this->guard($guard)->user();
}
/**
* Attempt to get the guard from the local cache.
*
* @param string $name
* @param string|null $name
* @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
*/
public function guard($name = null)
{
$name = $name ?: $this->getDefaultDriver();
return isset($this->guards[$name])
? $this->guards[$name]
: $this->guards[$name] = $this->resolve($name);
return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
}
/**
@@ -96,7 +96,9 @@ class AuthManager implements FactoryContract
return $this->{$driverMethod}($name, $config);
}
throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined.");
throw new InvalidArgumentException(
"Auth driver [{$config['driver']}] for guard [{$name}] is not defined."
);
}
/**
@@ -120,9 +122,13 @@ class AuthManager implements FactoryContract
*/
public function createSessionDriver($name, $config)
{
$provider = $this->createUserProvider($config['provider']);
$provider = $this->createUserProvider($config['provider'] ?? null);
$guard = new SessionGuard($name, $provider, $this->app['session.store']);
$guard = new SessionGuard(
$name,
$provider,
$this->app['session.store'],
);
// When using the remember me functionality of the authentication services we
// will need to be set the encryption instance of the guard, which allows
@@ -139,6 +145,10 @@ class AuthManager implements FactoryContract
$guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));
}
if (isset($config['remember'])) {
$guard->setRememberDuration($config['remember']);
}
return $guard;
}
@@ -155,8 +165,11 @@ class AuthManager implements FactoryContract
// that takes an API token field from the request and matches it to the
// user in the database or another persistence layer where users are.
$guard = new TokenGuard(
$this->createUserProvider($config['provider']),
$this->app['request']
$this->createUserProvider($config['provider'] ?? null),
$this->app['request'],
$config['input_key'] ?? 'api_token',
$config['storage_key'] ?? 'api_token',
$config['hash'] ?? false
);
$this->app->refresh('request', $guard, 'setRequest');
@@ -197,9 +210,7 @@ class AuthManager implements FactoryContract
$this->setDefaultDriver($name);
$this->userResolver = function ($name = null) {
return $this->guard($name)->user();
};
$this->userResolver = fn ($name = null) => $this->guard($name)->user();
}
/**
@@ -223,7 +234,7 @@ class AuthManager implements FactoryContract
public function viaRequest($driver, callable $callback)
{
return $this->extend($driver, function () use ($callback) {
$guard = new RequestGuard($callback, $this->app['request']);
$guard = new RequestGuard($callback, $this->app['request'], $this->createUserProvider());
$this->app->refresh('request', $guard, 'setRequest');
@@ -282,6 +293,41 @@ class AuthManager implements FactoryContract
return $this;
}
/**
* Determines if any guards have already been resolved.
*
* @return bool
*/
public function hasResolvedGuards()
{
return count($this->guards) > 0;
}
/**
* Forget all of the resolved guard instances.
*
* @return $this
*/
public function forgetGuards()
{
$this->guards = [];
return $this;
}
/**
* Set the application instance used by the manager.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return $this
*/
public function setApplication($app)
{
$this->app = $app;
return $this;
}
/**
* Dynamically call the default driver instance.
*

View File

@@ -3,9 +3,12 @@
namespace Illuminate\Auth;
use Illuminate\Auth\Access\Gate;
use Illuminate\Support\ServiceProvider;
use Illuminate\Auth\Middleware\RequirePassword;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Contracts\Routing\UrlGenerator;
use Illuminate\Support\ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
@@ -17,12 +20,11 @@ class AuthServiceProvider extends ServiceProvider
public function register()
{
$this->registerAuthenticator();
$this->registerUserResolver();
$this->registerAccessGate();
$this->registerRequirePassword();
$this->registerRequestRebindHandler();
$this->registerEventRebindHandler();
}
/**
@@ -32,18 +34,9 @@ class AuthServiceProvider extends ServiceProvider
*/
protected function registerAuthenticator()
{
$this->app->singleton('auth', function ($app) {
// Once the authentication service has actually been requested by the developer
// we will set a variable in the application indicating such. This helps us
// know that we need to set any queued cookies in the after event later.
$app['auth.loaded'] = true;
$this->app->singleton('auth', fn ($app) => new AuthManager($app));
return new AuthManager($app);
});
$this->app->singleton('auth.driver', function ($app) {
return $app['auth']->guard();
});
$this->app->singleton('auth.driver', fn ($app) => $app['auth']->guard());
}
/**
@@ -53,11 +46,7 @@ class AuthServiceProvider extends ServiceProvider
*/
protected function registerUserResolver()
{
$this->app->bind(
AuthenticatableContract::class, function ($app) {
return call_user_func($app['auth']->userResolver());
}
);
$this->app->bind(AuthenticatableContract::class, fn ($app) => call_user_func($app['auth']->userResolver()));
}
/**
@@ -68,9 +57,7 @@ class AuthServiceProvider extends ServiceProvider
protected function registerAccessGate()
{
$this->app->singleton(GateContract::class, function ($app) {
return new Gate($app, function () use ($app) {
return call_user_func($app['auth']->userResolver());
});
return new Gate($app, fn () => call_user_func($app['auth']->userResolver()));
});
}
@@ -79,6 +66,22 @@ class AuthServiceProvider extends ServiceProvider
*
* @return void
*/
protected function registerRequirePassword()
{
$this->app->bind(RequirePassword::class, function ($app) {
return new RequirePassword(
$app[ResponseFactory::class],
$app[UrlGenerator::class],
$app['config']->get('auth.password_timeout')
);
});
}
/**
* Handle the re-binding of the request binding.
*
* @return void
*/
protected function registerRequestRebindHandler()
{
$this->app->rebinding('request', function ($app, $request) {
@@ -87,4 +90,23 @@ class AuthServiceProvider extends ServiceProvider
});
});
}
/**
* Handle the re-binding of the event dispatcher binding.
*
* @return void
*/
protected function registerEventRebindHandler()
{
$this->app->rebinding('events', function ($app, $dispatcher) {
if (! $app->resolved('auth') ||
$app['auth']->hasResolvedGuards() === false) {
return;
}
if (method_exists($guard = $app['auth']->guard(), 'setDispatcher')) {
$guard->setDispatcher($dispatcher);
}
});
}
}

View File

@@ -31,6 +31,16 @@ trait Authenticatable
return $this->{$this->getAuthIdentifierName()};
}
/**
* Get the unique broadcast identifier for the user.
*
* @return mixed
*/
public function getAuthIdentifierForBroadcasting()
{
return $this->getAuthIdentifier();
}
/**
* Get the password for the user.
*
@@ -44,12 +54,12 @@ trait Authenticatable
/**
* Get the token value for the "remember me" session.
*
* @return string
* @return string|null
*/
public function getRememberToken()
{
if (! empty($this->getRememberTokenName())) {
return $this->{$this->getRememberTokenName()};
return (string) $this->{$this->getRememberTokenName()};
}
}

View File

@@ -13,18 +13,27 @@ class AuthenticationException extends Exception
*/
protected $guards;
/**
* The path the user should be redirected to.
*
* @var string|null
*/
protected $redirectTo;
/**
* Create a new authentication exception.
*
* @param string $message
* @param array $guards
* @param string|null $redirectTo
* @return void
*/
public function __construct($message = 'Unauthenticated.', array $guards = [])
public function __construct($message = 'Unauthenticated.', array $guards = [], $redirectTo = null)
{
parent::__construct($message);
$this->guards = $guards;
$this->redirectTo = $redirectTo;
}
/**
@@ -36,4 +45,14 @@ class AuthenticationException extends Exception
{
return $this->guards;
}
/**
* Get the path the user should be redirected to.
*
* @return string|null
*/
public function redirectTo()
{
return $this->redirectTo;
}
}

View File

@@ -3,7 +3,9 @@
namespace Illuminate\Auth\Console;
use Illuminate\Console\Command;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'auth:clear-resets')]
class ClearResetsCommand extends Command
{
/**
@@ -13,6 +15,17 @@ class ClearResetsCommand extends Command
*/
protected $signature = 'auth:clear-resets {name? : The name of the password broker}';
/**
* The name of the console command.
*
* This name is used to identify the command during lazy loading.
*
* @var string|null
*
* @deprecated
*/
protected static $defaultName = 'auth:clear-resets';
/**
* The console command description.
*
@@ -25,10 +38,10 @@ class ClearResetsCommand extends Command
*
* @return void
*/
public function fire()
public function handle()
{
$this->laravel['auth.password']->broker($this->argument('name'))->getRepository()->deleteExpired();
$this->info('Expired reset tokens cleared!');
$this->components->info('Expired reset tokens cleared successfully.');
}
}

View File

@@ -1,119 +0,0 @@
<?php
namespace Illuminate\Auth\Console;
use Illuminate\Console\Command;
use Illuminate\Console\DetectsApplicationNamespace;
class MakeAuthCommand extends Command
{
use DetectsApplicationNamespace;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'make:auth
{--views : Only scaffold the authentication views}
{--force : Overwrite existing views by default}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Scaffold basic login and registration views and routes';
/**
* The views that need to be exported.
*
* @var array
*/
protected $views = [
'auth/login.stub' => 'auth/login.blade.php',
'auth/register.stub' => 'auth/register.blade.php',
'auth/passwords/email.stub' => 'auth/passwords/email.blade.php',
'auth/passwords/reset.stub' => 'auth/passwords/reset.blade.php',
'layouts/app.stub' => 'layouts/app.blade.php',
'home.stub' => 'home.blade.php',
];
/**
* Execute the console command.
*
* @return void
*/
public function fire()
{
$this->createDirectories();
$this->exportViews();
if (! $this->option('views')) {
file_put_contents(
app_path('Http/Controllers/HomeController.php'),
$this->compileControllerStub()
);
file_put_contents(
base_path('routes/web.php'),
file_get_contents(__DIR__.'/stubs/make/routes.stub'),
FILE_APPEND
);
}
$this->info('Authentication scaffolding generated successfully.');
}
/**
* Create the directories for the files.
*
* @return void
*/
protected function createDirectories()
{
if (! is_dir(resource_path('views/layouts'))) {
mkdir(resource_path('views/layouts'), 0755, true);
}
if (! is_dir(resource_path('views/auth/passwords'))) {
mkdir(resource_path('views/auth/passwords'), 0755, true);
}
}
/**
* Export the authentication views.
*
* @return void
*/
protected function exportViews()
{
foreach ($this->views as $key => $value) {
if (file_exists(resource_path('views/'.$value)) && ! $this->option('force')) {
if (! $this->confirm("The [{$value}] view already exists. Do you want to replace it?")) {
continue;
}
}
copy(
__DIR__.'/stubs/make/views/'.$key,
resource_path('views/'.$value)
);
}
}
/**
* Compiles the HomeController stub.
*
* @return string
*/
protected function compileControllerStub()
{
return str_replace(
'{{namespace}}',
$this->getAppNamespace(),
file_get_contents(__DIR__.'/stubs/make/controllers/HomeController.stub')
);
}
}

View File

@@ -1,28 +0,0 @@
<?php
namespace {{namespace}}Http\Controllers;
use Illuminate\Http\Request;
class HomeController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* Show the application dashboard.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return view('home');
}
}

View File

@@ -1,4 +0,0 @@
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');

View File

@@ -1,68 +0,0 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Login</div>
<div class="panel-body">
<form class="form-horizontal" method="POST" action="{{ route('login') }}">
{{ csrf_field() }}
<div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
<label for="email" class="col-md-4 control-label">E-Mail Address</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required autofocus>
@if ($errors->has('email'))
<span class="help-block">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
<label for="password" class="col-md-4 control-label">Password</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control" name="password" required>
@if ($errors->has('password'))
<span class="help-block">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<div class="checkbox">
<label>
<input type="checkbox" name="remember" {{ old('remember') ? 'checked' : '' }}> Remember Me
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-8 col-md-offset-4">
<button type="submit" class="btn btn-primary">
Login
</button>
<a class="btn btn-link" href="{{ route('password.request') }}">
Forgot Your Password?
</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@@ -1,46 +0,0 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Reset Password</div>
<div class="panel-body">
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
<form class="form-horizontal" method="POST" action="{{ route('password.email') }}">
{{ csrf_field() }}
<div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
<label for="email" class="col-md-4 control-label">E-Mail Address</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required>
@if ($errors->has('email'))
<span class="help-block">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<button type="submit" class="btn btn-primary">
Send Password Reset Link
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@@ -1,76 +0,0 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Reset Password</div>
<div class="panel-body">
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
<form class="form-horizontal" method="POST" action="{{ route('password.request') }}">
{{ csrf_field() }}
<input type="hidden" name="token" value="{{ $token }}">
<div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
<label for="email" class="col-md-4 control-label">E-Mail Address</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" value="{{ $email or old('email') }}" required autofocus>
@if ($errors->has('email'))
<span class="help-block">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
<label for="password" class="col-md-4 control-label">Password</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control" name="password" required>
@if ($errors->has('password'))
<span class="help-block">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group{{ $errors->has('password_confirmation') ? ' has-error' : '' }}">
<label for="password-confirm" class="col-md-4 control-label">Confirm Password</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
@if ($errors->has('password_confirmation'))
<span class="help-block">
<strong>{{ $errors->first('password_confirmation') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<button type="submit" class="btn btn-primary">
Reset Password
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@@ -1,76 +0,0 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Register</div>
<div class="panel-body">
<form class="form-horizontal" method="POST" action="{{ route('register') }}">
{{ csrf_field() }}
<div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}">
<label for="name" class="col-md-4 control-label">Name</label>
<div class="col-md-6">
<input id="name" type="text" class="form-control" name="name" value="{{ old('name') }}" required autofocus>
@if ($errors->has('name'))
<span class="help-block">
<strong>{{ $errors->first('name') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
<label for="email" class="col-md-4 control-label">E-Mail Address</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required>
@if ($errors->has('email'))
<span class="help-block">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
<label for="password" class="col-md-4 control-label">Password</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control" name="password" required>
@if ($errors->has('password'))
<span class="help-block">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group">
<label for="password-confirm" class="col-md-4 control-label">Confirm Password</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<button type="submit" class="btn btn-primary">
Register
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@@ -1,17 +0,0 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Dashboard</div>
<div class="panel-body">
You are logged in!
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@@ -1,8 +1,7 @@
<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
@@ -10,71 +9,68 @@
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div id="app">
<nav class="navbar navbar-default navbar-static-top">
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<!-- Collapsed Hamburger -->
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#app-navbar-collapse">
<span class="sr-only">Toggle Navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Branding Image -->
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
</div>
<div class="collapse navbar-collapse" id="app-navbar-collapse">
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="nav navbar-nav">
&nbsp;
<ul class="navbar-nav mr-auto">
</ul>
<!-- Right Side Of Navbar -->
<ul class="nav navbar-nav navbar-right">
<ul class="navbar-nav ml-auto">
<!-- Authentication Links -->
@if (Auth::guest())
<li><a href="{{ route('login') }}">Login</a></li>
<li><a href="{{ route('register') }}">Register</a></li>
@guest
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
@if (Route::has('register'))
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
</li>
@endif
@else
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }} <span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu">
<li>
<a href="{{ route('logout') }}"
onclick="event.preventDefault();
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
Logout
</a>
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
{{ csrf_field() }}
</form>
</li>
</ul>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
</div>
</li>
@endif
@endguest
</ul>
</div>
</div>
</nav>
@yield('content')
<main class="py-4">
@yield('content')
</main>
</div>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}"></script>
</body>
</html>

View File

@@ -16,28 +16,42 @@ trait CreatesUserProviders
/**
* Create the user provider implementation for the driver.
*
* @param string $provider
* @return \Illuminate\Contracts\Auth\UserProvider
* @param string|null $provider
* @return \Illuminate\Contracts\Auth\UserProvider|null
*
* @throws \InvalidArgumentException
*/
public function createUserProvider($provider)
public function createUserProvider($provider = null)
{
$config = $this->app['config']['auth.providers.'.$provider];
if (is_null($config = $this->getProviderConfiguration($provider))) {
return;
}
if (isset($this->customProviderCreators[$config['driver']])) {
if (isset($this->customProviderCreators[$driver = ($config['driver'] ?? null)])) {
return call_user_func(
$this->customProviderCreators[$config['driver']], $this->app, $config
$this->customProviderCreators[$driver], $this->app, $config
);
}
switch ($config['driver']) {
case 'database':
return $this->createDatabaseProvider($config);
case 'eloquent':
return $this->createEloquentProvider($config);
default:
throw new InvalidArgumentException("Authentication user provider [{$config['driver']}] is not defined.");
return match ($driver) {
'database' => $this->createDatabaseProvider($config),
'eloquent' => $this->createEloquentProvider($config),
default => throw new InvalidArgumentException(
"Authentication user provider [{$driver}] is not defined."
),
};
}
/**
* Get the user provider configuration.
*
* @param string|null $provider
* @return array|null
*/
protected function getProviderConfiguration($provider)
{
if ($provider = $provider ?: $this->getDefaultUserProvider()) {
return $this->app['config']['auth.providers.'.$provider];
}
}
@@ -49,7 +63,7 @@ trait CreatesUserProviders
*/
protected function createDatabaseProvider($config)
{
$connection = $this->app['db']->connection();
$connection = $this->app['db']->connection($config['connection'] ?? null);
return new DatabaseUserProvider($connection, $this->app['hash'], $config['table']);
}
@@ -64,4 +78,14 @@ trait CreatesUserProviders
{
return new EloquentUserProvider($this->app['hash'], $config['model']);
}
/**
* Get the default user provider name.
*
* @return string
*/
public function getDefaultUserProvider()
{
return $this->app['config']['auth.defaults.provider'];
}
}

View File

@@ -2,11 +2,12 @@
namespace Illuminate\Auth;
use Illuminate\Support\Str;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Closure;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\ConnectionInterface;
class DatabaseUserProvider implements UserProvider
{
@@ -15,7 +16,7 @@ class DatabaseUserProvider implements UserProvider
*
* @var \Illuminate\Database\ConnectionInterface
*/
protected $conn;
protected $connection;
/**
* The hasher implementation.
@@ -34,14 +35,14 @@ class DatabaseUserProvider implements UserProvider
/**
* Create a new database user provider.
*
* @param \Illuminate\Database\ConnectionInterface $conn
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Illuminate\Contracts\Hashing\Hasher $hasher
* @param string $table
* @return void
*/
public function __construct(ConnectionInterface $conn, HasherContract $hasher, $table)
public function __construct(ConnectionInterface $connection, HasherContract $hasher, $table)
{
$this->conn = $conn;
$this->connection = $connection;
$this->table = $table;
$this->hasher = $hasher;
}
@@ -54,7 +55,7 @@ class DatabaseUserProvider implements UserProvider
*/
public function retrieveById($identifier)
{
$user = $this->conn->table($this->table)->find($identifier);
$user = $this->connection->table($this->table)->find($identifier);
return $this->getGenericUser($user);
}
@@ -68,12 +69,12 @@ class DatabaseUserProvider implements UserProvider
*/
public function retrieveByToken($identifier, $token)
{
$user = $this->conn->table($this->table)
->where('id', $identifier)
->where('remember_token', $token)
->first();
$user = $this->getGenericUser(
$this->connection->table($this->table)->find($identifier)
);
return $this->getGenericUser($user);
return $user && $user->getRememberToken() && hash_equals($user->getRememberToken(), $token)
? $user : null;
}
/**
@@ -85,9 +86,9 @@ class DatabaseUserProvider implements UserProvider
*/
public function updateRememberToken(UserContract $user, $token)
{
$this->conn->table($this->table)
->where('id', $user->getAuthIdentifier())
->update(['remember_token' => $token]);
$this->connection->table($this->table)
->where($user->getAuthIdentifierName(), $user->getAuthIdentifier())
->update([$user->getRememberTokenName() => $token]);
}
/**
@@ -98,20 +99,34 @@ class DatabaseUserProvider implements UserProvider
*/
public function retrieveByCredentials(array $credentials)
{
$credentials = array_filter(
$credentials,
fn ($key) => ! str_contains($key, 'password'),
ARRAY_FILTER_USE_KEY
);
if (empty($credentials)) {
return;
}
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// generic "user" object that will be utilized by the Guard instances.
$query = $this->conn->table($this->table);
$query = $this->connection->table($this->table);
foreach ($credentials as $key => $value) {
if (! Str::contains($key, 'password')) {
if (is_array($value) || $value instanceof Arrayable) {
$query->whereIn($key, $value);
} elseif ($value instanceof Closure) {
$value($query);
} else {
$query->where($key, $value);
}
}
// Now we are ready to execute the query to see if we have an user matching
// the given credentials. If not, we will just return nulls and indicate
// that there are no matching users for these given credential arrays.
// Now we are ready to execute the query to see if we have a user matching
// the given credentials. If not, we will just return null and indicate
// that there are no matching users from the given credential arrays.
$user = $query->first();
return $this->getGenericUser($user);

View File

@@ -2,10 +2,11 @@
namespace Illuminate\Auth;
use Illuminate\Support\Str;
use Closure;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Contracts\Support\Arrayable;
class EloquentUserProvider implements UserProvider
{
@@ -23,6 +24,13 @@ class EloquentUserProvider implements UserProvider
*/
protected $model;
/**
* The callback that may modify the user retrieval queries.
*
* @var (\Closure(\Illuminate\Database\Eloquent\Builder):mixed)|null
*/
protected $queryCallback;
/**
* Create a new database user provider.
*
@@ -46,9 +54,9 @@ class EloquentUserProvider implements UserProvider
{
$model = $this->createModel();
return $model->newQuery()
->where($model->getAuthIdentifierName(), $identifier)
->first();
return $this->newModelQuery($model)
->where($model->getAuthIdentifierName(), $identifier)
->first();
}
/**
@@ -62,10 +70,17 @@ class EloquentUserProvider implements UserProvider
{
$model = $this->createModel();
return $model->newQuery()
->where($model->getAuthIdentifierName(), $identifier)
->where($model->getRememberTokenName(), $token)
->first();
$retrievedModel = $this->newModelQuery($model)->where(
$model->getAuthIdentifierName(), $identifier
)->first();
if (! $retrievedModel) {
return;
}
$rememberToken = $retrievedModel->getRememberToken();
return $rememberToken && hash_equals($rememberToken, $token) ? $retrievedModel : null;
}
/**
@@ -96,6 +111,12 @@ class EloquentUserProvider implements UserProvider
*/
public function retrieveByCredentials(array $credentials)
{
$credentials = array_filter(
$credentials,
fn ($key) => ! str_contains($key, 'password'),
ARRAY_FILTER_USE_KEY
);
if (empty($credentials)) {
return;
}
@@ -103,10 +124,14 @@ class EloquentUserProvider implements UserProvider
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
$query = $this->createModel()->newQuery();
$query = $this->newModelQuery();
foreach ($credentials as $key => $value) {
if (! Str::contains($key, 'password')) {
if (is_array($value) || $value instanceof Arrayable) {
$query->whereIn($key, $value);
} elseif ($value instanceof Closure) {
$value($query);
} else {
$query->where($key, $value);
}
}
@@ -128,6 +153,23 @@ class EloquentUserProvider implements UserProvider
return $this->hasher->check($plain, $user->getAuthPassword());
}
/**
* Get a new query builder for the model instance.
*
* @param \Illuminate\Database\Eloquent\Model|null $model
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function newModelQuery($model = null)
{
$query = is_null($model)
? $this->createModel()->newQuery()
: $model->newQuery();
with($query, $this->queryCallback);
return $query;
}
/**
* Create a new instance of the model.
*
@@ -185,4 +227,27 @@ class EloquentUserProvider implements UserProvider
return $this;
}
/**
* Get the callback that modifies the query before retrieving users.
*
* @return \Closure|null
*/
public function getQueryCallback()
{
return $this->queryCallback;
}
/**
* Sets the callback to modify the query before retrieving users.
*
* @param (\Closure(\Illuminate\Database\Eloquent\Builder):mixed)|null $queryCallback
* @return $this
*/
public function withQuery($queryCallback = null)
{
$this->queryCallback = $queryCallback;
return $this;
}
}

View File

@@ -4,6 +4,13 @@ namespace Illuminate\Auth\Events;
class Attempting
{
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The credentials for the user.
*
@@ -21,11 +28,14 @@ class Attempting
/**
* Create a new event instance.
*
* @param string $guard
* @param array $credentials
* @param bool $remember
* @return void
*/
public function __construct($credentials, $remember)
public function __construct($guard, $credentials, $remember)
{
$this->guard = $guard;
$this->remember = $remember;
$this->credentials = $credentials;
}

View File

@@ -8,6 +8,13 @@ class Authenticated
{
use SerializesModels;
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The authenticated user.
*
@@ -18,11 +25,13 @@ class Authenticated
/**
* Create a new event instance.
*
* @param string $guard
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($user)
public function __construct($guard, $user)
{
$this->user = $user;
$this->guard = $guard;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class CurrentDeviceLogout
{
use SerializesModels;
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The authenticated user.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
public $user;
/**
* Create a new event instance.
*
* @param string $guard
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($guard, $user)
{
$this->user = $user;
$this->guard = $guard;
}
}

View File

@@ -4,6 +4,13 @@ namespace Illuminate\Auth\Events;
class Failed
{
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The user the attempter was trying to authenticate as.
*
@@ -21,12 +28,15 @@ class Failed
/**
* Create a new event instance.
*
* @param string $guard
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param array $credentials
* @return void
*/
public function __construct($user, $credentials)
public function __construct($guard, $user, $credentials)
{
$this->user = $user;
$this->guard = $guard;
$this->credentials = $credentials;
}
}

View File

@@ -8,6 +8,13 @@ class Login
{
use SerializesModels;
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The authenticated user.
*
@@ -25,13 +32,15 @@ class Login
/**
* Create a new event instance.
*
* @param string $guard
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param bool $remember
* @return void
*/
public function __construct($user, $remember)
public function __construct($guard, $user, $remember)
{
$this->user = $user;
$this->guard = $guard;
$this->remember = $remember;
}
}

View File

@@ -8,6 +8,13 @@ class Logout
{
use SerializesModels;
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The authenticated user.
*
@@ -18,11 +25,13 @@ class Logout
/**
* Create a new event instance.
*
* @param string $guard
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($user)
public function __construct($guard, $user)
{
$this->user = $user;
$this->guard = $guard;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class OtherDeviceLogout
{
use SerializesModels;
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The authenticated user.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
public $user;
/**
* Create a new event instance.
*
* @param string $guard
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($guard, $user)
{
$this->user = $user;
$this->guard = $guard;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class PasswordReset
{
use SerializesModels;
/**
* The user.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
public $user;
/**
* Create a new event instance.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($user)
{
$this->user = $user;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class Validated
{
use SerializesModels;
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The user retrieved and validated from the User Provider.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
public $user;
/**
* Create a new event instance.
*
* @param string $guard
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($guard, $user)
{
$this->user = $user;
$this->guard = $guard;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class Verified
{
use SerializesModels;
/**
* The verified user.
*
* @var \Illuminate\Contracts\Auth\MustVerifyEmail
*/
public $user;
/**
* Create a new event instance.
*
* @param \Illuminate\Contracts\Auth\MustVerifyEmail $user
* @return void
*/
public function __construct($user)
{
$this->user = $user;
}
}

View File

@@ -41,9 +41,7 @@ class GenericUser implements UserContract
*/
public function getAuthIdentifier()
{
$name = $this->getAuthIdentifierName();
return $this->attributes[$name];
return $this->attributes[$this->getAuthIdentifierName()];
}
/**

View File

@@ -3,6 +3,7 @@
namespace Illuminate\Auth;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\UserProvider;
/**
* These methods are typically the same across all guards.
@@ -24,7 +25,7 @@ trait GuardHelpers
protected $provider;
/**
* Determine if the current user is authenticated.
* Determine if the current user is authenticated. If not, throw an exception.
*
* @return \Illuminate\Contracts\Auth\Authenticatable
*
@@ -39,6 +40,16 @@ trait GuardHelpers
throw new AuthenticationException;
}
/**
* Determine if the guard has a user instance.
*
* @return bool
*/
public function hasUser()
{
return ! is_null($this->user);
}
/**
* Determine if the current user is authenticated.
*
@@ -62,7 +73,7 @@ trait GuardHelpers
/**
* Get the ID for the currently authenticated user.
*
* @return int|null
* @return int|string|null
*/
public function id()
{
@@ -83,4 +94,37 @@ trait GuardHelpers
return $this;
}
/**
* Forget the current user.
*
* @return $this
*/
public function forgetUser()
{
$this->user = null;
return $this;
}
/**
* Get the user provider used by the guard.
*
* @return \Illuminate\Contracts\Auth\UserProvider
*/
public function getProvider()
{
return $this->provider;
}
/**
* Set the user provider used by the guard.
*
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @return void
*/
public function setProvider(UserProvider $provider)
{
$this->provider = $provider;
}
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,22 @@
<?php
namespace Illuminate\Auth\Listeners;
use Illuminate\Auth\Events\Registered;
use Illuminate\Contracts\Auth\MustVerifyEmail;
class SendEmailVerificationNotification
{
/**
* Handle the event.
*
* @param \Illuminate\Auth\Events\Registered $event
* @return void
*/
public function handle(Registered $event)
{
if ($event->user instanceof MustVerifyEmail && ! $event->user->hasVerifiedEmail()) {
$event->user->sendEmailVerificationNotification();
}
}
}

View File

@@ -5,8 +5,9 @@ namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as Auth;
use Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests;
class Authenticate
class Authenticate implements AuthenticatesRequests
{
/**
* The authentication factory instance.
@@ -38,7 +39,7 @@ class Authenticate
*/
public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($guards);
$this->authenticate($request, $guards);
return $next($request);
}
@@ -46,15 +47,16 @@ class Authenticate
/**
* Determine if the user is logged in to any of the given guards.
*
* @param \Illuminate\Http\Request $request
* @param array $guards
* @return void
*
* @throws \Illuminate\Auth\AuthenticationException
*/
protected function authenticate(array $guards)
protected function authenticate($request, array $guards)
{
if (empty($guards)) {
return $this->auth->authenticate();
$guards = [null];
}
foreach ($guards as $guard) {
@@ -63,6 +65,33 @@ class Authenticate
}
}
throw new AuthenticationException('Unauthenticated.', $guards);
$this->unauthenticated($request, $guards);
}
/**
* Handle an unauthenticated user.
*
* @param \Illuminate\Http\Request $request
* @param array $guards
* @return void
*
* @throws \Illuminate\Auth\AuthenticationException
*/
protected function unauthenticated($request, array $guards)
{
throw new AuthenticationException(
'Unauthenticated.', $guards, $this->redirectTo($request)
);
}
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string|null
*/
protected function redirectTo($request)
{
//
}
}

View File

@@ -31,10 +31,15 @@ class AuthenticateWithBasicAuth
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @param string|null $field
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
*/
public function handle($request, Closure $next, $guard = null)
public function handle($request, Closure $next, $guard = null, $field = null)
{
return $this->auth->guard($guard)->basic() ?: $next($request);
$this->auth->guard($guard)->basic($field ?: 'email');
return $next($request);
}
}

View File

@@ -3,19 +3,11 @@
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Contracts\Auth\Access\Gate;
use Illuminate\Contracts\Auth\Factory as Auth;
use Illuminate\Database\Eloquent\Model;
class Authorize
{
/**
* The authentication factory instance.
*
* @var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* The gate instance.
*
@@ -26,13 +18,11 @@ class Authorize
/**
* Create a new middleware instance.
*
* @param \Illuminate\Contracts\Auth\Factory $auth
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function __construct(Auth $auth, Gate $gate)
public function __construct(Gate $gate)
{
$this->auth = $auth;
$this->gate = $gate;
}
@@ -42,7 +32,7 @@ class Authorize
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $ability
* @param array|null $models
* @param array|null ...$models
* @return mixed
*
* @throws \Illuminate\Auth\AuthenticationException
@@ -50,8 +40,6 @@ class Authorize
*/
public function handle($request, Closure $next, $ability, ...$models)
{
$this->auth->authenticate();
$this->gate->authorize($ability, $this->getGateArguments($request, $models));
return $next($request);
@@ -62,7 +50,7 @@ class Authorize
*
* @param \Illuminate\Http\Request $request
* @param array|null $models
* @return array|string|\Illuminate\Database\Eloquent\Model
* @return \Illuminate\Database\Eloquent\Model|array|string
*/
protected function getGateArguments($request, $models)
{
@@ -80,11 +68,16 @@ class Authorize
*
* @param \Illuminate\Http\Request $request
* @param string $model
* @return string|\Illuminate\Database\Eloquent\Model
* @return \Illuminate\Database\Eloquent\Model|string
*/
protected function getModel($request, $model)
{
return $this->isClassName($model) ? $model : $request->route($model);
if ($this->isClassName($model)) {
return trim($model);
} else {
return $request->route($model, null) ??
((preg_match("/^['\"](.*)['\"]$/", trim($model), $matches)) ? $matches[1] : null);
}
}
/**
@@ -95,6 +88,6 @@ class Authorize
*/
protected function isClassName($value)
{
return strpos($value, '\\') !== false;
return str_contains($value, '\\');
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\URL;
class EnsureEmailIsVerified
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $redirectToRoute
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse|null
*/
public function handle($request, Closure $next, $redirectToRoute = null)
{
if (! $request->user() ||
($request->user() instanceof MustVerifyEmail &&
! $request->user()->hasVerifiedEmail())) {
return $request->expectsJson()
? abort(403, 'Your email address is not verified.')
: Redirect::guest(URL::route($redirectToRoute ?: 'verification.notice'));
}
return $next($request);
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Contracts\Routing\UrlGenerator;
class RequirePassword
{
/**
* The response factory instance.
*
* @var \Illuminate\Contracts\Routing\ResponseFactory
*/
protected $responseFactory;
/**
* The URL generator instance.
*
* @var \Illuminate\Contracts\Routing\UrlGenerator
*/
protected $urlGenerator;
/**
* The password timeout.
*
* @var int
*/
protected $passwordTimeout;
/**
* Create a new middleware instance.
*
* @param \Illuminate\Contracts\Routing\ResponseFactory $responseFactory
* @param \Illuminate\Contracts\Routing\UrlGenerator $urlGenerator
* @param int|null $passwordTimeout
* @return void
*/
public function __construct(ResponseFactory $responseFactory, UrlGenerator $urlGenerator, $passwordTimeout = null)
{
$this->responseFactory = $responseFactory;
$this->urlGenerator = $urlGenerator;
$this->passwordTimeout = $passwordTimeout ?: 10800;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $redirectToRoute
* @param int|null $passwordTimeoutSeconds
* @return mixed
*/
public function handle($request, Closure $next, $redirectToRoute = null, $passwordTimeoutSeconds = null)
{
if ($this->shouldConfirmPassword($request, $passwordTimeoutSeconds)) {
if ($request->expectsJson()) {
return $this->responseFactory->json([
'message' => 'Password confirmation required.',
], 423);
}
return $this->responseFactory->redirectGuest(
$this->urlGenerator->route($redirectToRoute ?? 'password.confirm')
);
}
return $next($request);
}
/**
* Determine if the confirmation timeout has expired.
*
* @param \Illuminate\Http\Request $request
* @param int|null $passwordTimeoutSeconds
* @return bool
*/
protected function shouldConfirmPassword($request, $passwordTimeoutSeconds = null)
{
$confirmedAt = time() - $request->session()->get('auth.password_confirmed_at', 0);
return $confirmedAt > ($passwordTimeoutSeconds ?? $this->passwordTimeout);
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Illuminate\Auth;
use Illuminate\Auth\Notifications\VerifyEmail;
trait MustVerifyEmail
{
/**
* Determine if the user has verified their email address.
*
* @return bool
*/
public function hasVerifiedEmail()
{
return ! is_null($this->email_verified_at);
}
/**
* Mark the given user's email as verified.
*
* @return bool
*/
public function markEmailAsVerified()
{
return $this->forceFill([
'email_verified_at' => $this->freshTimestamp(),
])->save();
}
/**
* Send the email verification notification.
*
* @return void
*/
public function sendEmailVerificationNotification()
{
$this->notify(new VerifyEmail);
}
/**
* Get the email address that should be used for verification.
*
* @return string
*/
public function getEmailForVerification()
{
return $this->email;
}
}

View File

@@ -2,8 +2,9 @@
namespace Illuminate\Auth\Notifications;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Lang;
class ResetPassword extends Notification
{
@@ -14,6 +15,20 @@ class ResetPassword extends Notification
*/
public $token;
/**
* The callback that should be used to create the reset password URL.
*
* @var (\Closure(mixed, string): string)|null
*/
public static $createUrlCallback;
/**
* The callback that should be used to build the mail message.
*
* @var (\Closure(mixed, string): \Illuminate\Notifications\Messages\MailMessage)|null
*/
public static $toMailCallback;
/**
* Create a notification instance.
*
@@ -43,10 +58,67 @@ class ResetPassword extends Notification
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $this->token);
}
return $this->buildMailMessage($this->resetUrl($notifiable));
}
/**
* Get the reset password notification mail message for the given URL.
*
* @param string $url
* @return \Illuminate\Notifications\Messages\MailMessage
*/
protected function buildMailMessage($url)
{
return (new MailMessage)
->line('You are receiving this email because we received a password reset request for your account.')
->action('Reset Password', url(config('app.url').route('password.reset', $this->token, false)))
->line('If you did not request a password reset, no further action is required.');
->subject(Lang::get('Reset Password Notification'))
->line(Lang::get('You are receiving this email because we received a password reset request for your account.'))
->action(Lang::get('Reset Password'), $url)
->line(Lang::get('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.'.config('auth.defaults.passwords').'.expire')]))
->line(Lang::get('If you did not request a password reset, no further action is required.'));
}
/**
* Get the reset URL for the given notifiable.
*
* @param mixed $notifiable
* @return string
*/
protected function resetUrl($notifiable)
{
if (static::$createUrlCallback) {
return call_user_func(static::$createUrlCallback, $notifiable, $this->token);
}
return url(route('password.reset', [
'token' => $this->token,
'email' => $notifiable->getEmailForPasswordReset(),
], false));
}
/**
* Set a callback that should be used when creating the reset password button URL.
*
* @param \Closure(mixed, string): string $callback
* @return void
*/
public static function createUrlUsing($callback)
{
static::$createUrlCallback = $callback;
}
/**
* Set a callback that should be used when building the notification mail message.
*
* @param \Closure(mixed, string): \Illuminate\Notifications\Messages\MailMessage $callback
* @return void
*/
public static function toMailUsing($callback)
{
static::$toMailCallback = $callback;
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace Illuminate\Auth\Notifications;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\URL;
class VerifyEmail extends Notification
{
/**
* The callback that should be used to create the verify email URL.
*
* @var \Closure|null
*/
public static $createUrlCallback;
/**
* The callback that should be used to build the mail message.
*
* @var \Closure|null
*/
public static $toMailCallback;
/**
* Get the notification's channels.
*
* @param mixed $notifiable
* @return array|string
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Build the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$verificationUrl = $this->verificationUrl($notifiable);
if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $verificationUrl);
}
return $this->buildMailMessage($verificationUrl);
}
/**
* Get the verify email notification mail message for the given URL.
*
* @param string $url
* @return \Illuminate\Notifications\Messages\MailMessage
*/
protected function buildMailMessage($url)
{
return (new MailMessage)
->subject(Lang::get('Verify Email Address'))
->line(Lang::get('Please click the button below to verify your email address.'))
->action(Lang::get('Verify Email Address'), $url)
->line(Lang::get('If you did not create an account, no further action is required.'));
}
/**
* Get the verification URL for the given notifiable.
*
* @param mixed $notifiable
* @return string
*/
protected function verificationUrl($notifiable)
{
if (static::$createUrlCallback) {
return call_user_func(static::$createUrlCallback, $notifiable);
}
return URL::temporarySignedRoute(
'verification.verify',
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
[
'id' => $notifiable->getKey(),
'hash' => sha1($notifiable->getEmailForVerification()),
]
);
}
/**
* Set a callback that should be used when creating the email verification URL.
*
* @param \Closure $callback
* @return void
*/
public static function createUrlUsing($callback)
{
static::$createUrlCallback = $callback;
}
/**
* Set a callback that should be used when building the notification mail message.
*
* @param \Closure $callback
* @return void
*/
public static function toMailUsing($callback)
{
static::$toMailCallback = $callback;
}
}

View File

@@ -2,11 +2,11 @@
namespace Illuminate\Auth\Passwords;
use Carbon\Carbon;
use Illuminate\Support\Str;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
class DatabaseTokenRepository implements TokenRepositoryInterface
{
@@ -45,6 +45,13 @@ class DatabaseTokenRepository implements TokenRepositoryInterface
*/
protected $expires;
/**
* Minimum number of seconds before re-redefining the token.
*
* @var int
*/
protected $throttle;
/**
* Create a new token repository instance.
*
@@ -53,16 +60,19 @@ class DatabaseTokenRepository implements TokenRepositoryInterface
* @param string $table
* @param string $hashKey
* @param int $expires
* @param int $throttle
* @return void
*/
public function __construct(ConnectionInterface $connection, HasherContract $hasher,
$table, $hashKey, $expires = 60)
$table, $hashKey, $expires = 60,
$throttle = 60)
{
$this->table = $table;
$this->hasher = $hasher;
$this->hashKey = $hashKey;
$this->expires = $expires * 60;
$this->connection = $connection;
$this->throttle = $throttle;
}
/**
@@ -139,6 +149,38 @@ class DatabaseTokenRepository implements TokenRepositoryInterface
return Carbon::parse($createdAt)->addSeconds($this->expires)->isPast();
}
/**
* Determine if the given user recently created a password reset token.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return bool
*/
public function recentlyCreatedToken(CanResetPasswordContract $user)
{
$record = (array) $this->getTable()->where(
'email', $user->getEmailForPasswordReset()
)->first();
return $record && $this->tokenRecentlyCreated($record['created_at']);
}
/**
* Determine if the token was recently created.
*
* @param string $createdAt
* @return bool
*/
protected function tokenRecentlyCreated($createdAt)
{
if ($this->throttle <= 0) {
return false;
}
return Carbon::parse($createdAt)->addSeconds(
$this->throttle
)->isFuture();
}
/**
* Delete a token record by user.
*

View File

@@ -3,11 +3,11 @@
namespace Illuminate\Auth\Passwords;
use Closure;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Contracts\Auth\PasswordBroker as PasswordBrokerContract;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Support\Arr;
use UnexpectedValueException;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Auth\PasswordBroker as PasswordBrokerContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class PasswordBroker implements PasswordBrokerContract
{
@@ -25,13 +25,6 @@ class PasswordBroker implements PasswordBrokerContract
*/
protected $users;
/**
* The custom password validator callback.
*
* @var \Closure
*/
protected $passwordValidator;
/**
* Create a new password broker instance.
*
@@ -39,8 +32,7 @@ class PasswordBroker implements PasswordBrokerContract
* @param \Illuminate\Contracts\Auth\UserProvider $users
* @return void
*/
public function __construct(TokenRepositoryInterface $tokens,
UserProvider $users)
public function __construct(TokenRepositoryInterface $tokens, UserProvider $users)
{
$this->users = $users;
$this->tokens = $tokens;
@@ -50,9 +42,10 @@ class PasswordBroker implements PasswordBrokerContract
* Send a password reset link to a user.
*
* @param array $credentials
* @param \Closure|null $callback
* @return string
*/
public function sendResetLink(array $credentials)
public function sendResetLink(array $credentials, Closure $callback = null)
{
// First we will check to see if we found a user at the given credentials and
// if we did not we will redirect back to this current URI with a piece of
@@ -63,12 +56,20 @@ class PasswordBroker implements PasswordBrokerContract
return static::INVALID_USER;
}
// Once we have the reset token, we are ready to send the message out to this
// user with a link to reset their password. We will then redirect back to
// the current URI having nothing set in the session to indicate errors.
$user->sendPasswordResetNotification(
$this->tokens->create($user)
);
if ($this->tokens->recentlyCreatedToken($user)) {
return static::RESET_THROTTLED;
}
$token = $this->tokens->create($user);
if ($callback) {
$callback($user, $token);
} else {
// Once we have the reset token, we are ready to send the message out to this
// user with a link to reset their password. We will then redirect back to
// the current URI having nothing set in the session to indicate errors.
$user->sendPasswordResetNotification($token);
}
return static::RESET_LINK_SENT;
}
@@ -82,11 +83,11 @@ class PasswordBroker implements PasswordBrokerContract
*/
public function reset(array $credentials, Closure $callback)
{
$user = $this->validateReset($credentials);
// If the responses from the validate method is not a user instance, we will
// assume that it is a redirect and simply return it from this method and
// the user is properly redirected having an error message on the post.
$user = $this->validateReset($credentials);
if (! $user instanceof CanResetPasswordContract) {
return $user;
}
@@ -107,7 +108,7 @@ class PasswordBroker implements PasswordBrokerContract
* Validate a password reset for the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\CanResetPassword
* @return \Illuminate\Contracts\Auth\CanResetPassword|string
*/
protected function validateReset(array $credentials)
{
@@ -115,10 +116,6 @@ class PasswordBroker implements PasswordBrokerContract
return static::INVALID_USER;
}
if (! $this->validateNewPassword($credentials)) {
return static::INVALID_PASSWORD;
}
if (! $this->tokens->exists($user, $credentials['token'])) {
return static::INVALID_TOKEN;
}
@@ -126,60 +123,11 @@ class PasswordBroker implements PasswordBrokerContract
return $user;
}
/**
* Set a custom password validator.
*
* @param \Closure $callback
* @return void
*/
public function validator(Closure $callback)
{
$this->passwordValidator = $callback;
}
/**
* Determine if the passwords match for the request.
*
* @param array $credentials
* @return bool
*/
public function validateNewPassword(array $credentials)
{
if (isset($this->passwordValidator)) {
list($password, $confirm) = [
$credentials['password'],
$credentials['password_confirmation'],
];
return call_user_func(
$this->passwordValidator, $credentials
) && $password === $confirm;
}
return $this->validatePasswordWithDefaults($credentials);
}
/**
* Determine if the passwords are valid for the request.
*
* @param array $credentials
* @return bool
*/
protected function validatePasswordWithDefaults(array $credentials)
{
list($password, $confirm) = [
$credentials['password'],
$credentials['password_confirmation'],
];
return $password === $confirm && mb_strlen($password) >= 6;
}
/**
* Get the user for the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\CanResetPassword
* @return \Illuminate\Contracts\Auth\CanResetPassword|null
*
* @throws \UnexpectedValueException
*/
@@ -199,7 +147,7 @@ class PasswordBroker implements PasswordBrokerContract
/**
* Create a new password reset token for the given user.
*
* @param CanResetPasswordContract $user
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return string
*/
public function createToken(CanResetPasswordContract $user)
@@ -210,7 +158,7 @@ class PasswordBroker implements PasswordBrokerContract
/**
* Delete password reset tokens of the given user.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return void
*/
public function deleteToken(CanResetPasswordContract $user)
@@ -221,8 +169,8 @@ class PasswordBroker implements PasswordBrokerContract
/**
* Validate the given password reset token.
*
* @param CanResetPasswordContract $user
* @param string $token
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @param string $token
* @return bool
*/
public function tokenExists(CanResetPasswordContract $user, $token)

View File

@@ -2,16 +2,18 @@
namespace Illuminate\Auth\Passwords;
use Illuminate\Support\Str;
use InvalidArgumentException;
use Illuminate\Contracts\Auth\PasswordBrokerFactory as FactoryContract;
use InvalidArgumentException;
/**
* @mixin \Illuminate\Contracts\Auth\PasswordBroker
*/
class PasswordBrokerManager implements FactoryContract
{
/**
* The application instance.
*
* @var \Illuminate\Foundation\Application
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
@@ -25,7 +27,7 @@ class PasswordBrokerManager implements FactoryContract
/**
* Create a new PasswordBroker manager instance.
*
* @param \Illuminate\Foundation\Application $app
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function __construct($app)
@@ -36,16 +38,14 @@ class PasswordBrokerManager implements FactoryContract
/**
* Attempt to get the broker from the local cache.
*
* @param string $name
* @param string|null $name
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker($name = null)
{
$name = $name ?: $this->getDefaultDriver();
return isset($this->brokers[$name])
? $this->brokers[$name]
: $this->brokers[$name] = $this->resolve($name);
return $this->brokers[$name] ?? ($this->brokers[$name] = $this->resolve($name));
}
/**
@@ -69,7 +69,7 @@ class PasswordBrokerManager implements FactoryContract
// aggregate service of sorts providing a convenient interface for resets.
return new PasswordBroker(
$this->createTokenRepository($config),
$this->app['auth']->createUserProvider($config['provider'])
$this->app['auth']->createUserProvider($config['provider'] ?? null)
);
}
@@ -83,18 +83,19 @@ class PasswordBrokerManager implements FactoryContract
{
$key = $this->app['config']['app.key'];
if (Str::startsWith($key, 'base64:')) {
if (str_starts_with($key, 'base64:')) {
$key = base64_decode(substr($key, 7));
}
$connection = isset($config['connection']) ? $config['connection'] : null;
$connection = $config['connection'] ?? null;
return new DatabaseTokenRepository(
$this->app['db']->connection($connection),
$this->app['hash'],
$config['table'],
$key,
$config['expire']
$config['expire'],
$config['throttle'] ?? 0
);
}
@@ -134,7 +135,7 @@ class PasswordBrokerManager implements FactoryContract
* Dynamically call the default driver instance.
*
* @param string $method
* @param array $parameters
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)

View File

@@ -2,17 +2,11 @@
namespace Illuminate\Auth\Passwords;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
class PasswordResetServiceProvider extends ServiceProvider
class PasswordResetServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = true;
/**
* Register the service provider.
*

View File

@@ -23,6 +23,14 @@ interface TokenRepositoryInterface
*/
public function exists(CanResetPasswordContract $user, $token);
/**
* Determine if the given user recently created a password reset token.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return bool
*/
public function recentlyCreatedToken(CanResetPasswordContract $user);
/**
* Delete a token record.
*

View File

@@ -2,8 +2,6 @@
namespace Illuminate\Auth;
use Illuminate\Support\Str;
class Recaller
{
/**
@@ -21,7 +19,7 @@ class Recaller
*/
public function __construct($recaller)
{
$this->recaller = $recaller;
$this->recaller = @unserialize($recaller, ['allowed_classes' => false]) ?: $recaller;
}
/**
@@ -31,7 +29,7 @@ class Recaller
*/
public function id()
{
return explode('|', $this->recaller, 2)[0];
return explode('|', $this->recaller, 3)[0];
}
/**
@@ -41,7 +39,17 @@ class Recaller
*/
public function token()
{
return explode('|', $this->recaller, 2)[1];
return explode('|', $this->recaller, 3)[1];
}
/**
* Get the password from the recaller.
*
* @return string
*/
public function hash()
{
return explode('|', $this->recaller, 4)[2];
}
/**
@@ -51,7 +59,7 @@ class Recaller
*/
public function valid()
{
return $this->properString() && $this->hasBothSegments();
return $this->properString() && $this->hasAllSegments();
}
/**
@@ -61,18 +69,28 @@ class Recaller
*/
protected function properString()
{
return is_string($this->recaller) && Str::contains($this->recaller, '|');
return is_string($this->recaller) && str_contains($this->recaller, '|');
}
/**
* Determine if the recaller has both segments.
* Determine if the recaller has all segments.
*
* @return bool
*/
protected function hasBothSegments()
protected function hasAllSegments()
{
$segments = explode('|', $this->recaller);
return count($segments) == 2 && trim($segments[0]) !== '' && trim($segments[1]) !== '';
return count($segments) >= 3 && trim($segments[0]) !== '' && trim($segments[1]) !== '';
}
/**
* Get the recaller's segments.
*
* @return array
*/
public function segments()
{
return explode('|', $this->recaller);
}
}

View File

@@ -2,8 +2,9 @@
namespace Illuminate\Auth;
use Illuminate\Http\Request;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Traits\Macroable;
class RequestGuard implements Guard
@@ -29,12 +30,14 @@ class RequestGuard implements Guard
*
* @param callable $callback
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Contracts\Auth\UserProvider|null $provider
* @return void
*/
public function __construct(callable $callback, Request $request)
public function __construct(callable $callback, Request $request, UserProvider $provider = null)
{
$this->request = $request;
$this->callback = $callback;
$this->provider = $provider;
}
/**
@@ -52,7 +55,7 @@ class RequestGuard implements Guard
}
return $this->user = call_user_func(
$this->callback, $this->request
$this->callback, $this->request, $this->getProvider()
);
}
@@ -65,7 +68,7 @@ class RequestGuard implements Guard
public function validate(array $credentials = [])
{
return ! is_null((new static(
$this->callback, $credentials['request']
$this->callback, $credentials['request'], $this->getProvider()
))->user());
}

View File

@@ -2,25 +2,37 @@
namespace Illuminate\Auth;
use RuntimeException;
use Illuminate\Support\Str;
use Illuminate\Http\Response;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Contracts\Session\Session;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Auth\StatefulGuard;
use Symfony\Component\HttpFoundation\Request;
use Illuminate\Contracts\Auth\SupportsBasicAuth;
use Illuminate\Contracts\Cookie\QueueingFactory as CookieJar;
use Illuminate\Auth\Events\Attempting;
use Illuminate\Auth\Events\Authenticated;
use Illuminate\Auth\Events\CurrentDeviceLogout;
use Illuminate\Auth\Events\Failed;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Auth\Events\OtherDeviceLogout;
use Illuminate\Auth\Events\Validated;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\Auth\SupportsBasicAuth;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Cookie\QueueingFactory as CookieJar;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Session\Session;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Support\Timebox;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
use GuardHelpers, Macroable;
/**
* The name of the Guard. Typically "session".
* The name of the guard. Typically "web".
*
* Corresponds to guard name in authentication configuration.
*
@@ -42,6 +54,13 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*/
protected $viaRemember = false;
/**
* The number of minutes that the "remember me" cookie should be valid for.
*
* @var int
*/
protected $rememberDuration = 576000;
/**
* The session used by the guard.
*
@@ -70,6 +89,13 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*/
protected $events;
/**
* The timebox instance.
*
* @var \Illuminate\Support\Timebox
*/
protected $timebox;
/**
* Indicates if the logout method has been called.
*
@@ -90,18 +116,21 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
* @param string $name
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @param \Illuminate\Contracts\Session\Session $session
* @param \Symfony\Component\HttpFoundation\Request $request
* @param \Symfony\Component\HttpFoundation\Request|null $request
* @param \Illuminate\Support\Timebox|null $timebox
* @return void
*/
public function __construct($name,
UserProvider $provider,
Session $session,
Request $request = null)
Request $request = null,
Timebox $timebox = null)
{
$this->name = $name;
$this->session = $session;
$this->request = $request;
$this->provider = $provider;
$this->timebox = $timebox ?: new Timebox;
}
/**
@@ -127,30 +156,24 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
$user = null;
if (! is_null($id)) {
if ($user = $this->provider->retrieveById($id)) {
$this->fireAuthenticatedEvent($user);
}
if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
$this->fireAuthenticatedEvent($this->user);
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$recaller = $this->recaller();
if (is_null($this->user) && ! is_null($recaller = $this->recaller())) {
$this->user = $this->userFromRecaller($recaller);
if (is_null($user) && ! is_null($recaller)) {
$user = $this->userFromRecaller($recaller);
if ($this->user) {
$this->updateSession($this->user->getAuthIdentifier());
if ($user) {
$this->updateSession($user->getAuthIdentifier());
$this->fireLoginEvent($user, true);
$this->fireLoginEvent($this->user, true);
}
}
return $this->user = $user;
return $this->user;
}
/**
@@ -196,7 +219,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
/**
* Get the ID for the currently authenticated user.
*
* @return int|null
* @return int|string|null
*/
public function id()
{
@@ -264,6 +287,8 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
* @param string $field
* @param array $extraConditions
* @return \Symfony\Component\HttpFoundation\Response|null
*
* @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
*/
public function basic($field = 'email', $extraConditions = [])
{
@@ -287,6 +312,8 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
* @param string $field
* @param array $extraConditions
* @return \Symfony\Component\HttpFoundation\Response|null
*
* @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
*/
public function onceBasic($field = 'email', $extraConditions = [])
{
@@ -317,7 +344,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
}
/**
* Get the credential array for a HTTP Basic request.
* Get the credential array for an HTTP Basic request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @param string $field
@@ -331,18 +358,20 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
/**
* Get the response for basic authentication.
*
* @return \Symfony\Component\HttpFoundation\Response
* @return void
*
* @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
*/
protected function failedBasicResponse()
{
return new Response('Invalid credentials.', 401, ['WWW-Authenticate' => 'Basic']);
throw new UnauthorizedHttpException('Basic', 'Invalid credentials.');
}
/**
* Attempt to authenticate a user using the given credentials.
*
* @param array $credentials
* @param bool $remember
* @param bool $remember
* @return bool
*/
public function attempt(array $credentials = [], $remember = false)
@@ -368,6 +397,34 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
return false;
}
/**
* Attempt to authenticate a user with credentials and additional callbacks.
*
* @param array $credentials
* @param array|callable|null $callbacks
* @param bool $remember
* @return bool
*/
public function attemptWhen(array $credentials = [], $callbacks = null, $remember = false)
{
$this->fireAttemptEvent($credentials, $remember);
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
// This method does the exact same thing as attempt, but also executes callbacks after
// the user is retrieved and validated. If one of the callbacks returns falsy we do
// not login the user. Instead, we will fail the specific authentication attempt.
if ($this->hasValidCredentials($user, $credentials) && $this->shouldLogin($callbacks, $user)) {
$this->login($user, $remember);
return true;
}
$this->fireFailedEvent($user, $credentials);
return false;
}
/**
* Determine if the user matches the credentials.
*
@@ -377,14 +434,42 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*/
protected function hasValidCredentials($user, $credentials)
{
return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
return $this->timebox->call(function ($timebox) use ($user, $credentials) {
$validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
if ($validated) {
$timebox->returnEarly();
$this->fireValidatedEvent($user);
}
return $validated;
}, 200 * 1000);
}
/**
* Determine if the user should login by executing the given callbacks.
*
* @param array|callable|null $callbacks
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return bool
*/
protected function shouldLogin($callbacks, AuthenticatableContract $user)
{
foreach (Arr::wrap($callbacks) as $callback) {
if (! $callback($user, $this)) {
return false;
}
}
return true;
}
/**
* Log the given user ID into the application.
*
* @param mixed $id
* @param bool $remember
* @param bool $remember
* @return \Illuminate\Contracts\Auth\Authenticatable|false
*/
public function loginUsingId($id, $remember = false)
@@ -461,7 +546,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
protected function queueRecallerCookie(AuthenticatableContract $user)
{
$this->getCookieJar()->queue($this->createRecaller(
$user->getAuthIdentifier().'|'.$user->getRememberToken()
$user->getAuthIdentifier().'|'.$user->getRememberToken().'|'.$user->getAuthPassword()
));
}
@@ -473,7 +558,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*/
protected function createRecaller($value)
{
return $this->getCookieJar()->forever($this->getRecallerName(), $value);
return $this->getCookieJar()->make($this->getRecallerName(), $value, $this->getRememberDuration());
}
/**
@@ -485,17 +570,45 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
$user = $this->user();
// If we have an event dispatcher instance, we can fire off the logout event
// so any further processing can be done. This allows the developer to be
// listening for anytime a user signs out of this application manually.
$this->clearUserDataFromStorage();
if (! is_null($this->user)) {
if (! is_null($this->user) && ! empty($user->getRememberToken())) {
$this->cycleRememberToken($user);
}
// If we have an event dispatcher instance, we can fire off the logout event
// so any further processing can be done. This allows the developer to be
// listening for anytime a user signs out of this application manually.
if (isset($this->events)) {
$this->events->dispatch(new Events\Logout($user));
$this->events->dispatch(new Logout($this->name, $user));
}
// Once we have fired the logout event we will clear the users out of memory
// so they are no longer available as the user is no longer considered as
// being signed into this application and should not be available here.
$this->user = null;
$this->loggedOut = true;
}
/**
* Log the user out of the application on their current device only.
*
* This method does not cycle the "remember" token.
*
* @return void
*/
public function logoutCurrentDevice()
{
$user = $this->user();
$this->clearUserDataFromStorage();
// If we have an event dispatcher instance, we can fire off the logout event
// so any further processing can be done. This allows the developer to be
// listening for anytime a user signs out of this application manually.
if (isset($this->events)) {
$this->events->dispatch(new CurrentDeviceLogout($this->name, $user));
}
// Once we have fired the logout event we will clear the users out of memory
@@ -515,9 +628,12 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
$this->session->remove($this->getName());
$this->getCookieJar()->unqueue($this->getRecallerName());
if (! is_null($this->recaller())) {
$this->getCookieJar()->queue($this->getCookieJar()
->forget($this->getRecallerName()));
$this->getCookieJar()->queue(
$this->getCookieJar()->forget($this->getRecallerName())
);
}
}
@@ -534,6 +650,55 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
$this->provider->updateRememberToken($user, $token);
}
/**
* Invalidate other sessions for the current user.
*
* The application must be using the AuthenticateSession middleware.
*
* @param string $password
* @param string $attribute
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*
* @throws \Illuminate\Auth\AuthenticationException
*/
public function logoutOtherDevices($password, $attribute = 'password')
{
if (! $this->user()) {
return;
}
$result = $this->rehashUserPassword($password, $attribute);
if ($this->recaller() ||
$this->getCookieJar()->hasQueued($this->getRecallerName())) {
$this->queueRecallerCookie($this->user());
}
$this->fireOtherDeviceLogoutEvent($this->user());
return $result;
}
/**
* Rehash the current user's password.
*
* @param string $password
* @param string $attribute
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*
* @throws \InvalidArgumentException
*/
protected function rehashUserPassword($password, $attribute)
{
if (! Hash::check($password, $this->user()->{$attribute})) {
throw new InvalidArgumentException('The given password does not match the current password.');
}
return tap($this->user()->forceFill([
$attribute => Hash::make($password),
]))->save();
}
/**
* Register an authentication attempt event listener.
*
@@ -542,9 +707,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*/
public function attempting($callback)
{
if (isset($this->events)) {
$this->events->listen(Events\Attempting::class, $callback);
}
$this->events?->listen(Events\Attempting::class, $callback);
}
/**
@@ -556,11 +719,18 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*/
protected function fireAttemptEvent(array $credentials, $remember = false)
{
if (isset($this->events)) {
$this->events->dispatch(new Events\Attempting(
$credentials, $remember
));
}
$this->events?->dispatch(new Attempting($this->name, $credentials, $remember));
}
/**
* Fires the validated event if the dispatcher is set.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
protected function fireValidatedEvent($user)
{
$this->events?->dispatch(new Validated($this->name, $user));
}
/**
@@ -572,9 +742,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*/
protected function fireLoginEvent($user, $remember = false)
{
if (isset($this->events)) {
$this->events->dispatch(new Events\Login($user, $remember));
}
$this->events?->dispatch(new Login($this->name, $user, $remember));
}
/**
@@ -585,9 +753,18 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*/
protected function fireAuthenticatedEvent($user)
{
if (isset($this->events)) {
$this->events->dispatch(new Events\Authenticated($user));
}
$this->events?->dispatch(new Authenticated($this->name, $user));
}
/**
* Fire the other device logout event if the dispatcher is set.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
protected function fireOtherDeviceLogoutEvent($user)
{
$this->events?->dispatch(new OtherDeviceLogout($this->name, $user));
}
/**
@@ -599,9 +776,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
*/
protected function fireFailedEvent($user, array $credentials)
{
if (isset($this->events)) {
$this->events->dispatch(new Events\Failed($user, $credentials));
}
$this->events?->dispatch(new Failed($this->name, $user, $credentials));
}
/**
@@ -644,6 +819,29 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
return $this->viaRemember;
}
/**
* Get the number of minutes the remember me cookie should be valid for.
*
* @return int
*/
protected function getRememberDuration()
{
return $this->rememberDuration;
}
/**
* Set the number of minutes the remember me cookie should be valid for.
*
* @param int $minutes
* @return $this
*/
public function setRememberDuration($minutes)
{
$this->rememberDuration = $minutes;
return $this;
}
/**
* Get the cookie creator instance used by the guard.
*
@@ -695,34 +893,13 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
/**
* Get the session store used by the guard.
*
* @return \Illuminate\Session\Store
* @return \Illuminate\Contracts\Session\Session
*/
public function getSession()
{
return $this->session;
}
/**
* Get the user provider used by the guard.
*
* @return \Illuminate\Contracts\Auth\UserProvider
*/
public function getProvider()
{
return $this->provider;
}
/**
* Set the user provider used by the guard.
*
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @return void
*/
public function setProvider(UserProvider $provider)
{
$this->provider = $provider;
}
/**
* Return the currently cached user.
*
@@ -772,4 +949,14 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
return $this;
}
/**
* Get the timebox instance used by the guard.
*
* @return \Illuminate\Support\Timebox
*/
public function getTimebox()
{
return $this->timebox;
}
}

View File

@@ -2,9 +2,9 @@
namespace Illuminate\Auth;
use Illuminate\Http\Request;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Http\Request;
class TokenGuard implements Guard
{
@@ -31,19 +31,35 @@ class TokenGuard implements Guard
*/
protected $storageKey;
/**
* Indicates if the API token is hashed in storage.
*
* @var bool
*/
protected $hash = false;
/**
* Create a new authentication guard.
*
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @param \Illuminate\Http\Request $request
* @param string $inputKey
* @param string $storageKey
* @param bool $hash
* @return void
*/
public function __construct(UserProvider $provider, Request $request)
public function __construct(
UserProvider $provider,
Request $request,
$inputKey = 'api_token',
$storageKey = 'api_token',
$hash = false)
{
$this->hash = $hash;
$this->request = $request;
$this->provider = $provider;
$this->inputKey = 'api_token';
$this->storageKey = 'api_token';
$this->inputKey = $inputKey;
$this->storageKey = $storageKey;
}
/**
@@ -65,9 +81,9 @@ class TokenGuard implements Guard
$token = $this->getTokenForRequest();
if (! empty($token)) {
$user = $this->provider->retrieveByCredentials(
[$this->storageKey => $token]
);
$user = $this->provider->retrieveByCredentials([
$this->storageKey => $this->hash ? hash('sha256', $token) : $token,
]);
}
return $this->user = $user;

View File

@@ -14,12 +14,13 @@
}
],
"require": {
"php": ">=5.6.4",
"illuminate/contracts": "5.4.*",
"illuminate/http": "5.4.*",
"illuminate/queue": "5.4.*",
"illuminate/support": "5.4.*",
"nesbot/carbon": "~1.20"
"php": "^8.0.2",
"illuminate/collections": "^9.0",
"illuminate/contracts": "^9.0",
"illuminate/http": "^9.0",
"illuminate/macroable": "^9.0",
"illuminate/queue": "^9.0",
"illuminate/support": "^9.0"
},
"autoload": {
"psr-4": {
@@ -28,13 +29,13 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.4-dev"
"dev-master": "9.x-dev"
}
},
"suggest": {
"illuminate/console": "Required to use the auth:clear-resets command (5.4.*).",
"illuminate/queue": "Required to fire login / logout events (5.4.*).",
"illuminate/session": "Required to use the session based guard (5.4.*)."
"illuminate/console": "Required to use the auth:clear-resets command (^9.0).",
"illuminate/queue": "Required to fire login / logout events (^9.0).",
"illuminate/session": "Required to use the session based guard (^9.0)."
},
"config": {
"sort-packages": true