Generalizzazione sistema di autenticazione OAuth2

This commit is contained in:
Dasc3er 2021-09-23 17:13:19 +02:00
parent ac8602c41d
commit 5a5734c526
11 changed files with 214 additions and 148 deletions

View File

@ -23,6 +23,8 @@ use Carbon\Carbon;
use Common\SimpleModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Modules\Emails\OAuth2\Google;
use Modules\Emails\OAuth2\Microsoft;
use Notifications\EmailNotification;
use Traits\LocalPoolTrait;
@ -32,14 +34,20 @@ class Account extends Model
use LocalPoolTrait;
use SoftDeletes;
protected $table = 'em_accounts';
protected $casts = [
'oauth2_config' => 'array',
public static $providers = [
'microsoft' => [
'name' => 'Microsoft',
'class' => Microsoft::class,
'help' => 'https://docs.openstamanager.com/faq/configurazione-oauth2#microsoft',
],
'google' => [
'name' => 'Google',
'class' => Google::class,
'help' => 'https://docs.openstamanager.com/faq/configurazione-oauth2#google',
],
];
/** @var OAuth2 */
protected $gestoreOAuth2;
protected $table = 'em_accounts';
public function testConnection()
{
@ -61,17 +69,6 @@ class Account extends Model
return $result;
}
public function getGestoreOAuth2()
{
if (isset($this->gestoreOAuth2)) {
return $this->gestoreOAuth2;
}
$this->gestoreOAuth2 = new OAuth2($this);
return $this->gestoreOAuth2;
}
/* Relazioni Eloquent */
public function templates()
@ -79,6 +76,11 @@ class Account extends Model
return $this->hasMany(Template::class, 'id_account');
}
public function oauth2()
{
return $this->belongsTo(\Models\OAuth2::class, 'id_oauth2');
}
public function emails()
{
return $this->hasMany(Mail::class, 'id_account');

View File

@ -3,7 +3,6 @@
namespace Modules\Emails\OAuth2;
use League\OAuth2\Client\Provider\Google as OriginalProvider;
use Modules\Emails\Account;
class Google extends OriginalProvider implements ProviderInterface
{
@ -12,16 +11,6 @@ class Google extends OriginalProvider implements ProviderInterface
'accessType' => 'offline',
];
public function __construct(Account $account, $redirect_uri)
{
parent::__construct([
'clientId' => $account->client_id,
'clientSecret' => $account->client_secret,
'redirectUri' => $redirect_uri,
'accessType' => 'offline',
]);
}
public function getOptions()
{
return self::$options;

View File

@ -2,7 +2,6 @@
namespace Modules\Emails\OAuth2;
use Modules\Emails\Account;
use TheNetworg\OAuth2\Client\Provider\Azure;
class Microsoft extends Azure implements ProviderInterface
@ -24,18 +23,15 @@ class Microsoft extends Azure implements ProviderInterface
],
];
public function __construct(Account $account, $redirect_uri)
public function __construct(array $options = [], array $collaborators = [])
{
parent::__construct([
'clientId' => $account->client_id,
'clientSecret' => $account->client_secret,
'redirectUri' => $redirect_uri,
'accessType' => 'offline',
// Configurazioni specifiche per il provider di Microsoft Azure
$config = array_merge($options, [
'defaultEndPointVersion' => parent::ENDPOINT_VERSION_2_0,
'tenant' => $options['tenant_id'],
]);
// Configurazioni specifiche per il provider di Microsoft Azure
$this->defaultEndPointVersion = parent::ENDPOINT_VERSION_2_0;
$this->tenant = $account->oauth2_config['tenant_id'];
parent::__construct($config, $collaborators);
}
public function getOptions()

View File

@ -2,12 +2,8 @@
namespace Modules\Emails\OAuth2;
use Modules\Emails\Account;
interface ProviderInterface
{
public function __construct(Account $account, $redirect_uri);
/**
* Restituisce l'array di configurazione per la connessione remota al servizio del provider.
*

View File

@ -17,6 +17,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use Models\Module;
use Models\OAuth2;
use Modules\Emails\Account;
include_once __DIR__.'/../../core.php';
switch (filter('op')) {
@ -34,6 +38,8 @@ switch (filter('op')) {
break;
case 'update':
$account = Account::find($id_record);
$predefined = post('predefined');
if (!empty($predefined)) {
$dbo->query('UPDATE em_accounts SET predefined = 0');
@ -55,26 +61,38 @@ switch (filter('op')) {
'timeout' => post('timeout'),
'ssl_no_verify' => post('ssl_no_verify'),
'predefined' => $predefined,
// OAuth2
'provider' => post('provider'),
'client_id' => post('client_id'),
'client_secret' => post('client_secret'),
'oauth2_config' => json_encode(post('config')),
], ['id' => $id_record]);
flash()->info(tr('Informazioni salvate correttamente!'));
// Rimozione informazioni OAuth2 in caso di disabilitazione
if (!$abilita_oauth2) {
$dbo->update('em_accounts', [
'provider' => null,
'client_id' => null,
'client_secret' => null,
'access_token' => null,
'refresh_token' => null,
'oauth2_config' => null,
], ['id' => $id_record]);
$oauth2 = $account->oauth2;
if (!empty($oauth2)) {
$account->oauth2()->dissociate();
$account->save();
$oauth2->delete();
}
}
// Aggiornamento delle informazioni per OAuth2
else {
$oauth2 = $account->oauth2 ?: OAuth2::build();
$oauth2->class = post('provider');
$oauth2->client_id = post('client_id');
$oauth2->client_secret = post('client_secret');
$oauth2->config = post('config');
// Link di redirect dopo la configurazione
$modulo_account_email = Module::pool('Account email');
$oauth2->after_configuration = base_path().'/editor.php?id_module='.$modulo_account_email->id.'&id_record='.$id_record;
$oauth2->save();
// Associazione Account-OAuth2
$account->oauth2()->associate($oauth2);
$account->save();
}
// Validazione indirizzo email mittente
@ -119,7 +137,9 @@ switch (filter('op')) {
break;
case 'oauth2':
$redirect = base_path().'/oauth2.php?id_account='.$account->id;
$oauth2 = $account->oauth2;
$redirect = base_path().'/oauth2.php?id='.$oauth2->id;
redirect($redirect);
break;

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use Modules\Emails\OAuth2;
use Modules\Emails\Account;
include_once __DIR__.'/../../core.php';
@ -101,16 +101,18 @@ echo '
</div>';
// Elenco provider disponibili
$providers = OAuth2::$providers;
$providers = Account::$providers;
$elenco_provider = [];
foreach ($providers as $key => $provider) {
$elenco_provider[] = [
'id' => $key,
'id' => $provider['class'],
'short' => $key,
'text' => $provider['name'],
'help' => $provider['help'],
];
}
$oauth2 = $account->oauth2;
echo '
<!-- OAuth2 -->
<div class="box box-info">
@ -122,32 +124,32 @@ echo '
<div class="row">
<div class="col-md-6">
<span class="label label-warning pull-right hidden" id="guida-configurazione"></span>
{[ "type": "select", "label": "'.tr('Provider account').'", "name": "provider", "value": "$provider$", "values": '.json_encode($elenco_provider).', "disabled": "'.intval(empty($account->provider)).'" ]}
{[ "type": "select", "label": "'.tr('Provider account').'", "name": "provider", "value": '.json_encode($oauth2->class).', "values": '.json_encode($elenco_provider).', "disabled": "'.intval(empty($oauth2)).'" ]}
</div>
<div class="col-md-3">
{[ "type": "checkbox", "label": "'.tr('Abilita OAuth2').'", "name": "abilita_oauth2", "value": "'.intval(!empty($account->provider)).'" ]}
{[ "type": "checkbox", "label": "'.tr('Abilita OAuth2').'", "name": "abilita_oauth2", "value": "'.intval(!empty($oauth2)).'" ]}
</div>
<div id="oauth2-config">
<div class="col-md-3">
<a type="button" class="btn btn-success btn-block '.(empty($account->provider) || empty($account->client_id) || empty($account->client_secret) ? 'disabled' : '').'" style="margin-top: 25px" href="'.base_url().'/editor.php?id_module='.$id_module.'&id_record='.$id_record.'&op=oauth2">
<i class="fa fa-refresh"></i> '.(empty($account->access_token) ? tr('Completa configurazione') : tr('Ripeti configurazione')).'
</a>
</div>
<div class="col-md-6">
{[ "type": "text", "label": "'.tr('Client ID').'", "name": "client_id", "value": "$client_id$", "disabled": "'.intval(empty($account->provider)).'" ]}
</div>
<div class="col-md-6">
{[ "type": "text", "label": "'.tr('Client Secret').'", "name": "client_secret", "value": "$client_secret$", "disabled": "'.intval(empty($account->provider)).'" ]}
</div>
<div id="provider-config"></div>
<div class="col-md-3 oauth2-config">
<a type="button" class="btn btn-success btn-block '.(empty($oauth2->class) || empty($oauth2->client_id) || empty($oauth2->client_secret) ? 'disabled' : '').'" style="margin-top: 25px" href="'.base_url().'/editor.php?id_module='.$id_module.'&id_record='.$id_record.'&op=oauth2">
<i class="fa fa-refresh"></i> '.(empty($oauth2->access_token) ? tr('Completa configurazione') : tr('Ripeti configurazione')).'
</a>
</div>
</div>
<div class="row oauth2-config">
<div class="col-md-6">
{[ "type": "text", "label": "'.tr('Client ID').'", "name": "client_id", "value": "'.$oauth2->client_id.'", "disabled": "'.intval(empty($oauth2)).'" ]}
</div>
<div class="col-md-6">
{[ "type": "text", "label": "'.tr('Client Secret').'", "name": "client_secret", "value": "'.$oauth2->client_secret.'", "disabled": "'.intval(empty($oauth2)).'" ]}
</div>
<div id="provider-config"></div>
</div>
<div class="alert alert-info">
<i class="fa fa-info-circle"></i> '.tr('Durante la procedura di configurazione verrà effettuato il logout dal gestionale').'.
</div>
@ -163,7 +165,7 @@ foreach ($providers as $key => $provider) {
$config = $provider['class']::getConfigInputs();
foreach ($config as $name => $field) {
$field['name'] = 'config['.$name.']';
$field['value'] = $account->oauth2_config[$name];
$field['value'] = $oauth2 ? $oauth2->config[$name] : null;
echo '
<div class="col-md-6">'.input($field).'</div>';
@ -186,7 +188,7 @@ abilita_oauth2.change(function() {
const disable = !abilita_oauth2.get();
provider.setDisabled(disable);
const inputs = $("#oauth2-config .openstamanager-input");
const inputs = $(".oauth2-config .openstamanager-input");
for (i of inputs) {
input(i).setDisabled(disable);
}
@ -204,7 +206,7 @@ provider.change(function() {
// Impostazione dei dati aggiuntivi da configurare
config.html("")
aggiungiContenuto(config, "#provider-" + data.id);
aggiungiContenuto(config, "#provider-" + data.short);
})
$(document).ready(function() {

View File

@ -1,8 +1,23 @@
<?php
/*
* OpenSTAManager: il software gestionale open source per l'assistenza tecnica e la fatturazione
* Copyright (C) DevCode s.r.l.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use Models\Module;
use Modules\Emails\Account;
use Modules\Emails\OAuth2;
use Models\OAuth2;
$skip_permissions = true;
include_once __DIR__.'/core.php';
@ -12,12 +27,12 @@ session_write_close();
$state = $_GET['state'];
$code = $_GET['code'];
// Account individuato via oauth2_state
// Account individuato via state
if (!empty($state)) {
$account = Account::where('oauth2_state', '=', $state)
$account = OAuth2::where('state', '=', $state)
->first();
} else {
$account = Account::find(get('id_account'));
$account = OAuth2::find(get('id'));
// Impostazione access token a null per reimpostare la configurazione
$account->access_token = null;
@ -31,16 +46,12 @@ if (empty($account)) {
return;
}
// Inizializzazione
$oauth2 = new OAuth2($account);
// Redirect all'URL di autorizzazione del servizio esterno
$redirect = $oauth2->configure($code, $state);
$redirect = $account->configure($code, $state);
// Redirect automatico al record
if (empty($redirect)) {
$modulo_account_email = Module::pool('Account email');
$redirect = base_path().'/editor.php?id_module='.$modulo_account_email->id.'&id_record='.$account->id;
$redirect = $account->after_configuration;
}
if (empty($_GET['error'])) {

View File

@ -1,50 +1,63 @@
<?php
/*
* OpenSTAManager: il software gestionale open source per l'assistenza tecnica e la fatturazione
* Copyright (C) DevCode s.r.l.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Modules\Emails;
namespace Models;
use Common\SimpleModelTrait;
use Illuminate\Database\Eloquent\Model;
use InvalidArgumentException;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use Modules\Emails\OAuth2\Google;
use Modules\Emails\OAuth2\Microsoft;
class OAuth2
class OAuth2 extends Model
{
public static $providers = [
'microsoft' => [
'name' => 'Microsoft',
'class' => Microsoft::class,
'help' => 'https://docs.openstamanager.com/faq/configurazione-oauth2#microsoft',
],
'google' => [
'name' => 'Google',
'class' => Google::class,
'help' => 'https://docs.openstamanager.com/faq/configurazione-oauth2#google',
],
];
use SimpleModelTrait;
protected $provider;
protected $account;
public function __construct(Account $account)
{
$this->account = $account;
protected $table = 'zz_oauth2';
// Inizializza il provider per l'autenticazione OAuth2.
$redirect_uri = base_url().'/oauth2.php';
$class = $this->getProviderConfiguration()['class'];
$this->provider = new $class($this->account, $redirect_uri);
}
protected $casts = [
'config' => 'array',
];
public function getProvider()
{
return $this->provider;
}
// Inizializza il provider per l'autenticazione OAuth2.
if (!isset($this->provider)) {
$config = $this->config ?? [];
$config = array_merge($config, [
'clientId' => $this->client_id,
'clientSecret' => $this->client_secret,
'redirectUri' => base_url().'/oauth2.php',
'accessType' => 'offline',
]);
public function getProviderConfiguration()
{
return self::$providers[$this->account->provider];
$class = $this->class;
if (!class_exists($class)) {
throw new InvalidArgumentException('Classe non esistente');
}
$this->provider = new $class($config);
}
return $this->provider;
}
public function needsConfiguration()
@ -78,7 +91,7 @@ class OAuth2
}
$provider = $this->getProvider();
$options = $provider->getOptions();
$options = method_exists($provider, 'getOptions') ? $provider->getOptions() : [];
if (empty($code)) {
// Fetch the authorization URL from the provider; this returns the
// urlAuthorize option and generates and applies any necessary parameters
@ -86,19 +99,19 @@ class OAuth2
$authorization_url = $provider->getAuthorizationUrl($options);
// Get the state generated for you and store it to the session.
$this->account->oauth2_state = $provider->getState();
$this->account->save();
$this->state = $provider->getState();
$this->save();
// Redirect the user to the authorization URL.
return $authorization_url;
} elseif (!empty($this->account->oauth2_state) && $this->account->oauth2_state !== $state) {
$this->account->oauth2_state = null;
$this->account->save();
} elseif (!empty($this->state) && $this->state !== $state) {
$this->state = null;
$this->save();
throw new InvalidArgumentException();
} else {
$this->account->oauth2_state = null;
$this->account->save();
$this->state = null;
$this->save();
// Try to get an access token using the authorization code grant
$access_token = $provider->getAccessToken('authorization_code', [
@ -112,11 +125,14 @@ class OAuth2
return null;
}
/**
* @return string|null
*/
public function getRefreshToken()
{
$this->checkTokens();
return $this->account->refresh_token;
return $this->attributes['refresh_token'];
}
/**
@ -128,34 +144,37 @@ class OAuth2
{
$this->checkTokens();
return unserialize($this->account->access_token);
return unserialize($this->attributes['access_token']);
}
/**
* Imposta l'access token per l'autenticazione OAuth2.
* Imposta Access Token e Refresh Token per l'autenticazione OAuth2.
*
* @param AccessToken|null
*/
public function updateTokens($access_token, $refresh_token)
protected function updateTokens($access_token, $refresh_token)
{
$this->account->access_token = serialize($access_token);
$this->access_token = serialize($access_token);
$previous_refresh_token = $this->account->refresh_token;
$this->account->refresh_token = $refresh_token ?: $previous_refresh_token;
$previous_refresh_token = $this->refresh_token;
$this->refresh_token = $refresh_token ?: $previous_refresh_token;
$this->account->save();
$this->save();
}
/**
* Controlla la validità dei token correnti e ne effettua il refresh se necessario.
*/
protected function checkTokens()
{
$access_token = unserialize($this->account->access_token);
$access_token = unserialize($this->access_token);
if (!empty($access_token) && $access_token->hasExpired()) {
// Tentativo di refresh del token di accesso
$refresh_token = $this->account->refresh_token;
$refresh_token = $this->refresh_token;
if (!empty($refresh_token)) {
$access_token = $this->getProvider()->getAccessToken('refresh_token', [
'refresh_token' => $this->account->refresh_token,
'refresh_token' => $this->refresh_token,
]);
$refresh_token = $access_token->getRefreshToken();

View File

@ -69,16 +69,15 @@ class EmailNotification extends PHPMailer implements NotificationInterface
$this->Username = $account['username'];
// Configurazione OAuth2
if (!empty($account['access_token'])) {
$oauth2 = $account->getGestoreOAuth2();
$oauth2 = $account->oauth2;
if (!empty($oauth2)) {
$this->AuthType = 'XOAUTH2';
$this->setOAuth(
new OAuth([
'provider' => $oauth2->getProvider(),
'refreshToken' => $oauth2->getRefreshToken(),
'clientId' => $account->client_id,
'clientSecret' => $account->client_secret,
'clientId' => $oauth2->client_id,
'clientSecret' => $oauth2->client_secret,
'userName' => $account->username,
])
);

12
update/2_4_26.php Normal file
View File

@ -0,0 +1,12 @@
<?php
use Models\Module;
$modulo_account_email = Module::pool('Account email');
$redirect = base_path().'/editor.php?id_module='.$modulo_account_email->id.'&id_record=';
$database->query("INSERT INTO `zz_oauth2` (`id`, `class`, `client_id`, `client_secret`, `config`, `state`, `access_token`, `refresh_token`, `after_configuration`) SELECT `id`, 'Modules\\\\Emails\\\\OAuth2\\\\Microsoft', `client_id`, `client_secret`, `oauth2_config`, `oauth2_state`, `access_token`, `refresh_token`, CONCAT('".$redirect."', `id`) FROM `em_accounts` WHERE `provider` = 'microsoft' AND `client_id` IS NOT NULL");
$database->query("INSERT INTO `zz_oauth2` (`id`, `class`, `client_id`, `client_secret`, `config`, `state`, `access_token`, `refresh_token`, `after_configuration`) SELECT `id`, 'Modules\\\\Emails\\\\OAuth2\\\\Google', `client_id`, `client_secret`, `oauth2_config`, `oauth2_state`, `access_token`, `refresh_token`, CONCAT('".$redirect."', `id`) FROM `em_accounts` WHERE `provider` = 'google' AND `client_id` IS NOT NULL");
$database->query('UPDATE `em_accounts` SET `id_oauth2` = (SELECT `id` FROM `zz_oauth2` WHERE `zz_oauth2`.`id` = `em_accounts`.`id`)');

View File

@ -107,3 +107,23 @@ INSERT INTO `em_lists` (`id`, `name`, `description`, `query`, `deleted_at`) VALU
-- Fix riferimento documento per righe create da Interventi
UPDATE `co_righe_documenti` SET `original_document_id` = `idintervento`, `original_document_type` = 'Modules\\Interventi\\Intervento' WHERE `idintervento` IS NOT NULL;
-- Generalizzazione della configurazione OAuth2
CREATE TABLE IF NOT EXISTS `zz_oauth2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`class` varchar(255) DEFAULT NULL,
`client_id` text DEFAULT NULL,
`client_secret` text DEFAULT NULL,
`config` text DEFAULT NULL,
`state` text DEFAULT NULL,
`access_token` text DEFAULT NULL,
`refresh_token` text DEFAULT NULL,
`after_configuration` text DEFAULT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
ALTER TABLE `em_accounts` ADD `id_oauth2` INT(11) DEFAULT NULL,
ADD FOREIGN KEY (`id_oauth2`) REFERENCES `zz_oauth2`(`id`);