Skip to content

Commit

Permalink
Merge pull request #56 from robbieaverill/pulls/1.0/lock-command
Browse files Browse the repository at this point in the history
NEW Add lock and unlock commands for member
  • Loading branch information
robingram authored Jun 12, 2018
2 parents 2eccc1d + e170561 commit ac00785
Show file tree
Hide file tree
Showing 15 changed files with 333 additions and 27 deletions.
2 changes: 2 additions & 0 deletions console.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Commands:
- SilverLeague\Console\Command\Member\ChangeGroupsCommand
- SilverLeague\Console\Command\Member\ChangePasswordCommand
- SilverLeague\Console\Command\Member\CreateCommand
- SilverLeague\Console\Command\Member\LockCommand
- SilverLeague\Console\Command\Member\UnlockCommand
- SilverLeague\Console\Command\Object\ChildrenCommand
- SilverLeague\Console\Command\Object\DebugCommand
- SilverLeague\Console\Command\Object\ExtensionsCommand
Expand Down
2 changes: 2 additions & 0 deletions docs/en/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ This documentation section contains instructions and examples of the commands bu
* [`member:change-groups`](commands/member-change-groups.md): Change a member's groups
* [`member:change-password`](commands/member-change-password.md): Change a member's password
* [`member:create`](commands/member-create.md): Create a new member, and optionally add them to groups
* [`member:lock`](commands/member-lock.md): Lock a member
* [`member:unlock`](commands/member-unlock.md): Unlock a member

### Object debugging

Expand Down
22 changes: 22 additions & 0 deletions docs/en/commands/member-lock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Command: `member:lock`

Locks a member for the duration of `Member.lock_out_delay_mins` minutes.

## Usage

```shell
$ ssconsole member:lock [<email>]
```

## Options

| Type | Name | Required | Description | Options | Default |
| --- | --- | --- | --- | --- | --- |
| Argument | `email` | Yes | The email address for the member | _string_ | Prompt |

## Example

```
$ ssconsole member:lock [email protected]
Member [email protected] locked for 15 mins.
```
22 changes: 22 additions & 0 deletions docs/en/commands/member-unlock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Command: `member:unlock`

Unlocks a member.

## Usage

```shell
$ ssconsole member:unlock [<email>]
```

## Options

| Type | Name | Required | Description | Options | Default |
| --- | --- | --- | --- | --- | --- |
| Argument | `email` | Yes | The email address for the member | _string_ | Prompt |

## Example

```
$ ssconsole member:unlock [email protected]
Member [email protected] unlocked.
```
4 changes: 2 additions & 2 deletions docs/en/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
It is recommended to install this module globally with composer:

```shell
composer global require silverleague/console
composer global require silverleague/ssconsole
```

Ensure your composer's `bin` folder has been added to your system path.
Expand All @@ -17,7 +17,7 @@ Ensure your composer's `bin` folder has been added to your system path.
You can still require this module as a project dependency if you don't want to install it globally, of course:

```shell
composer require --dev silverleague/console
composer require --dev silverleague/ssconsole
$ vendor/bin/ssconsole
```

Expand Down
43 changes: 43 additions & 0 deletions src/Command/Member/AbstractMemberCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace SilverLeague\Console\Command\Member;

use SilverLeague\Console\Command\SilverStripeCommand;
use SilverStripe\Security\Member;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
* Provides a base for member related commands, lookups etc
*
* @package silverstripe-console
* @author Robbie Averill <[email protected]>
*/
abstract class AbstractMemberCommand extends SilverStripeCommand
{
/**
* Get a member by the provided email address, output an error message if not found
*
* @param InputInterface $input
* @param OutputInterface $output
* @return Member|false
*/
protected function getMember(InputInterface $input, OutputInterface $output)
{
$email = $this->getOrAskForArgument($input, $output, 'email', 'Enter email address: ');
if (empty($email)) {
$output->writeln('<error>Please enter an email address.</error>');
return false;
}

/** @var Member $member */
$member = Member::get()->filter('email', $email)->first();
if (!$member) {
$output->writeln('<error>Member with email "' . $email . '" was not found.');
return false;
}

return $member;
}
}

12 changes: 4 additions & 8 deletions src/Command/Member/ChangeGroupsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

namespace SilverLeague\Console\Command\Member;

use SilverLeague\Console\Command\SilverStripeCommand;
use SilverStripe\Security\Group;
use SilverStripe\Security\Member;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
Expand All @@ -16,7 +14,7 @@
* @package silverstripe-console
* @author Robbie Averill <[email protected]>
*/
class ChangeGroupsCommand extends SilverStripeCommand
class ChangeGroupsCommand extends AbstractMemberCommand
{
/**
* {@inheritDoc}
Expand All @@ -34,16 +32,14 @@ protected function configure()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$email = $this->getOrAskForArgument($input, $output, 'email', 'Enter email address: ');
$member = Member::get()->filter('email', $email)->first();
$member = $this->getMember($input, $output);
if (!$member) {
$output->writeln('<error>Member with email "' . $email . '" was not found.');
return;
}

if ($member->Groups()->count()) {
$output->writeln(
'Member <info>' . $email . '</info> is already in the following groups (will be overwritten):'
'Member <info>' . $member->Email . '</info> is already in the following groups (will be overwritten):'
);
$output->writeln(' ' . implode(', ', $member->Groups()->column('Code')));
$output->writeln('');
Expand All @@ -55,7 +51,7 @@ protected function execute(InputInterface $input, OutputInterface $output)

$newGroups = $this->getHelper('question')->ask($input, $output, $question);

$output->writeln('Adding <info>' . $email . '</info> to groups: ' . implode(', ', $newGroups));
$output->writeln('Adding <info>' . $member->Email . '</info> to groups: ' . implode(', ', $newGroups));
// $member->Groups()->removeAll();
foreach ($newGroups as $group) {
$member->addToGroupByCode($group);
Expand Down
8 changes: 2 additions & 6 deletions src/Command/Member/ChangePasswordCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

namespace SilverLeague\Console\Command\Member;

use SilverLeague\Console\Command\SilverStripeCommand;
use SilverStripe\ORM\ValidationResult;
use SilverStripe\Security\Member;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
Expand All @@ -15,7 +13,7 @@
* @package silverstripe-console
* @author Robbie Averill <[email protected]>
*/
class ChangePasswordCommand extends SilverStripeCommand
class ChangePasswordCommand extends AbstractMemberCommand
{
/**
* {@inheritDoc}
Expand All @@ -41,10 +39,8 @@ protected function execute(InputInterface $input, OutputInterface $output)
return;
}

/** @var Member $member */
$member = Member::get()->filter('email', $email)->first();
$member = $this->getMember($input, $output);
if (!$member) {
$output->writeln('<error>Member with email "' . $email . '" was not found.');
return;
}

Expand Down
7 changes: 7 additions & 0 deletions src/Command/Member/CreateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ protected function execute(InputInterface $input, OutputInterface $output)
return;
}

// Check for existing member
$member = Member::get()->filter(['Email' => $data['Email']])->first();
if ($member) {
$output->writeln('<error>Member already exists with email address: ' . $data['Email']);
return;
}

$member = Member::create();
foreach ($data as $key => $value) {
$member->setField($key, $value);
Expand Down
42 changes: 42 additions & 0 deletions src/Command/Member/LockCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace SilverLeague\Console\Command\Member;

use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\Security\Member;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
* Lock a user
*
* @package silverstripe-console
* @author Robbie Averill <[email protected]>
*/
class LockCommand extends AbstractMemberCommand
{
protected function configure()
{
$this
->setName('member:lock')
->setDescription('Lock a member account')
->addArgument('email', InputArgument::OPTIONAL, 'Email address');
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$member = $this->getMember($input, $output);
if (!$member) {
return;
}

$lockoutMins = Member::config()->get('lock_out_delay_mins');
$member->LockedOutUntil = date('Y-m-d H:i:s', DBDatetime::now()->getTimestamp() + $lockoutMins * 60);
$member->FailedLoginCount = 0;
$member->write();

$output->writeln('Member <info>' . $member->Email . '</info> locked for <info>' . $lockoutMins . ' mins.</info>');
}
}

39 changes: 39 additions & 0 deletions src/Command/Member/UnlockCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace SilverLeague\Console\Command\Member;

use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
* Unlock a user
*
* @package silverstripe-console
* @author Robbie Averill <[email protected]>
*/
class UnlockCommand extends AbstractMemberCommand
{
protected function configure()
{
$this
->setName('member:unlock')
->setDescription('Unlock a member account')
->addArgument('email', InputArgument::OPTIONAL, 'Email address');
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$member = $this->getMember($input, $output);
if (!$member) {
return;
}

$member->LockedOutUntil = null;
$member->FailedLoginCount = 0;
$member->write();

$output->writeln('Member <info>' . $member->Email . '</info> unlocked.');
}
}

17 changes: 11 additions & 6 deletions tests/Command/Member/CreateCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,19 @@ public function testExecute()

$this->command->getApplication()->getHelperSet()->set($questionHelper, 'question');

$tester = $this->executeTest(
[
'email' => '[email protected]',
'password' => 'OpenSe$am3!'
]
);
$tester = $this->executeTest([
'email' => '[email protected]',
'password' => 'OpenSe$am3!',
]);
$output = $tester->getDisplay();
$this->assertContains('Member created', $output);

$tester = $this->executeTest([
'email' => '[email protected]',
'password' => 'OpenSe$am3!',
]);
$output = $tester->getDisplay();
$this->assertContains('Member already exists', $output);
}

/**
Expand Down
65 changes: 65 additions & 0 deletions tests/Command/Member/LockCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace SilverLeague\Console\Tests\Command\Member;

use SilverLeague\Console\Tests\Command\AbstractCommandTest;
use SilverStripe\Security\Member;

/**
* Test the lock member command
*
* @package silverstripe-console
* @author Robbie Averill <[email protected]>
*/
class LockCommandTest extends AbstractCommandTest
{
/**
* Delete fixtured members after tests have run
*/
protected function tearDown()
{
parent::tearDown();

$testMember = Member::get()->filter(['Email' => '[email protected]'])->first();
if ($testMember && $testMember->exists()) {
$testMember->delete();
}
}

protected function getTestCommand()
{
return 'member:lock';
}

public function testExecute()
{
$member = $this->createMember();
$this->assertFalse($member->isLockedOut());

$tester = $this->executeTest(['email' => '[email protected]']);
/** @var Member $member */
$member = Member::get()->byID($member->ID);
$this->assertContains('Member [email protected] locked for', $tester->getDisplay());
$this->assertTrue($member->isLockedOut());
}

public function testMemberNotFound()
{
$result = $this->executeTest(['email' => '[email protected]']);
$this->assertContains('Member with email "[email protected]" was not found.', $result->getDisplay());
}

/**
* Creates a dummy user for testing with
*
* @return Member
*/
protected function createMember()
{
$member = Member::create();
$member->Email = '[email protected]';
$member->Password = 'Opensesame1';
$member->write();
return $member;
}
}
Loading

0 comments on commit ac00785

Please sign in to comment.