Set   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 124
Duplicated Lines 5.65 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 21
lcom 1
cbo 10
dl 7
loc 124
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 15 1
C execute() 0 25 8
A _error() 7 7 2
A _newPasswordTemplate() 0 8 4
A _alterPasswords() 0 14 2
A _generateRandomPassword() 0 9 4

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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