Commit 834649f0 authored by Jacob Priddy's avatar Jacob Priddy 👌

Force users emplid to be prepended to doorcode on /me route

parent 97f2b6f0
Pipeline #12442 failed with stages
in 3 minutes and 3 seconds
......@@ -5,7 +5,7 @@ namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Source\UseCases\Users\GetUser\GetUserUseCase;
use Source\UseCases\Tokens\GetTokens\GetTokensUseCase;
use Source\UseCases\Users\UpdateUser\UpdateUserUseCase;
use Source\UseCases\Users\UpdateUser\UpdateCurrentUser;
use Source\UseCases\GroupUser\GetUserGroups\GetUserGroupsUseCase;
use Source\UseCases\DoorUser\UserDoorAccess\UserDoorAccessUseCase;
use Source\UseCases\Users\GetUser\APIPresenter as GetUserAPIPresenter;
......@@ -65,32 +65,34 @@ class MeController extends ApiController
/**
* Update the current user
*
* Update the currently logged in user.
* Update the currently logged in user. Automatically prepends emplid (if there is one) to the user
*
* @authenticated
* @bodyParam display_name string The user's display name. Example: Sheev Palpatine
* @bodyParam password string The user's new password. Minimum of 20 characters. Example: My Super Secret P455w0rd
* @bodyParam doorcode string The user's new doorcode, minimum of 11 digits. Example: 292889311069
*
* @response 422 {"message":"The given data was invalid.","errors":{"display_name":["The display name must be a string."],"password":["The password must be a string.","The password must be at least 20 characters."],"doorcode":["The doorcode must be a string.","The doorcode must be a number.","The doorcode must be between 11 and 255 digits."]}}
* @response 422 {"message":"The given data was invalid.","errors":{"display_name":["The display name must be a string."],"password":["The password must be a string.","The password must be at least 20 characters."],"doorcode":["The doorcode must be a string.","The doorcode must be a number.","The doorcode must be between 11 and 255 digits."]}}
*
* @param \Source\UseCases\Users\UpdateUser\UpdateUserUseCase $useCase
* @param \Source\UseCases\Users\UpdateUser\UpdateCurrentUser $useCase
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function update(UpdateUserUseCase $useCase): JsonResponse
public function update(UpdateCurrentUser $useCase): JsonResponse
{
$this->validate($this->request, [
'display_name' => 'string|max:255',
'password' => 'string|min:20|max:255',
'doorcode' => 'string|numeric|digits_between:11,255',
'doorcode' => 'string|numeric|digits_between:4,248',
]);
$presenter = new UpdateUserAPIPresenter();
$useCase->update($this->authorizer->getCurrentUserId(), $this->request->all(), $presenter);
if ($presenter->hasError()) {
$this->setStatusCode(400);
return $this->respondWithError($presenter->getViewModel()['message']);
}
......
......@@ -167,6 +167,7 @@ class UsersController extends ApiController
* @throws \Illuminate\Validation\ValidationException
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function update(UpdateUserUseCase $updateUser, string $userId): JsonResponse
{
......
<?php
namespace Source\UseCases\Users\UpdateUser;
use Source\Entities\User;
use Source\Entities\Password;
use Source\Authorization\Authorizer;
use Source\Entities\HashedSearchable;
use Source\Gateways\Users\UsersRepository;
use Source\Exceptions\EntityNotFoundException;
class UpdateCurrentUser implements UpdateUserUseCase
{
protected const MIN_USER_SET_DOORCODE_LEN = 11;
/**
* @var \Source\Authorization\Authorizer
*/
protected Authorizer $authorizer;
/**
* @var \Source\Gateways\Users\UsersRepository
*/
protected UsersRepository $users;
protected string $salt;
/**
* @param \Source\Authorization\Authorizer $authorizer
* @param \Source\Gateways\Users\UsersRepository $users
* @param string $salt
*/
public function __construct(Authorizer $authorizer, UsersRepository $users, string $salt)
{
$this->authorizer = $authorizer;
$this->users = $users;
$this->salt = $salt;
}
/**
* @inheritDoc
* @throws \Exception
*/
public function update(string $userId, array $attributes, Presenter $presenter): void
{
$this->authorizer->protectAdminRights($userId);
$user = $this->users->get($userId);
if (!$user) {
throw new EntityNotFoundException();
}
$response = new ResponseModel();
$doorcode = $attributes['doorcode'] ?? null;
if ($doorcode) {
if ($user->getEmplid()) {
$doorcode = $user->getEmplid() . $doorcode;
} else if (strlen($doorcode) < self::MIN_USER_SET_DOORCODE_LEN) {
$response->setError('Doorcode must be at least ' .
self::MIN_USER_SET_DOORCODE_LEN .
' digits long since you do not have an emplid');
$presenter->present($response);
return;
}
}
$newUser = new User(
$user->getId(),
$user->getFirstName(),
$user->getLastName(),
$attributes['display_name'] ?? $user->getDisplayName(),
$user->getEmail(),
$user->getEmplid(),
Password::hash($attributes['password'] ?? null) ?? $user->getPassword(),
HashedSearchable::hash($this->salt, $doorcode) ?? $user->getDoorcode(),
$user->getExpiresAt(),
$user->getCreatedAt(),
$user->getUpdatedAt()
);
$returnedUser = $this->users->update($user->getId(), $newUser);
if (!$returnedUser) {
$response->setError('Unable to update user.');
} else {
$response->setUser($returnedUser);
}
$presenter->present($response);
}
}
......@@ -2,7 +2,6 @@
namespace Source\UseCases\Users\UpdateUser;
use Exception;
use Carbon\Carbon;
use Source\Entities\User;
use Source\Entities\Password;
......@@ -40,7 +39,7 @@ class UpdateUser implements UpdateUserUseCase
/**
* @inheritDoc
* @throws Exception
* @throws \Exception
*/
public function update(string $userId, array $attributes, Presenter $presenter): void
{
......
......@@ -3,8 +3,6 @@
namespace Source\UseCases\Users\UpdateUser;
use Source\Exceptions\EntityNotFoundException;
interface UpdateUserUseCase
{
/**
......@@ -21,7 +19,8 @@ interface UpdateUserUseCase
* @param string $userId
* @param array $attributes
* @param Presenter $presenter
* @throws EntityNotFoundException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function update(string $userId, array $attributes, Presenter $presenter): void;
}
......@@ -24,6 +24,10 @@ class UpdateUserUseCaseServiceProvider extends ServiceProvider implements Deferr
$this->app->bind(UpdateUserUseCase::class, static function (Application $app) {
return new UpdateUser($app->make(Authorizer::class), $app->make(UsersRepository::class), config('app.key'));
});
$this->app->bind(UpdateCurrentUser::class, static function (Application $app) {
return new UpdateCurrentUser($app->make(Authorizer::class), $app->make(UsersRepository::class), config('app.key'));
});
}
/**
......@@ -31,6 +35,9 @@ class UpdateUserUseCaseServiceProvider extends ServiceProvider implements Deferr
*/
public function provides()
{
return [UpdateUserUseCase::class];
return [
UpdateUserUseCase::class,
UpdateCurrentUser::class
];
}
}
......@@ -72,9 +72,9 @@ class UpdateCurrentUserApiTest extends AuthenticatesWithApplicationTestCase
// Test for doorcode too short
[
[
'doorcode' => '1234567890',
'doorcode' => '123',
],
['doorcode' => ['The doorcode must be between 11 and 255 digits.']],
['doorcode' => ['The doorcode must be between 4 and 248 digits.']],
],
];
}
......@@ -85,6 +85,7 @@ class UpdateCurrentUserApiTest extends AuthenticatesWithApplicationTestCase
* @param array $message
* @dataProvider invalidAttributesProvider
* @throws EntityNotFoundException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_tests_the_validation_rules(array $data, array $message): void
{
......@@ -96,6 +97,7 @@ class UpdateCurrentUserApiTest extends AuthenticatesWithApplicationTestCase
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_changes_the_display_name(): void
{
......@@ -109,6 +111,7 @@ class UpdateCurrentUserApiTest extends AuthenticatesWithApplicationTestCase
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_changes_the_password(): void
{
......@@ -122,6 +125,7 @@ class UpdateCurrentUserApiTest extends AuthenticatesWithApplicationTestCase
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_changes_the_doorcode(): void
{
......@@ -135,6 +139,7 @@ class UpdateCurrentUserApiTest extends AuthenticatesWithApplicationTestCase
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_presents_error(): void
{
......@@ -148,7 +153,7 @@ class UpdateCurrentUserApiTest extends AuthenticatesWithApplicationTestCase
$this->handleTest([]);
$this->response->assertStatus(200);
$this->response->assertStatus(400);
$this->response->assertJson(['status' => 'error', 'message' => 'Unable to update user.']);
}
}
<?php
namespace Tests\Unit\Source\UseCases\Users\UpdateUser;
use Source\Entities\User;
use Source\Entities\Password;
use PHPUnit\Framework\TestCase;
use Tests\Doubles\AuthorizerStub;
use Source\Entities\HashedSearchable;
use Source\Exceptions\EntityNotFoundException;
use Tests\Doubles\InMemoryUsersRepositoryStub;
use Source\Gateways\Users\InMemoryUsersRepository;
use Source\UseCases\Users\UpdateUser\ResponseModel;
use Source\UseCases\Users\UpdateUser\UpdateCurrentUser;
class CurrentUserUpdateUseCaseTest extends TestCase
{
/**
* @var \Source\Gateways\Users\InMemoryUsersRepository
*/
protected InMemoryUsersRepository $usersRepository;
/**
* @var \Source\UseCases\Users\UpdateUser\UpdateCurrentUser
*/
protected UpdateCurrentUser $useCase;
/**
* @var \Source\UseCases\Users\UpdateUser\ResponseModel
*/
protected ResponseModel $response;
/**
* @var \Tests\Unit\Source\UseCases\Users\UpdateUser\PresenterStub
*/
protected PresenterStub $presenter;
/**
* @var \Tests\Doubles\AuthorizerStub
*/
protected AuthorizerStub $authorizer;
public function setUp(): void
{
parent::setUp();
$this->authorizer = new AuthorizerStub();
$this->usersRepository = new InMemoryUsersRepository();
$this->useCase = new UpdateCurrentUser($this->authorizer, $this->usersRepository, '');
$this->presenter = new PresenterStub();
}
/**
* @param string $userId
* @param array $attributes
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function handleTest(string $userId, array $attributes): void
{
$this->useCase->update($userId, $attributes, $this->presenter);
$this->response = $this->presenter->response;
}
/**
* @test
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function presenter_was_called(): void
{
$user = new User(69, 'special name', '', '', '', '1234');
$this->usersRepository->create($user);
$this->handleTest('69', ['doorcode' => '123']);
$this->assertTrue($this->presenter->wasPresenterCalled());
}
/**
* @test
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_updates_a_user(): void
{
$user = new User(69, 'camdiddle beerbong', '', '', '');
$this->usersRepository->create($user);
$this->handleTest('69', ['doorcode' => '123456789012', 'password' => 'reeee', 'display_name' => 'gary is a menace']);
$this->assertFalse($this->response->hasError());
$this->assertEquals('gary is a menace', $this->usersRepository->get('69')->getDisplayName());
$this->assertEquals(HashedSearchable::hash('', '123456789012'), $this->usersRepository->get('69')->getDoorcode());
$this->assertTrue($this->authorizer->adminRightsWereProtected());
}
/**
* @test
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_cannot_update_non_existent_user(): void
{
$this->expectException(EntityNotFoundException::class);
$this->handleTest('420', []);
}
/**
* @test
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_sets_error_on_fail(): void
{
$updatedUser = new User(420, 'updated name', '', '', '');
$stub = new InMemoryUsersRepositoryStub();
$stub->setUserToReturnOnGet($updatedUser);
$this->useCase = new UpdateCurrentUser($this->authorizer, $stub, '');
$this->handleTest('420', []);
$this->assertTrue($this->response->hasError());
$this->assertEquals('Unable to update user.', $this->response->getError());
}
/**
* @test
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_will_update_nothing_if_no_changes_are_required(): void
{
$user = new User(
69,
'scat',
'man',
'IM THE SCAT MAN',
'do biddle be bahhhh',
'hes the scat man',
new Password('hash'),
new HashedSearchable('hash')
);
$this->usersRepository->create($user);
$this->handleTest('69', []);
$this->assertFalse($this->response->hasError());
$this->assertEquals($user, $this->response->getUser());
$this->assertTrue($this->authorizer->adminRightsWereProtected());
}
/**
* @test
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_wont_set_if_doorcode_is_too_short_and_no_emplid(): void
{
$user = new User(
69,
'',
'',
'',
''
);
$this->usersRepository->create($user);
$this->handleTest('69', ['doorcode' => '1234']);
$this->assertTrue($this->response->hasError());
$this->assertEquals(
'Doorcode must be at least 11 digits long since you do not have an emplid',
$this->response->getError()
);
}
/**
* @test
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_prepends_emplid_to_doorcode(): void
{
$user = new User(69, '', '', '', '', 'emplid');
$this->usersRepository->create($user);
$this->handleTest('69', ['doorcode' => '1234']);
$this->assertFalse($this->response->hasError());
$this->assertEquals(HashedSearchable::hash('', 'emplid1234'), $this->usersRepository->get('69')->getDoorcode());
$this->assertTrue($this->authorizer->adminRightsWereProtected());
}
}
......@@ -59,7 +59,8 @@ class UseCaseTest extends TestCase
/**
* @param string $userId
* @param array $attributes
* @throws EntityNotFoundException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function handleTest(string $userId, array $attributes): void
{
......@@ -70,7 +71,8 @@ class UseCaseTest extends TestCase
/**
* @test
* @throws EntityNotFoundException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function presenter_was_called(): void
{
......@@ -84,7 +86,8 @@ class UseCaseTest extends TestCase
/**
* @test
* @throws EntityNotFoundException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_updates_a_user(): void
{
......@@ -102,7 +105,8 @@ class UseCaseTest extends TestCase
/**
* @test
* @throws EntityNotFoundException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_cannot_update_non_existent_user(): void
{
......@@ -114,7 +118,8 @@ class UseCaseTest extends TestCase
/**
* @test
* @throws EntityNotFoundException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_sets_error_on_fail(): void
{
......@@ -132,7 +137,8 @@ class UseCaseTest extends TestCase
/**
* @test
* @throws EntityNotFoundException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_doesnt_need_doorcode_or_password(): void
{
......@@ -149,7 +155,8 @@ class UseCaseTest extends TestCase
/**
* @test
* @throws EntityNotFoundException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_will_overwrite_pass_and_door_code(): void
{
......@@ -169,7 +176,8 @@ class UseCaseTest extends TestCase
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_will_set_expires_at(): void
{
......@@ -185,7 +193,8 @@ class UseCaseTest extends TestCase
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function it_will_update_nothing_if_no_changes_are_required(): void
{
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment