Failed Conditions
Push — issue#702 ( 91bd46...0b5bf0 )
by Guilherme
19:37 queued 12:15
created

ConvertSubjectIdentifiersCommand::getClient()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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