1
|
|
|
<?php |
2
|
|
|
namespace Nubs\PwMan\Command; |
3
|
|
|
|
4
|
|
|
use Exception; |
5
|
|
|
use GnuPG; |
6
|
|
|
use Nubs\PwMan\PasswordFile; |
7
|
|
|
use Nubs\PwMan\PasswordGenerator; |
8
|
|
|
use Nubs\PwMan\PasswordManager; |
9
|
|
|
use Nubs\Sensible\CommandFactory\EditorFactory; |
10
|
|
|
use Nubs\Which\LocatorFactory\PlatformLocatorFactory as WhichLocatorFactory; |
11
|
|
|
use Symfony\Component\Console\Command\Command; |
12
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
13
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
14
|
|
|
use Symfony\Component\Console\Input\InputOption; |
15
|
|
|
use Symfony\Component\Console\Output\ConsoleOutput; |
16
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
17
|
|
|
use Symfony\Component\Process\ProcessBuilder; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* A symonfy console command to set passwords in a password file. |
21
|
|
|
*/ |
22
|
|
|
class Set extends Command |
23
|
|
|
{ |
24
|
|
|
/** |
25
|
|
|
* Configures the command's options. |
26
|
|
|
* |
27
|
|
|
* @return void |
28
|
|
|
*/ |
29
|
|
|
protected function configure() |
30
|
|
|
{ |
31
|
|
|
$this->setName('set') |
32
|
|
|
->setDescription('Sets the password for the specified application') |
33
|
|
|
->addArgument('password-file', InputArgument::REQUIRED, 'The path to the encrypted password file') |
34
|
|
|
->addOption('application', 'a', InputOption::VALUE_REQUIRED, 'The application to configure') |
35
|
|
|
->addOption('decrypt-key', 'd', InputOption::VALUE_REQUIRED, 'The uid or fingerprint for the decryption key') |
36
|
|
|
->addOption('decrypt-passphrase', 'y', InputOption::VALUE_REQUIRED, 'The passphrase for the decryption key') |
37
|
|
|
->addOption('encrypt-key', 'e', InputOption::VALUE_REQUIRED, 'The uid or fingerprint for the encryption key') |
38
|
|
|
->addOption('username', 'u', InputOption::VALUE_REQUIRED, 'The username for the application') |
39
|
|
|
->addOption('password', 'p', InputOption::VALUE_REQUIRED, 'The password for the application') |
40
|
|
|
->addOption('length', 'l', InputOption::VALUE_REQUIRED, 'The length of the random passwords for the application') |
41
|
|
|
->addOption('characters', 'c', InputOption::VALUE_REQUIRED, 'The allowed characters to use in randomly generated passwords') |
42
|
|
|
->addOption('exclude-characters', 'x', InputOption::VALUE_REQUIRED, 'The characters to exclude from randomly generated passwords'); |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Sets the password for the specified application. |
47
|
|
|
* |
48
|
|
|
* @param \Symfony\Component\Console\Input\InputInterface $input The command input. |
49
|
|
|
* @param \Symfony\Component\Console\Output\OutputInterface $output The command output. |
50
|
|
|
* @return int The return status |
51
|
|
|
*/ |
52
|
|
|
protected function execute(InputInterface $input, OutputInterface $output) |
53
|
|
|
{ |
54
|
|
|
$passwordFile = new PasswordFile($input->getArgument('password-file'), new GnuPG()); |
55
|
|
|
$passwordFile->addDecryptKey($input->getOption('decrypt-key') ?: '', $input->getOption('decrypt-passphrase') ?: ''); |
56
|
|
|
$passwords = $passwordFile->getPasswords(); |
57
|
|
|
if ($passwords === null) { |
58
|
|
|
return $this->_error($output, 'Failed to load passwords from file!'); |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
$application = $input->getOption('application') ?: ''; |
62
|
|
|
|
63
|
|
|
$passwordManager = new PasswordManager($passwords); |
64
|
|
|
$existingPasswords = $passwordManager->matchingApplication($application); |
65
|
|
|
|
66
|
|
|
$passwordsToEdit = empty($existingPasswords) ? $this->_newPasswordTemplate($input) : $existingPasswords; |
67
|
|
|
|
68
|
|
|
try { |
69
|
|
|
$passwordManager->replacePasswords($existingPasswords, $this->_alterPasswords($passwordsToEdit)); |
70
|
|
|
} catch (Exception $e) { |
71
|
|
|
return $this->_error($output, $e->getMessage()); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
$passwordFile->addEncryptKey($input->getOption('encrypt-key') ?: ''); |
75
|
|
|
$passwordFile->setPasswords($passwordManager->getPasswords()); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Prints an error message and returns the given error code. |
80
|
|
|
* |
81
|
|
|
* @param \Symfony\Component\Console\Output\OutputInterface $output The command output. |
82
|
|
|
* @param string $message The message to output. |
83
|
|
|
* @param int $code The return status. |
84
|
|
|
* @return int The return status |
85
|
|
|
*/ |
86
|
|
View Code Duplication |
private function _error(OutputInterface $output, $message, $code = 1) |
|
|
|
|
87
|
|
|
{ |
88
|
|
|
$stderr = $output instanceof ConsoleOutput ? $output->getErrorOutput() : $output; |
89
|
|
|
$stderr->writeln("<error>{$message}</error>"); |
90
|
|
|
|
91
|
|
|
return $code; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* Generates the simple format of a new password using command-line options. |
96
|
|
|
* |
97
|
|
|
* @param \Symfony\Component\Console\Input\InputInterface $input The command input. |
98
|
|
|
* @return array The password template. |
99
|
|
|
*/ |
100
|
|
|
private function _newPasswordTemplate(InputInterface $input) |
101
|
|
|
{ |
102
|
|
|
$application = $input->getOption('application') ?: ''; |
103
|
|
|
$username = $input->getOption('username') ?: ''; |
104
|
|
|
$password = $input->getOption('password') ?: $this->_generateRandomPassword($input); |
105
|
|
|
|
106
|
|
|
return [$application => ['application' => $application, 'username' => $username, 'password' => $password]]; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Alters the passwords with the user's changes returning the passwords to use instead. |
111
|
|
|
* |
112
|
|
|
* @param array $passwords The passwords to replace. |
113
|
|
|
* @return array The new passwords to use instead. |
114
|
|
|
*/ |
115
|
|
|
private function _alterPasswords(array $passwords) |
116
|
|
|
{ |
117
|
|
|
$commandLocatorFactory = new WhichLocatorFactory(); |
118
|
|
|
$editorFactory = new EditorFactory($commandLocatorFactory->create()); |
119
|
|
|
$editor = $editorFactory->create(); |
120
|
|
|
|
121
|
|
|
$updates = json_decode($editor->editData(new ProcessBuilder(), json_encode($passwords, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT)), true); |
122
|
|
|
|
123
|
|
|
if ($updates === null) { |
124
|
|
|
throw new Exception('Invalid json for application!'); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
return $updates; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Generates a random password using the command line options. |
132
|
|
|
* |
133
|
|
|
* @param \Symfony\Component\Console\Input\InputInterface $input The command input. |
134
|
|
|
* @return string A random password. |
135
|
|
|
*/ |
136
|
|
|
private function _generateRandomPassword(InputInterface $input) |
137
|
|
|
{ |
138
|
|
|
$randomCharacterRange = str_split($input->getOption('characters') ?: PasswordGenerator::defaultCharacters()); |
139
|
|
|
$excludeCharacters = str_split($input->getOption('exclude-characters') ?: []); |
140
|
|
|
$randomCharacterRange = array_diff($randomCharacterRange, $excludeCharacters); |
141
|
|
|
$passwordGenerator = new PasswordGenerator(join('', $randomCharacterRange), $input->getOption('length') ?: 32); |
142
|
|
|
|
143
|
|
|
return $passwordGenerator(); |
144
|
|
|
} |
145
|
|
|
} |
146
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.