Passed
Push — master ( 13c68b...d6cf3f )
by Angel Fernando Quiroz
09:49 queued 19s
created

AzureSyncAbstractCommand::getUpdateActionByRole()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 16
nc 1
nop 0
dl 0
loc 22
rs 9.7333
c 2
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
declare(strict_types=1);
6
7
namespace Chamilo\CoreBundle\Command;
8
9
use Chamilo\CoreBundle\Entity\AzureSyncState;
10
use Chamilo\CoreBundle\Helpers\AccessUrlHelper;
11
use Chamilo\CoreBundle\Helpers\AuthenticationConfigHelper;
12
use Chamilo\CoreBundle\Helpers\AzureAuthenticatorHelper;
13
use Chamilo\CoreBundle\Helpers\UserHelper;
14
use Chamilo\CoreBundle\Repository\AzureSyncStateRepository;
15
use Chamilo\CoreBundle\Repository\Node\UsergroupRepository;
16
use Chamilo\CoreBundle\Repository\Node\UserRepository;
17
use Chamilo\CoreBundle\Settings\SettingsManager;
18
use Doctrine\ORM\EntityManagerInterface;
19
use Exception;
20
use Generator;
21
use GuzzleHttp\Exception\GuzzleException;
22
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
23
use KnpU\OAuth2ClientBundle\Client\Provider\AzureClient;
24
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
25
use League\OAuth2\Client\Token\AccessTokenInterface;
26
use Symfony\Component\Console\Command\Command;
27
use TheNetworg\OAuth2\Client\Provider\Azure;
28
29
use const PHP_URL_QUERY;
30
31
abstract class AzureSyncAbstractCommand extends Command
32
{
33
    protected AzureClient $client;
34
35
    protected Azure $provider;
36
37
    protected array $providerParams = [];
38
39
    public function __construct(
40
        protected readonly ClientRegistry $clientRegistry,
41
        readonly AuthenticationConfigHelper $configHelper,
42
        readonly protected AzureAuthenticatorHelper $azureHelper,
43
        readonly protected AzureSyncStateRepository $syncStateRepo,
44
        protected readonly EntityManagerInterface $entityManager,
45
        protected readonly UserRepository $userRepository,
46
        protected readonly UsergroupRepository $usergroupRepository,
47
        protected readonly AccessUrlHelper $accessUrlHelper,
48
        protected readonly SettingsManager $settingsManager,
49
        protected readonly UserHelper $userHelper,
50
    ) {
51
        parent::__construct();
52
53
        $this->providerParams = $configHelper->getProviderConfig('azure');
54
55
        $this->client = $this->clientRegistry->getClient('azure');
56
57
        $this->provider = $this->client->getOAuth2Provider();
58
        $this->provider->tenant = $this->providerParams['tenant'] ?? null;
59
    }
60
61
    /**
62
     * @throws GuzzleException
63
     * @throws IdentityProviderException
64
     */
65
    protected function generateOrRefreshToken(?AccessTokenInterface &$token): void
66
    {
67
        if (!$token || ($token->getExpires() && !$token->getRefreshToken())) {
68
            $token = $this->provider->getAccessToken(
69
                'client_credentials',
70
                ['resource' => $this->provider->resource]
71
            );
72
        }
73
    }
74
75
    /**
76
     * @throws Exception
77
     */
78
    protected function getAzureUsers(): Generator
79
    {
80
        if ($this->providerParams['script_users_delta']) {
81
            $usersDeltaLink = $this->syncStateRepo->findOneBy(['title' => AzureSyncState::USERS_DATALINK]);
82
83
            $query = $usersDeltaLink
84
                ? $usersDeltaLink->getValue()
85
                : \sprintf('$select=%s', implode(',', AzureAuthenticatorHelper::QUERY_USER_FIELDS));
86
        } else {
87
            $query = \sprintf(
88
                '$top=%d&$select=%s',
89
                AzureSyncState::API_PAGE_SIZE,
90
                implode(',', AzureAuthenticatorHelper::QUERY_USER_FIELDS)
91
            );
92
        }
93
94
        $token = null;
95
96
        do {
97
            try {
98
                $this->generateOrRefreshToken($token);
99
100
                $azureUsersRequest = $this->provider->get(
101
                    $this->providerParams['script_users_delta'] ? "/v1.0/users/delta?$query" : "/v1.0/users?$query",
102
                    $token
103
                );
104
            } catch (GuzzleException|Exception $e) {
105
                throw new Exception('Exception when requesting users from Azure: '.$e->getMessage());
106
            }
107
108
            $azureUsersInfo = $azureUsersRequest ?? [];
109
110
            foreach ($azureUsersInfo as $azureUserInfo) {
111
                $azureUserInfo['mail'] = $azureUserInfo['mail'] ?? null;
112
                $azureUserInfo['surname'] = $azureUserInfo['surname'] ?? null;
113
                $azureUserInfo['givenName'] = $azureUserInfo['givenName'] ?? null;
114
115
                yield $azureUserInfo;
116
            }
117
118
            $hasNextLink = false;
119
120
            if (!empty($azureUsersRequest['@odata.nextLink'])) {
121
                $hasNextLink = true;
122
                $query = parse_url($azureUsersRequest['@odata.nextLink'], PHP_URL_QUERY);
123
            }
124
125
            if ($this->providerParams['script_users_delta'] && !empty($azureUsersRequest['@odata.deltaLink'])) {
126
                $this->syncStateRepo->save(
127
                    AzureSyncState::USERS_DATALINK,
128
                    parse_url($azureUsersRequest['@odata.deltaLink'], PHP_URL_QUERY),
129
                );
130
            }
131
        } while ($hasNextLink);
132
    }
133
134
    /**
135
     * @throws Exception
136
     */
137
    protected function getAzureGroupMembers(string $groupUid): Generator
138
    {
139
        $query = \sprintf(
140
            '$top=%d&$select=%s',
141
            AzureSyncState::API_PAGE_SIZE,
142
            implode(',', AzureAuthenticatorHelper::QUERY_GROUP_MEMBERS_FIELDS)
143
        );
144
145
        $token = null;
146
147
        do {
148
            try {
149
                $this->generateOrRefreshToken($token);
150
151
                $azureGroupMembersRequest = $this->provider->get(
152
                    "/v1.0/groups/$groupUid/members?$query",
153
                    $token
154
                );
155
            } catch (GuzzleException|Exception $e) {
156
                throw new Exception('Exception when requesting group members from Azure: '.$e->getMessage());
157
            }
158
159
            $azureGroupMembers = $azureGroupMembersRequest ?? [];
160
161
            foreach ($azureGroupMembers as $azureGroupMember) {
162
                yield $azureGroupMember;
163
            }
164
165
            $hasNextLink = false;
166
167
            if (!empty($azureGroupMembersRequest['@odata.nextLink'])) {
168
                $hasNextLink = true;
169
                $query = parse_url($azureGroupMembersRequest['@odata.nextLink'], PHP_URL_QUERY);
170
            }
171
        } while ($hasNextLink);
172
    }
173
174
    /**
175
     * @throws Exception
176
     */
177
    protected function getAzureGroups(): Generator
178
    {
179
        $getUsergroupsDelta = 'true' === $this->providerParams['script_usergroups_delta'];
180
181
        if ($getUsergroupsDelta) {
182
            $usergroupsDeltaLink = $this->syncStateRepo->findOneBy(['title' => AzureSyncState::USERGROUPS_DATALINK]);
183
184
            $query = $usergroupsDeltaLink
185
                ? $usergroupsDeltaLink->getValue()
186
                : sprintf('$select=%s', implode(',', AzureAuthenticatorHelper::QUERY_GROUP_FIELDS));
187
        } else {
188
            $query = sprintf(
189
                '$top=%d&$select=%s',
190
                AzureSyncState::API_PAGE_SIZE,
191
                implode(',', AzureAuthenticatorHelper::QUERY_GROUP_FIELDS)
192
            );
193
        }
194
195
        $token = null;
196
197
        do {
198
            try {
199
                $this->generateOrRefreshToken($token);
200
201
                $azureGroupsRequest = $this->provider->get(
202
                    $getUsergroupsDelta ? "/v1.0/groups/delta?$query" : "/v1.0/groups?$query",
203
                    $token
204
                );
205
            } catch (Exception|GuzzleException $e) {
206
                throw new Exception('Exception when requesting groups from Azure: '.$e->getMessage());
207
            }
208
209
            $azureGroupsInfo = $azureGroupsRequest ?? [];
210
211
            foreach ($azureGroupsInfo as $azureGroupInfo) {
212
                if (!empty($this->providerParams['group_filter_regex']) &&
213
                    !preg_match("/{$this->providerParams['group_filter_regex']}/", $azureGroupInfo['displayName'])
214
                ) {
215
                    continue;
216
                }
217
218
                yield $azureGroupInfo;
219
            }
220
221
            $hasNextLink = false;
222
223
            if (!empty($azureGroupsRequest['@odata.nextLink'])) {
224
                $hasNextLink = true;
225
                $query = parse_url($azureGroupsRequest['@odata.nextLink'], PHP_URL_QUERY);
226
            }
227
228
            if ($getUsergroupsDelta && !empty($azureGroupsRequest['@odata.deltaLink'])) {
229
                $this->syncStateRepo->save(
230
                    AzureSyncState::USERGROUPS_DATALINK,
231
                    parse_url($azureGroupsRequest['@odata.deltaLink'], PHP_URL_QUERY),
232
                );
233
            }
234
        } while ($hasNextLink);
235
    }
236
}
237