Failed Conditions
Push — issue#774 ( 057f04 )
by Guilherme
15:16
created

ConvertSubjectIdentifiersCommand   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 212
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 0
loc 212
ccs 0
cts 136
cp 0
rs 10
c 0
b 0
f 0
wmc 23

8 Methods

Rating   Name   Duplication   Size   Complexity  
A checkAndOpenFile() 0 13 4
B checkCount() 0 22 4
A getClientMetadata() 0 11 2
A getAuthorizationRepository() 0 6 1
A configure() 0 9 1
C execute() 0 72 8
A getClient() 0 12 2
A getPublicSubQuery() 0 15 1
1
<?php
2
/**
3
 * This file is part of the login-cidadao project or it's bundles.
4
 *
5
 * (c) Guilherme Donato <guilhermednt on github>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace LoginCidadao\OpenIDBundle\Command;
12
13
use Doctrine\ORM\EntityManagerInterface;
14
use Doctrine\ORM\NonUniqueResultException;
15
use Doctrine\ORM\NoResultException;
16
use LoginCidadao\CoreBundle\Entity\Authorization;
17
use LoginCidadao\CoreBundle\Entity\AuthorizationRepository;
18
use LoginCidadao\OAuthBundle\Model\ClientInterface;
19
use LoginCidadao\OpenIDBundle\Entity\ClientMetadata;
20
use LoginCidadao\OpenIDBundle\Manager\ClientManager;
21
use LoginCidadao\OpenIDBundle\Service\SubjectIdentifierService;
22
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
23
use Symfony\Component\Console\Input\InputArgument;
24
use Symfony\Component\Console\Input\InputInterface;
25
use Symfony\Component\Console\Input\InputOption;
26
use Symfony\Component\Console\Output\OutputInterface;
27
use Symfony\Component\Console\Style\SymfonyStyle;
28
29
class ConvertSubjectIdentifiersCommand extends ContainerAwareCommand
30
{
31
    /** @var EntityManagerInterface */
32
    private $em;
33
34
    /** @var array */
35
    private $clientMetadata = [];
36
37
    protected function configure()
38
    {
39
        $this
40
            ->setName('lc:oidc:convert-subject-identifiers')
41
            ->setDescription("Convert Subject Identifiers, from public to pairwise.")
42
            ->addArgument('client', InputArgument::REQUIRED, "Client's ID")
43
            ->addArgument('file', InputArgument::REQUIRED, "File where to save the conversion result")
44
            ->addOption('dry-run', null, InputOption::VALUE_NONE,
45
                'Prevent changes from being persisted on the database');
46
    }
47
48
    /**
49
     * @param InputInterface $input
50
     * @param OutputInterface $output
51
     * @return int|null|void
52
     */
53
    protected function execute(InputInterface $input, OutputInterface $output)
54
    {
55
        $dryRyn = $input->hasOption('dry-run');
56
        $io = new SymfonyStyle($input, $output);
57
58
        $io->title('Convert Public Subject Identifiers to Pairwise');
59
60
        $fp = $this->checkAndOpenFile($io, $input);
61
        if ($fp === false) {
62
            return;
63
        }
64
65
        $this->em = $this->getContainer()->get('doctrine.orm.entity_manager');
66
        $client = $this->getClient($input->getArgument('client'));
67
68
        $count = $this->checkCount($io, $client);
69
        if ($count === null) {
70
            fclose($fp);
71
72
            return;
73
        }
74
75
        $publicSubs = $this->getPublicSubQuery($client)
76
            ->getQuery()->iterate();
77
78
        $io->section("Generating Sector Identifiers...");
79
80
        /** @var SubjectIdentifierService $subIdService */
81
        $subIdService = $this->getContainer()->get('oidc.subject_identifier.service');
82
83
        $metadata = $this->getClientMetadata($client);
84
        $metadata->setSubjectType('pairwise');
85
86
        $io->progressStart($count);
87
        $this->em->beginTransaction();
88
        $done = 0;
89
        try {
90
            foreach ($publicSubs as $row) {
91
                /** @var Authorization $auth */
92
                $auth = $row[0];
93
                $person = $auth->getPerson();
94
95
                $sub = $subIdService->convertSubjectIdentifier($person, $metadata);
96
97
                $row = [$person->getId(), $sub->getSubjectIdentifier()];
98
                if (false === fputcsv($fp, $row)) {
99
                    throw new \RuntimeException('Error writing CSV to file! Aborting!');
100
                }
101
102
                $this->em->persist($sub);
103
                if ($done++ % 50 === 0) {
104
                    $this->em->clear();
105
                }
106
                $io->progressAdvance();
107
            }
108
            $io->progressFinish();
109
110
            if ($dryRyn) {
111
                $this->em->rollback();
112
                $io->note("Dry Run: no changes were persisted.");
113
            } else {
114
                $this->em->commit();
115
            }
116
            $this->em->clear();
117
118
            $io->success("Done! {$done} Authorizations updated!");
119
        } catch (\Exception $e) {
120
            $io->error($e->getMessage());
121
            $this->em->rollback();
122
            $this->em->clear();
123
        }
124
        fclose($fp);
125
    }
126
127
    /**
128
     * @return AuthorizationRepository
129
     */
130
    private function getAuthorizationRepository()
131
    {
132
        /** @var AuthorizationRepository $repo */
133
        $repo = $this->em->getRepository('LoginCidadaoCoreBundle:Authorization');
134
135
        return $repo;
136
    }
137
138
    /**
139
     * @param ClientInterface $client
140
     * @return ClientMetadata
141
     */
142
    private function getClientMetadata(ClientInterface $client)
143
    {
144
        $id = $client->getId();
145
        if (array_key_exists($id, $this->clientMetadata)) {
146
            $metadata = $this->clientMetadata[$id];
147
        } else {
148
            $metadata = $client->getMetadata();
149
            $this->clientMetadata[$id] = $metadata;
150
        }
151
152
        return $metadata;
153
    }
154
155
    /**
156
     * @param $clientId
157
     * @return ClientInterface
158
     */
159
    private function getClient($clientId)
160
    {
161
        /** @var ClientManager $clientManager */
162
        $clientManager = $this->getContainer()->get('lc.client_manager');
163
164
        $client = $clientManager->getClientById($clientId);
165
        if (!$client instanceof ClientInterface) {
166
            // TODO: use appropriate exception
167
            throw new \RuntimeException('Client not found');
168
        }
169
170
        return $client;
171
    }
172
173
    /**
174
     * @param ClientInterface $client
175
     * @return \Doctrine\ORM\QueryBuilder
176
     */
177
    private function getPublicSubQuery(ClientInterface $client)
178
    {
179
        $repo = $this->getAuthorizationRepository();
180
181
        return $repo->createQueryBuilder('a')
182
            ->innerJoin('LoginCidadaoCoreBundle:Person', 'p', 'WITH', 'a.person = p')
183
            ->leftJoin(
184
                'LoginCidadaoOpenIDBundle:SubjectIdentifier',
185
                's',
186
                'WITH',
187
                's.person = a.person AND s.client = a.client'
188
            )
189
            ->where('CAST(p.id AS text) = s.subjectIdentifier')
190
            ->andWhere('a.client = :client')
191
            ->setParameter('client', $client);
192
    }
193
194
    /**
195
     * @param SymfonyStyle $io
196
     * @param ClientInterface $client
197
     * @return int|null
198
     */
199
    private function checkCount(SymfonyStyle $io, ClientInterface $client)
200
    {
201
        try {
202
            $count = $this->getPublicSubQuery($client)
203
                ->select('COUNT(a)')
204
                ->getQuery()
205
                ->getSingleScalarResult();
206
        } catch (NoResultException $e) {
207
            $count = 0;
208
        } catch (NonUniqueResultException $e) {
209
            $io->error("Error counting public subject identifiers: {$e->getMessage()}");
210
211
            return null;
212
        }
213
214
        if ($count === 0) {
215
            $io->success("No changes needed. You're all set!");
216
217
            return null;
218
        }
219
220
        return $count;
221
    }
222
223
    /**
224
     * @param SymfonyStyle $io
225
     * @param InputInterface $input
226
     * @return false|resource file pointer on success or false on error
227
     */
228
    private function checkAndOpenFile(SymfonyStyle $io, InputInterface $input)
229
    {
230
        $file = $input->getArgument('file');
231
        if (file_exists($file) && !$io->confirm("File '{$file}' already exists. Override?", false)) {
232
            $io->comment('Aborted');
233
234
            return false;
235
        }
236
        if (false === $fp = fopen($file, 'w')) {
237
            $io->error("Error opening file '{$file}'...");
238
        }
239
240
        return $fp;
241
    }
242
}
243