diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 10c079c99..921aaf435 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,9 +114,9 @@ Our tests are located in the `test/` directory, containing a PHPUnit config file * Use the present tense ("Add feature" not "Added feature") * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") * Limit the first line to 72 characters or less -* Please start the commit message with an applicable emoji code (following the [Gitmoji guide](https://gitmoji.carloscuesta.me/)). - Beware to use the code (for example `:bug:`) and not the character (🐛) as Unicode support in git clients is very poor for now... - Emoji examples : +* Please start the commit message with an applicable emoji code (following the [Gitmoji guide](https://gitmoji.dev/)). + Beware to use the code (for example `:bug:`) and not the character (🐛) as Unicode support in git clients is very poor for now... + Emoji examples : * 🌐 `:globe_with_meridians:` for translations * 🎹 `:art:` when improving the format/structure of the code * âšĄïž `:zap:` when improving performance diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index 60a0edef8..2e71abbc3 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -5,8 +5,6 @@ */ use Combodo\iTop\Application\Helper\WebResourcesHelper; -use Combodo\iTop\Application\UI\Base\Component\Title\Title; -use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory; require_once(APPROOT.'/application/utils.inc.php'); require_once(APPROOT.'/application/template.class.inc.php'); @@ -655,8 +653,7 @@ abstract class MenuNode $this->sMenuId = $sMenuId; $this->iParentIndex = $iParentIndex; $this->aReflectionProperties = array(); - if (utils::StrLen($sEnableClass) > 0) - { + if (utils::IsNotNullOrEmptyString($sEnableClass)) { $this->aReflectionProperties['enable_class'] = $sEnableClass; $this->aReflectionProperties['enable_action'] = $iActionCode; $this->aReflectionProperties['enable_permission'] = $iAllowedResults; diff --git a/application/ui.extkeywidget.class.inc.php b/application/ui.extkeywidget.class.inc.php index 4162ebcfe..5ba6de888 100644 --- a/application/ui.extkeywidget.class.inc.php +++ b/application/ui.extkeywidget.class.inc.php @@ -973,7 +973,7 @@ HTML ); $oPage->add_ready_script(<<iId}').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true}); +$('#ac_create_{$this->iId}').dialog({ width: $(window).width() * 0.6, height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true}); $('#dcr_{$this->iId} form').removeAttr('onsubmit'); $('#dcr_{$this->iId} form').find('button[type="submit"]').on('click', oACWidget_{$this->iId}.DoCreateObject); JS diff --git a/application/utils.inc.php b/application/utils.inc.php index 5a44ed348..99abff2e9 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -1942,7 +1942,7 @@ class utils */ public static function CompileCSSFromSASS($sSassContent, $aImportPaths = array(), $aVariables = array()) { - $oSass = new Compiler();//['checkImportResolutions'=>true]); + $oSass = new Compiler(); $oSass->setOutputStyle(OutputStyle::COMPRESSED); // Setting our variables $aCssVariable = []; @@ -2845,6 +2845,36 @@ HTML; return strlen($sString ?? ''); } + /** + * Helper around the native strlen() PHP method to test a string for null or empty value + * + * @link https://www.php.net/releases/8.1/en.php#deprecations_and_bc_breaks "Passing null to non-nullable internal function parameters is deprecated" + * + * @param string|null $sString + * + * @return bool if string null or empty + * @since 3.0.2 N°5302 + */ + public static function IsNullOrEmptyString(?string $sString): bool + { + return $sString == null || strlen($sString) == 0; + } + + /** + * Helper around the native strlen() PHP method to test a string not null or empty value + * + * @link https://www.php.net/releases/8.1/en.php#deprecations_and_bc_breaks "Passing null to non-nullable internal function parameters is deprecated" + * + * @param string|null $sString + * + * @return bool if string is not null and not empty + * @since 3.0.2 N°5302 + */ + public static function IsNotNullOrEmptyString(?string $sString): bool + { + return !static::IsNullOrEmptyString($sString); + } + //---------------------------------------------- // Environment helpers //---------------------------------------------- diff --git a/composer.json b/composer.json index 5b668d290..4bac9ae06 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,8 @@ { + "name": "combodo/itop", + "description": "IT Operations Portal", "type": "project", - "license": "AGPLv3", + "license": "AGPL-3.0-or-later", "require": { "php": ">=7.2.5 <8.0.0", "ext-ctype": "*", diff --git a/core/asynctask.class.inc.php b/core/asynctask.class.inc.php index 69eb4e1a4..05bc5bd74 100644 --- a/core/asynctask.class.inc.php +++ b/core/asynctask.class.inc.php @@ -293,7 +293,7 @@ abstract class AsyncTask extends DBObject $this->Set('remaining_retries', $this->GetMaxRetries($iErrorCode)); } - $this->Set('last_error', $sErrorMessage); + $this->SetTrim('last_error', $sErrorMessage); $this->Set('last_error_code', $iErrorCode); // Note: can be ZERO !!! $this->Set('last_attempt', time()); diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 536d0946c..aa83b0dd2 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -530,7 +530,7 @@ class Config ], 'email_transport' => [ 'type' => 'string', - 'description' => 'Mean to send emails: PHPMail (uses the function mail()) or SMTP (implements the client protocol)', + 'description' => 'Mean to send emails: PHPMail (uses the function mail()), SMTP (implements the client protocol) or SMTP_OAuth (connect to the server using OAuth 2.0)', 'default' => "PHPMail", 'value' => "PHPMail", 'source_of_value' => '', diff --git a/core/designdocument.class.inc.php b/core/designdocument.class.inc.php index a9448859d..24a50a78f 100644 --- a/core/designdocument.class.inc.php +++ b/core/designdocument.class.inc.php @@ -26,8 +26,10 @@ namespace Combodo\iTop; -use \DOMDocument; -use \DOMFormatException; +use DOMDocument; +use DOMFormatException; +use IssueLog; +use LogAPI; /** * Class \Combodo\iTop\DesignDocument @@ -64,9 +66,13 @@ class DesignDocument extends DOMDocument * @param $filename * @param int $options */ - public function load($filename, $options = 0) + public function load($filename, $options = null) { - parent::load($filename, LIBXML_NOBLANKS); + libxml_clear_errors(); + if (parent::load($filename, LIBXML_NOBLANKS) === false) { + $aErrors = libxml_get_errors(); + IssueLog::Error("Error loading $filename", LogAPI::CHANNEL_DEFAULT, $aErrors); + } } /** @@ -77,10 +83,10 @@ class DesignDocument extends DOMDocument * * @return int */ - public function save($filename, $options = 0) + public function save($filename, $options = null) { $this->documentElement->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance"); - return parent::save($filename, LIBXML_NOBLANKS); + return parent::save($filename); } /** diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 4d7d2f701..676a9e7e9 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -461,7 +461,7 @@ abstract class MetaModel $oStyle = self::$m_aClassParams[$sClass]['style']; $sIcon = $oStyle->GetIconAsAbsUrl(); } - if (utils::StrLen($sIcon) == 0) { + if (utils::IsNullOrEmptyString($sIcon)) { $sParentClass = self::GetParentPersistentClass($sClass); if (strlen($sParentClass) > 0) { return self::GetClassIcon($sParentClass, $bImgTag, $sMoreStyles); @@ -494,7 +494,7 @@ abstract class MetaModel $oStyle = new ormStyle("ibo-class-style--$sClass", "ibo-class-style-alt--$sClass"); } - if ((utils::StrLen($oStyle->GetMainColor()) > 0) && (utils::StrLen($oStyle->GetComplementaryColor()) > 0) && (utils::StrLen($oStyle->GetIconAsRelPath()) > 0)) { + if (utils::IsNotNullOrEmptyString($oStyle->GetMainColor()) && utils::IsNotNullOrEmptyString($oStyle->GetComplementaryColor()) && utils::IsNotNullOrEmptyString($oStyle->GetIconAsRelPath())) { // all the parameters are set, no need to search in the parent classes return $oStyle; } @@ -504,18 +504,18 @@ abstract class MetaModel while (strlen($sParentClass) > 0) { $oParentStyle = self::GetClassStyle($sParentClass); if (!is_null($oParentStyle)) { - if (utils::StrLen($oStyle->GetMainColor()) == 0) { + if (utils::IsNullOrEmptyString($oStyle->GetMainColor())) { $oStyle->SetMainColor($oParentStyle->GetMainColor()); $oStyle->SetStyleClass($oParentStyle->GetStyleClass()); } - if (utils::StrLen($oStyle->GetComplementaryColor()) == 0) { + if (utils::IsNullOrEmptyString($oStyle->GetComplementaryColor())) { $oStyle->SetComplementaryColor($oParentStyle->GetComplementaryColor()); $oStyle->SetAltStyleClass($oParentStyle->GetAltStyleClass()); } - if (utils::StrLen($oStyle->GetIconAsRelPath()) == 0) { + if (utils::IsNullOrEmptyString($oStyle->GetIconAsRelPath())) { $oStyle->SetIcon($oParentStyle->GetIconAsRelPath()); } - if ((utils::StrLen($oStyle->GetMainColor()) > 0) && (utils::StrLen($oStyle->GetComplementaryColor()) > 0) && (utils::StrLen($oStyle->GetIconAsRelPath()) > 0)) { + if (utils::IsNotNullOrEmptyString($oStyle->GetMainColor()) && utils::IsNotNullOrEmptyString($oStyle->GetComplementaryColor()) && utils::IsNotNullOrEmptyString($oStyle->GetIconAsRelPath())) { // all the parameters are set, no need to search in the parent classes return $oStyle; } @@ -523,7 +523,7 @@ abstract class MetaModel $sParentClass = self::GetParentPersistentClass($sParentClass); } - if ((utils::StrLen($oStyle->GetMainColor()) == 0) && (utils::StrLen($oStyle->GetComplementaryColor()) == 0) && (utils::StrLen($oStyle->GetIconAsRelPath()) == 0)) { + if (utils::IsNullOrEmptyString($oStyle->GetMainColor()) && utils::IsNullOrEmptyString($oStyle->GetComplementaryColor()) && utils::IsNullOrEmptyString($oStyle->GetIconAsRelPath())) { return null; } diff --git a/core/ormStyle.class.inc.php b/core/ormStyle.class.inc.php index 8f3b02abf..dabbd59bd 100644 --- a/core/ormStyle.class.inc.php +++ b/core/ormStyle.class.inc.php @@ -50,7 +50,7 @@ class ormStyle */ public function HasMainColor(): bool { - return utils::StrLen($this->sMainColor) > 0; + return utils::IsNotNullOrEmptyString($this->sMainColor); } /** @@ -68,7 +68,7 @@ class ormStyle */ public function SetMainColor(?string $sMainColor) { - $this->sMainColor = (utils::StrLen($sMainColor) === 0) ? null : $sMainColor; + $this->sMainColor = utils::IsNullOrEmptyString($sMainColor) ? null : $sMainColor; return $this; } @@ -78,7 +78,7 @@ class ormStyle */ public function HasComplementaryColor(): bool { - return utils::StrLen($this->sComplementaryColor) > 0; + return utils::IsNotNullOrEmptyString($this->sComplementaryColor); } /** @@ -96,7 +96,7 @@ class ormStyle */ public function SetComplementaryColor(?string $sComplementaryColor) { - $this->sComplementaryColor = (utils::StrLen($sComplementaryColor) === 0) ? null : $sComplementaryColor; + $this->sComplementaryColor = utils::IsNullOrEmptyString($sComplementaryColor) ? null : $sComplementaryColor; return $this; } @@ -116,7 +116,7 @@ class ormStyle */ public function HasStyleClass(): bool { - return utils::StrLen($this->sStyleClass) > 0; + return utils::IsNotNullOrEmptyString($this->sStyleClass); } /** @@ -134,7 +134,7 @@ class ormStyle */ public function SetStyleClass(?string $sStyleClass) { - $this->sStyleClass = (utils::StrLen($sStyleClass) === 0) ? null : $sStyleClass; + $this->sStyleClass = utils::IsNullOrEmptyString($sStyleClass) ? null : $sStyleClass; return $this; } @@ -144,7 +144,7 @@ class ormStyle */ public function HasAltStyleClass(): bool { - return utils::StrLen($this->sAltStyleClass) > 0; + return utils::IsNotNullOrEmptyString($this->sAltStyleClass); } /** @@ -162,7 +162,7 @@ class ormStyle */ public function SetAltStyleClass(?string $sAltStyleClass) { - $this->sAltStyleClass = (utils::StrLen($sAltStyleClass) === 0) ? null : $sAltStyleClass; + $this->sAltStyleClass = utils::IsNullOrEmptyString($sAltStyleClass) ? null : $sAltStyleClass; return $this; } @@ -172,7 +172,7 @@ class ormStyle */ public function HasDecorationClasses(): bool { - return utils::StrLen($this->sDecorationClasses) > 0; + return utils::IsNotNullOrEmptyString($this->sDecorationClasses); } /** @@ -190,7 +190,7 @@ class ormStyle */ public function SetDecorationClasses(?string $sDecorationClasses) { - $this->sDecorationClasses = (utils::StrLen($sDecorationClasses) === 0) ? null : $sDecorationClasses; + $this->sDecorationClasses = utils::IsNullOrEmptyString($sDecorationClasses) ? null : $sDecorationClasses; return $this; } @@ -200,7 +200,7 @@ class ormStyle */ public function HasIcon(): bool { - return utils::StrLen($this->sIcon) > 0; + return utils::IsNotNullOrEmptyString($this->sIcon); } /** @@ -210,7 +210,7 @@ class ormStyle */ public function SetIcon(?string $sIcon) { - $this->sIcon = (utils::StrLen($sIcon) === 0) ? null : $sIcon; + $this->sIcon = utils::IsNullOrEmptyString($sIcon) ? null : $sIcon; return $this; } diff --git a/css/backoffice/components/input/_input-one-way-password.scss b/css/backoffice/components/input/_input-one-way-password.scss index 57a5feecb..f2a3404d0 100644 --- a/css/backoffice/components/input/_input-one-way-password.scss +++ b/css/backoffice/components/input/_input-one-way-password.scss @@ -1,4 +1,4 @@ -/*! +/* * @copyright Copyright (C) 2010-2021 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ diff --git a/css/backoffice/vendors/_selectize.scss b/css/backoffice/vendors/_selectize.scss index 52851505a..ea917cd4d 100644 --- a/css/backoffice/vendors/_selectize.scss +++ b/css/backoffice/vendors/_selectize.scss @@ -1,4 +1,4 @@ -/*! +/* * @copyright Copyright (C) 2010-2021 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ diff --git a/datamodels/2.x/itop-oauth-client/README.md b/datamodels/2.x/itop-oauth-client/README.md index a520ac75c..9a45ad911 100644 --- a/datamodels/2.x/itop-oauth-client/README.md +++ b/datamodels/2.x/itop-oauth-client/README.md @@ -1,2 +1,5 @@ # Extension OAuth 2.0 client +## GMail + +If the account is in test, after 7 days the tokens are no longer valid, you have to renew the tokens manually. diff --git a/datamodels/2.x/itop-oauth-client/ajax.php b/datamodels/2.x/itop-oauth-client/ajax.php index 1b81b0829..4fbbbf477 100644 --- a/datamodels/2.x/itop-oauth-client/ajax.php +++ b/datamodels/2.x/itop-oauth-client/ajax.php @@ -17,8 +17,7 @@ if (version_compare(ITOP_DESIGN_LATEST_VERSION , '3.0') >= 0) { } $oUpdateController = new AjaxOauthClientController($sTemplates, 'itop-oauth-client'); -$oUpdateController->AllowOnlyAdmin(); -$oUpdateController->SetDefaultOperation('CreateMailbox'); +$oUpdateController->SetMenuId('OAuthClient'); $oUpdateController->HandleOperation(); diff --git a/datamodels/2.x/itop-oauth-client/datamodel.itop-oauth-client.xml b/datamodels/2.x/itop-oauth-client/datamodel.itop-oauth-client.xml index 70c6eeeea..616899b07 100644 --- a/datamodels/2.x/itop-oauth-client/datamodel.itop-oauth-client.xml +++ b/datamodels/2.x/itop-oauth-client/datamodel.itop-oauth-client.xml @@ -5,7 +5,7 @@ cmdbAbstractObject - cloud,searchable + grant_by_profile,application true autoincrement priv_oauth_client @@ -17,14 +17,15 @@ - - + @@ -119,22 +120,35 @@ public Overload-DBObject Get('scope'); - if ($this->Get('status') == 'inactive') { - $oPage->p(''.Dict::S('itop-oauth-client:Message:MissingToken').''); - } elseif (($sScope == 'SMTP' || $sScope == 'EMail') && $oConfig->Get('email_transport_smtp.username') == $this->Get('name')) { - $sLabel = Dict::S('itop-oauth-client:UsedForSMTP'); - $sTestLabel = Dict::S('itop-oauth-client:TestSMTP'); - $sTestURL = utils::GetAbsoluteUrlAppRoot().'setup/email.test.php'; - $oPage->p("$sLabel $sTestLabel"); - } - } - } + public function DisplayBareHeader(WebPage $oPage, $bEditMode = false) + { + $aHeaderBlocks = parent::DisplayBareHeader($oPage, $bEditMode); + $aTags = []; + if (!$bEditMode) { + $oConfig = utils::GetConfig(); + if ($this->Get('status') == 'inactive') { + $sLabel = Dict::S('itop-oauth-client:Message:MissingToken'); + $sTitle = ''; + $aTags['oauth-message'] = ['title' => $sTitle, 'css_classes' => 'ibo-object-details--tag--oauth-message', 'decoration_classes' => 'fas fa-exclamation-triangle', 'label' => $sLabel]; + } elseif ($this->Get('used_for_smtp') == 'yes' && $oConfig->Get('email_transport_smtp.username') == $this->Get('name')) { + $sLabel = Dict::S('itop-oauth-client:UsedForSMTP'); + $sTestLabel = Dict::S('itop-oauth-client:TestSMTP'); + $sTestURL = utils::GetAbsoluteUrlAppRoot().'setup/email.test.php'; + $sLabel = Dict::S('itop-oauth-client:UsedForSMTP')." $sTestLabel"; + $sTitle = ''; + $aTags['oauth-message'] = ['title' => $sTitle, 'css_classes' => 'ibo-object-details--tag--oauth-message', 'decoration_classes' => 'far fa-envelope', 'label' => $sLabel]; + } + } + foreach ($aTags as $sIconId => $aIconData) { + $sTagTooltipContent = utils::EscapeHtml($aIconData['title']); + $aHeaderBlocks['subtitle'][static::HEADER_BLOCKS_SUBTITLE_TAG_PREFIX.$sIconId] = new Combodo\iTop\Application\UI\Base\Component\Html\Html(<<{$aIconData['label']} + HTML + ); + } + + return $aHeaderBlocks; + } ]]> @@ -142,14 +156,14 @@ public Overload-DBObject @@ -157,68 +171,68 @@ public Overload-DBObject false public Get('provider').'.com'; - } + public function GetDefaultMailServer() + { + return 'imap.'.$this->Get('provider').'.com'; + } ]]> false public false public Get('status') == 'active') { - return new \League\OAuth2\Client\Token\AccessToken([ - 'access_token' => $this->Get('token'), - 'expires_in' => date_format(new DateTime($this->Get('token_expiration')), 'U') - time(), - 'refresh_token' => $this->Get('refresh_token'), - 'token_type' => 'Bearer', - ]); - } - return null; - } + public function GetAccessToken() + { + if ($this->Get('status') == 'active') { + return new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => $this->Get('token'), + 'expires_in' => date_format(new DateTime($this->Get('token_expiration')), 'U') - time(), + 'refresh_token' => $this->Get('refresh_token'), + 'token_type' => 'Bearer', + ]); + } + return null; + } ]]> false public Set('token', $oAccessToken->getToken()); - $this->Set('token_expiration', date(AttributeDateTime::GetSQLFormat(), $oAccessToken->getExpires())); - if (!empty($oAccessToken->getRefreshToken())) { - $this->Set('refresh_token', $oAccessToken->getRefreshToken()); - } - $this->Set('status', 'active'); - $this->DBUpdate(); - } + public function SetAccessToken(\League\OAuth2\Client\Token\AccessTokenInterface $oAccessToken) + { + $this->Set('token', $oAccessToken->getToken()); + $this->Set('token_expiration', date(AttributeDateTime::GetSQLFormat(), $oAccessToken->getExpires())); + if (!empty($oAccessToken->getRefreshToken())) { + $this->Set('refresh_token', $oAccessToken->getRefreshToken()); + } + $this->Set('status', 'active'); + $this->DBUpdate(); + } ]]> @@ -280,6 +294,632 @@ + + OAuthClient + + grant_by_profile,application + false + autoincrement + priv_oauth_client_azure + id + + + + + + + + + + + + + + + + + + + + + true + + + + + + + true + + + SMTP + + + IMAP + + + scope + SMTP,IMAP + true + + + advanced_scope + + true + + + true + + + simple + + + advanced + + + used_scope + simple + false + + + + + + + true + + + yes + + + no + + + used_for_smtp + no + true + + + +
+ + + 10 + + + 10 + + + 10 + + + 20 + + + 30 + + + 40 + + + 50 + + + 60 + + + 70 + + + 80 + + + + + + + 20 + + + 10 + + + 10 + + + 20 + + + 30 + + + 40 + + + + + + +
+ + + + 10 + + + 10 + + + + + + + 10 + + + 10 + + + 10 + + + +
+ + + false + public + Overload-DBObject + Set('provider', 'Azure'); + $this->Set('redirect_url', Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory::GetRedirectUri()); + $this->Set('scope', 'SMTP, IMAP'); + + parent::PrefillCreationForm($aContextParam); + } + ]]> + + + false + public + Overload-DBObject + ListChanges(); + if (array_key_exists('name', $aChanges) || array_key_exists('used_for_smtp', $aChanges)) + { + $sNewName = $this->Get('name'); + $sNewUseForSMTP = $this->Get('used_for_smtp'); + if ($sNewUseForSMTP == 'yes') { + $oSearch = DBObjectSearch::FromOQL_AllData("SELECT OAuthClientGoogle WHERE name = :newname AND used_for_smtp = :newuseforsmtp AND id != :id UNION SELECT OAuthClientAzure WHERE name = :newname AND used_for_smtp = :newuseforsmtp AND id != :id"); + $oSet = new DBObjectSet($oSearch, array(), ['id' => $this->GetKey(), 'newname' => $sNewName, 'newuseforsmtp' => $sNewUseForSMTP]); + if ($oSet->Count() > 0) + { + $this->m_aCheckIssues[] = Dict::Format('OAuthClient:Name/UseForSMTPMustBeUnique', $sNewName, $sNewUseForSMTP); + } + } + } + } ]]> + + + false + public + Overload-DBObject + Get('provider'))) { + $this->Set('provider', 'Azure'); + } + if (empty($this->Get('redirect_url'))) { + $this->Set('redirect_url', Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory::GetRedirectUri()); + } + if (empty($this->Get('advanced_scope'))) { + $this->Set('used_scope', 'simple'); + if (count($this->Get('scope')->GetValues()) == 0) { + $this->Set('scope', 'SMTP, IMAP'); + } + } else { + $this->Set('used_scope', 'advanced'); + $this->Set('scope', ''); + } + } + ]]> + + + false + public + Overload-DBObject + + + + false + public + Overload-DBObject + + + + false + public + + + + false + public + Get('advanced_scope'))) { + return $this->Get('advanced_scope'); + } + $aScopes = $this->Get('scope')->GetValues(); + $aRawScopes = ['offline_access']; + foreach ($aScopes as $sScope) { + switch ($sScope) { + case 'SMTP': + $aRawScopes[] = 'https://outlook.office.com/SMTP.Send'; + break; + + case 'IMAP': + $aRawScopes[] = 'https://outlook.office.com/IMAP.AccessAsUser.All'; + break; + } + } + return implode(' ', $aRawScopes); + } + ]]> + + +
+ + OAuthClient + + grant_by_profile,application + false + autoincrement + priv_oauth_client_google + id + + + + + + + + + + + + + + + + + + + + + true + + + + + + + true + + + SMTP + + + IMAP + + + scope + SMTP,IMAP + true + + + advanced_scope + + true + + + true + + + simple + + + advanced + + + used_scope + simple + false + + + + + + + true + + + yes + + + no + + + used_for_smtp + no + true + + + +
+ + + 10 + + + 10 + + + 10 + + + 20 + + + 30 + + + 40 + + + 50 + + + 60 + + + 70 + + + 80 + + + + + + + 20 + + + 10 + + + 10 + + + 20 + + + 30 + + + 40 + + + + + + +
+ + + + 10 + + + 10 + + + + + + + 10 + + + 10 + + + 10 + + + +
+ + + false + public + Overload-DBObject + Set('provider', 'Google'); + $this->Set('redirect_url', Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory::GetRedirectUri()); + $this->Set('scope', 'SMTP, IMAP'); + + parent::PrefillCreationForm($aContextParam); + } + ]]> + + + false + public + Overload-DBObject + ListChanges(); + if (array_key_exists('name', $aChanges) || array_key_exists('used_for_smtp', $aChanges)) + { + $sNewName = $this->Get('name'); + $sNewUseForSMTP = $this->Get('used_for_smtp'); + if ($sNewUseForSMTP == 'yes') { + $oSearch = DBObjectSearch::FromOQL_AllData("SELECT OAuthClientGoogle WHERE name = :newname AND used_for_smtp = :newuseforsmtp AND id != :id UNION SELECT OAuthClientAzure WHERE name = :newname AND used_for_smtp = :newuseforsmtp AND id != :id"); + $oSet = new DBObjectSet($oSearch, array(), ['id' => $this->GetKey(), 'newname' => $sNewName, 'newuseforsmtp' => $sNewUseForSMTP]); + if ($oSet->Count() > 0) + { + $this->m_aCheckIssues[] = Dict::Format('OAuthClient:Name/UseForSMTPMustBeUnique', $sNewName, $sNewUseForSMTP); + } + } + } + } ]]> + + + false + public + Overload-DBObject + Get('provider'))) { + $this->Set('provider', 'Google'); + } + if (empty($this->Get('redirect_url'))) { + $this->Set('redirect_url', Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory::GetRedirectUri()); + } + if (empty($this->Get('advanced_scope'))) { + $this->Set('used_scope', 'simple'); + if (count($this->Get('scope')->GetValues()) == 0) { + $this->Set('scope', 'SMTP, IMAP'); + } + } else { + $this->Set('used_scope', 'advanced'); + $this->Set('scope', ''); + } + } + ]]> + + + false + public + Overload-DBObject + + + + false + public + Overload-DBObject + + + + false + public + + + + false + public + Get('advanced_scope'))) { + return $this->Get('advanced_scope'); + } + $aScopes = $this->Get('scope')->GetValues(); + $aRawScopes = []; + foreach ($aScopes as $sScope) { + switch ($sScope) { + case 'SMTP': + $aRawScopes['https://mail.google.com/'] = 'https://mail.google.com/'; + break; + + case 'IMAP': + $aRawScopes['https://mail.google.com/'] = 'https://mail.google.com/'; + break; + } + } + return implode(' ', $aRawScopes); + } + ]]> + + +
@@ -289,13 +929,16 @@ 1 0 OAuthClient - UR_ACTION_READ + UR_ACTION_MODIFY + + + + + - - diff --git a/datamodels/2.x/itop-oauth-client/en.dict.itop-oauth-client.php b/datamodels/2.x/itop-oauth-client/en.dict.itop-oauth-client.php index 1a91fb173..a8a0f4d9d 100644 --- a/datamodels/2.x/itop-oauth-client/en.dict.itop-oauth-client.php +++ b/datamodels/2.x/itop-oauth-client/en.dict.itop-oauth-client.php @@ -21,6 +21,11 @@ Dict::Add('EN US', 'English', 'English', [ 'itop-oauth-client:Message:MissingToken' => 'Generate access token before using this OAuth client', 'itop-oauth-client:Message:TokenCreated' => 'Access token created', 'itop-oauth-client:Message:TokenRecreated' => 'Access token regenerated', + + 'OAuthClient:Name/UseForSMTPMustBeUnique' => 'The combination Login (%1$s) and Use for SMTP (%2$s) has already be used for OAuth Client', + + 'OAuthClient:baseinfo' => 'Base Information', + 'OAuthClient:scope' => 'Scope', ]); // @@ -32,19 +37,17 @@ Dict::Add('EN US', 'English', 'English', [ 'Class:OAuthClient/Attribute:provider' => 'Provider', 'Class:OAuthClient/Attribute:provider+' => '', 'Class:OAuthClient/Attribute:name' => 'Login', - 'Class:OAuthClient/Attribute:name+' => '', - 'Class:OAuthClient/Attribute:scope' => 'Scope', - 'Class:OAuthClient/Attribute:scope+' => '', + 'Class:OAuthClient/Attribute:name+' => 'In general, this is your email address', 'Class:OAuthClient/Attribute:status' => 'Status', - 'Class:OAuthClient/Attribute:status+' => '', + 'Class:OAuthClient/Attribute:status+' => 'After creation, use the action “Generate access token” to be able to use this OAuth client', 'Class:OAuthClient/Attribute:status/Value:active' => 'Access token generated', 'Class:OAuthClient/Attribute:status/Value:inactive' => 'No Access token', 'Class:OAuthClient/Attribute:description' => 'Description', 'Class:OAuthClient/Attribute:description+' => '', 'Class:OAuthClient/Attribute:client_id' => 'Client id', - 'Class:OAuthClient/Attribute:client_id+' => '', + 'Class:OAuthClient/Attribute:client_id+' => 'A long string of characters provided by your OAuth2 provider', 'Class:OAuthClient/Attribute:client_secret' => 'Client secret', - 'Class:OAuthClient/Attribute:client_secret+' => '', + 'Class:OAuthClient/Attribute:client_secret+' => 'Another long string of characters provided by your OAuth2 provider', 'Class:OAuthClient/Attribute:refresh_token' => 'Refresh token', 'Class:OAuthClient/Attribute:refresh_token+' => '', 'Class:OAuthClient/Attribute:refresh_token_expiration' => 'Refresh token expiration', @@ -54,7 +57,7 @@ Dict::Add('EN US', 'English', 'English', [ 'Class:OAuthClient/Attribute:token_expiration' => 'Access token expiration', 'Class:OAuthClient/Attribute:token_expiration+' => '', 'Class:OAuthClient/Attribute:redirect_url' => 'Redirect url', - 'Class:OAuthClient/Attribute:redirect_url+' => '', + 'Class:OAuthClient/Attribute:redirect_url+' => 'This url must be copied in the OAuth2 configuration of the provider', 'Class:OAuthClient/Attribute:mailbox_list' => 'Mailbox list', 'Class:OAuthClient/Attribute:mailbox_list+' => '', ]); @@ -62,17 +65,53 @@ Dict::Add('EN US', 'English', 'English', [ // // Class: OAuthClientAzure // -Dict::Add('EN US', 'English', 'English', [ + +Dict::Add('EN US', 'English', 'English', array( 'Class:OAuthClientAzure' => 'OAuth client for Microsoft Azure', 'Class:OAuthClientAzure/Name' => '%1$s (%2$s)', - -]); + 'Class:OAuthClientAzure/Attribute:scope' => 'Scope', + 'Class:OAuthClientAzure/Attribute:scope+' => 'Usually default selection is appropriate', + 'Class:OAuthClientAzure/Attribute:scope/Value:SMTP' => 'SMTP', + 'Class:OAuthClientAzure/Attribute:scope/Value:SMTP+' => '', + 'Class:OAuthClientAzure/Attribute:scope/Value:IMAP' => 'IMAP', + 'Class:OAuthClientAzure/Attribute:scope/Value:IMAP+' => '', + 'Class:OAuthClientAzure/Attribute:advanced_scope' => 'Advanced scope', + 'Class:OAuthClientAzure/Attribute:advanced_scope+' => 'As soon as you enter something here it takes precedence on the “Scope” selection which is then ignored', + 'Class:OAuthClientAzure/Attribute:used_scope' => 'Used scope', + 'Class:OAuthClientAzure/Attribute:used_scope+' => '', + 'Class:OAuthClientAzure/Attribute:used_scope/Value:simple' => 'Simple', + 'Class:OAuthClientAzure/Attribute:used_scope/Value:simple+' => '', + 'Class:OAuthClientAzure/Attribute:used_scope/Value:advanced' => 'Advanced', + 'Class:OAuthClientAzure/Attribute:used_scope/Value:advanced+' => '', + 'Class:OAuthClientAzure/Attribute:used_for_smtp' => 'Used for SMTP', + 'Class:OAuthClientAzure/Attribute:used_for_smtp+' => 'At least one OAuth client must have this flag to “Yes”, if you want iTop to use it for sending mails', + 'Class:OAuthClientAzure/Attribute:used_for_smtp/Value:yes' => 'Yes', + 'Class:OAuthClientAzure/Attribute:used_for_smtp/Value:no' => 'No', +)); // // Class: OAuthClientGoogle // -Dict::Add('EN US', 'English', 'English', [ + +Dict::Add('EN US', 'English', 'English', array( 'Class:OAuthClientGoogle' => 'OAuth client for Google', 'Class:OAuthClientGoogle/Name' => '%1$s (%2$s)', -]); - + 'Class:OAuthClientGoogle/Attribute:scope' => 'Scope', + 'Class:OAuthClientGoogle/Attribute:scope+' => 'Usually default selection is appropriate', + 'Class:OAuthClientGoogle/Attribute:scope/Value:SMTP' => 'SMTP', + 'Class:OAuthClientGoogle/Attribute:scope/Value:SMTP+' => '', + 'Class:OAuthClientGoogle/Attribute:scope/Value:IMAP' => 'IMAP', + 'Class:OAuthClientGoogle/Attribute:scope/Value:IMAP+' => '', + 'Class:OAuthClientGoogle/Attribute:advanced_scope' => 'Advanced scope', + 'Class:OAuthClientGoogle/Attribute:advanced_scope+' => 'As soon as you enter something here it takes precedence on the “Scope” selection which is then ignored', + 'Class:OAuthClientGoogle/Attribute:used_scope' => 'Used scope', + 'Class:OAuthClientGoogle/Attribute:used_scope+' => '', + 'Class:OAuthClientGoogle/Attribute:used_scope/Value:simple' => 'Simple', + 'Class:OAuthClientGoogle/Attribute:used_scope/Value:simple+' => '', + 'Class:OAuthClientGoogle/Attribute:used_scope/Value:advanced' => 'Advanced', + 'Class:OAuthClientGoogle/Attribute:used_scope/Value:advanced+' => '', + 'Class:OAuthClientGoogle/Attribute:used_for_smtp' => 'Used for SMTP', + 'Class:OAuthClientGoogle/Attribute:used_for_smtp+' => 'At least one OAuth client must have this flag to “Yes”, if you want iTop to use it for sending mails', + 'Class:OAuthClientGoogle/Attribute:used_for_smtp/Value:yes' => 'Yes', + 'Class:OAuthClientGoogle/Attribute:used_for_smtp/Value:no' => 'No', +)); diff --git a/datamodels/2.x/itop-oauth-client/fr.dict.itop-oauth-client.php b/datamodels/2.x/itop-oauth-client/fr.dict.itop-oauth-client.php index e8461b535..3d5f84836 100644 --- a/datamodels/2.x/itop-oauth-client/fr.dict.itop-oauth-client.php +++ b/datamodels/2.x/itop-oauth-client/fr.dict.itop-oauth-client.php @@ -5,6 +5,7 @@ * @copyright Copyright (C) 2013 XXXXX * @license http://opensource.org/licenses/AGPL-3.0 */ + Dict::Add('FR FR', 'French', 'Français', [ 'Menu:CreateMailbox' => 'CrĂ©er une boite mail...', 'Menu:OAuthClient' => 'Client OAuth', @@ -20,6 +21,11 @@ Dict::Add('FR FR', 'French', 'Français', [ 'itop-oauth-client:Message:MissingToken' => 'GĂ©nĂ©rez le jeton d\'accĂšs avant d\'utiliser ce client OAuth', 'itop-oauth-client:Message:TokenCreated' => 'Le jeton d\'accĂšs Ă  Ă©tĂ© créé', 'itop-oauth-client:Message:TokenRecreated' => 'Le jeton d\'accĂšs Ă  Ă©tĂ© renouvelĂ©', + + 'OAuthClient:Name/UseForSMTPMustBeUnique' => 'La combinaison Login (%1$s) and UtilisĂ© pour SMTP (%2$s) a dĂ©jĂ  Ă©tĂ© utilisĂ©e pour OAuth Client', + + 'OAuthClient:baseinfo' => 'Information', + 'OAuthClient:scope' => 'Scope', ]); // @@ -31,15 +37,17 @@ Dict::Add('FR FR', 'French', 'Français', [ 'Class:OAuthClient/Attribute:provider' => 'Fournisseur', 'Class:OAuthClient/Attribute:provider+' => '', 'Class:OAuthClient/Attribute:name' => 'Login', - 'Class:OAuthClient/Attribute:name+' => '', - 'Class:OAuthClient/Attribute:scope' => 'Niveaux d\'accĂšs', - 'Class:OAuthClient/Attribute:scope+' => '', + 'Class:OAuthClient/Attribute:name+' => 'L\'adresse email Ă  utiliser chez ce fournisseur', + 'Class:OAuthClient/Attribute:status' => 'Statut', + 'Class:OAuthClient/Attribute:status+' => 'AprĂšs la crĂ©ation, effectuer l\'action \'CrĂ©er un jeton d\'accĂšs...\' pour activer ce client OAuth', + 'Class:OAuthClient/Attribute:status/Value:active' => 'Jeton d\'accĂšs créé', + 'Class:OAuthClient/Attribute:status/Value:inactive' => 'Pas de jeton d\'accĂšs', 'Class:OAuthClient/Attribute:description' => 'Description', 'Class:OAuthClient/Attribute:description+' => '', 'Class:OAuthClient/Attribute:client_id' => 'ID Client', - 'Class:OAuthClient/Attribute:client_id+' => '', + 'Class:OAuthClient/Attribute:client_id+' => 'Recopier la chaine fournie par votre fournisseur OAuth2', 'Class:OAuthClient/Attribute:client_secret' => 'Code secret du client', - 'Class:OAuthClient/Attribute:client_secret+' => '', + 'Class:OAuthClient/Attribute:client_secret+' => 'Recopier l\'information fournie par votre fournisseur OAuth2', 'Class:OAuthClient/Attribute:refresh_token' => 'Jeton de renouvellement', 'Class:OAuthClient/Attribute:refresh_token+' => '', 'Class:OAuthClient/Attribute:refresh_token_expiration' => 'Date d\'expiration du jeton de renouvellement', @@ -49,7 +57,7 @@ Dict::Add('FR FR', 'French', 'Français', [ 'Class:OAuthClient/Attribute:token_expiration' => 'Date d\'expiration du jeton d\'accĂšs', 'Class:OAuthClient/Attribute:token_expiration+' => '', 'Class:OAuthClient/Attribute:redirect_url' => 'URL de redirection', - 'Class:OAuthClient/Attribute:redirect_url+' => '', + 'Class:OAuthClient/Attribute:redirect_url+' => 'Cet URL doit ĂȘtre recopiĂ© dans la configuration OAuth2 de votre fournisseur', 'Class:OAuthClient/Attribute:mailbox_list' => 'Mailbox list', 'Class:OAuthClient/Attribute:mailbox_list+' => '', ]); @@ -57,22 +65,52 @@ Dict::Add('FR FR', 'French', 'Français', [ // // Class: OAuthClientAzure // -Dict::Add('FR FR', 'French', 'Français', [ + +Dict::Add('FR FR', 'French', 'Français', array( 'Class:OAuthClientAzure' => 'Client OAuth pour Microsoft Azure', 'Class:OAuthClientAzure/Name' => '%1$s (%2$s)', - -]); + 'Class:OAuthClientAzure/Attribute:scope' => 'Niveaux d\'accĂšs', + 'Class:OAuthClientAzure/Attribute:scope+' => 'Les niveaux par dĂ©faut sont les plus souvent suffisants', + 'Class:OAuthClientAzure/Attribute:scope/Value:SMTP' => 'SMTP', + 'Class:OAuthClientAzure/Attribute:scope/Value:SMTP+' => '', + 'Class:OAuthClientAzure/Attribute:scope/Value:IMAP' => 'IMAP', + 'Class:OAuthClientAzure/Attribute:scope/Value:IMAP+' => '', + 'Class:OAuthClientAzure/Attribute:advanced_scope' => 'Niveaux d\'accĂšs avancĂ©', + 'Class:OAuthClientAzure/Attribute:advanced_scope+' => 'A saisir, lorsque les niveaux prĂ©dĂ©finis ne suffisent pas', + 'Class:OAuthClientAzure/Attribute:used_scope' => 'Niveaux d\'accĂšs utilisĂ©s', + 'Class:OAuthClientAzure/Attribute:used_scope+' => '', + 'Class:OAuthClientAzure/Attribute:used_scope/Value:simple' => 'Simple', + 'Class:OAuthClientAzure/Attribute:used_scope/Value:simple+' => '', + 'Class:OAuthClientAzure/Attribute:used_scope/Value:advanced' => 'AvancĂ©', + 'Class:OAuthClientAzure/Attribute:used_scope/Value:advanced+' => '', + 'Class:OAuthClientAzure/Attribute:used_for_smtp' => 'UtilisĂ© pour SMTP', + 'Class:OAuthClientAzure/Attribute:used_for_smtp+' => 'Le Client OAuth utilisĂ© pour l\'envoi d\'emails doit ĂȘtre Ă  \'Oui\'', + 'Class:OAuthClientAzure/Attribute:used_for_smtp/Value:yes' => 'Oui', + 'Class:OAuthClientAzure/Attribute:used_for_smtp/Value:no' => 'Non', +)); // // Class: OAuthClientGoogle // -Dict::Add('FR FR', 'French', 'Français', [ + +Dict::Add('FR FR', 'French', 'Français', array( 'Class:OAuthClientGoogle' => 'Client OAuth pour Google', 'Class:OAuthClientGoogle/Name' => '%1$s (%2$s)', -]); - - -// Additional language entries not present in English dict -Dict::Add('FR FR', 'French', 'Français', array( - 'Class:OAuthClient/Name' => '%1$s-%%2$~', + 'Class:OAuthClientGoogle/Attribute:scope' => 'Niveaux d\'accĂšs', + 'Class:OAuthClientGoogle/Attribute:scope+' => 'Les niveaux par dĂ©faut sont les plus souvent suffisants', + 'Class:OAuthClientGoogle/Attribute:scope/Value:SMTP' => 'SMTP', + 'Class:OAuthClientGoogle/Attribute:scope/Value:SMTP+' => '', + 'Class:OAuthClientGoogle/Attribute:scope/Value:IMAP' => 'IMAP', + 'Class:OAuthClientGoogle/Attribute:scope/Value:IMAP+' => '', + 'Class:OAuthClientGoogle/Attribute:advanced_scope' => 'Niveaux d\'accĂšs avancĂ©', + 'Class:OAuthClientGoogle/Attribute:advanced_scope+' => 'A saisir, lorsque les niveaux prĂ©dĂ©finis ne suffisent pas', + 'Class:OAuthClientGoogle/Attribute:used_scope' => 'Niveaux d\'accĂšs utilisĂ©s', + 'Class:OAuthClientGoogle/Attribute:used_scope+' => '', + 'Class:OAuthClientGoogle/Attribute:used_scope/Value:simple' => 'Simple', + 'Class:OAuthClientGoogle/Attribute:used_scope/Value:advanced' => 'AvancĂ©', + 'Class:OAuthClientGoogle/Attribute:used_scope/Value:advanced+' => '', + 'Class:OAuthClientGoogle/Attribute:used_for_smtp' => 'UtilisĂ© pour SMTP', + 'Class:OAuthClientGoogle/Attribute:used_for_smtp+' => 'Le Client OAuth utilisĂ© pour l\'envoi d\'emails doit ĂȘtre Ă  \'Oui\'', + 'Class:OAuthClientGoogle/Attribute:used_for_smtp/Value:yes' => 'Oui', + 'Class:OAuthClientGoogle/Attribute:used_for_smtp/Value:no' => 'Non', )); diff --git a/datamodels/2.x/itop-oauth-client/module.itop-oauth-client.php b/datamodels/2.x/itop-oauth-client/module.itop-oauth-client.php index 3e80fcaf3..131628248 100644 --- a/datamodels/2.x/itop-oauth-client/module.itop-oauth-client.php +++ b/datamodels/2.x/itop-oauth-client/module.itop-oauth-client.php @@ -25,9 +25,8 @@ SetupWebPage::AddModule( 'datamodel' => array( 'vendor/autoload.php', 'model.itop-oauth-client.php', // Contains the PHP code generated by the "compilation" of datamodel.remote-authent-oauth.xml - 'src/Model/OAuthClientGoogle.php', - 'src/Model/OAuthClientAzure.php', 'src/Service/PopupMenuExtension.php', + 'src/Service/ApplicationUIExtension.php', ), 'webservice' => array( diff --git a/datamodels/2.x/itop-oauth-client/src/Model/OAuthClientAzure.php b/datamodels/2.x/itop-oauth-client/src/Model/OAuthClientAzure.php deleted file mode 100644 index eb798e64d..000000000 --- a/datamodels/2.x/itop-oauth-client/src/Model/OAuthClientAzure.php +++ /dev/null @@ -1,128 +0,0 @@ - 'cloud', - 'key_type' => 'autoincrement', - 'name_attcode' => ['name', 'scope'], - 'state_attcode' => '', - 'reconc_keys' => ['provider', 'name'], - 'db_table' => 'priv_oauth_client_azure', - 'db_key_field' => 'id', - 'icon' => utils::GetAbsoluteUrlModulesRoot().'itop-oauth-client/assets/img/icons8-azure.svg', - 'db_finalclass_field' => '', - 'uniqueness_rules' => [ - 'Username for scope' => - [ - 'attributes' => ['name', 'scope'], - 'filter' => null, - 'disabled' => false, - 'is_blocking' => true, - ], - 'OAuth Server' => - [ - 'attributes' => ['provider', 'scope', 'client_id', 'client_secret'], - 'filter' => null, - 'disabled' => false, - 'is_blocking' => true, - ], - ], - ]; - MetaModel::Init_Params($aParams); - MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeEnum('scope', [ - 'allowed_values' => new ValueSetEnum('EMail'), - 'display_style' => 'list', - 'sql' => 'scope', - 'default_value' => 'EMail', - 'is_null_allowed' => false, - 'depends_on' => [], - 'always_load_in_tables' => true, - ])); - - MetaModel::Init_SetZListItems('details', [ - 'name', - 'status', - 'description', - 'provider', - 'scope', - 'redirect_url', - 'client_id', - 'client_secret', - 'mailbox_list', - ]); - MetaModel::Init_SetZListItems('standard_search', [ - 'name', - 'provider', - 'status', - ]); - MetaModel::Init_SetZListItems('list', [ - 'status', - 'provider', - ]); - } - - public function PrefillCreationForm(&$aContextParam) - { - $this->Set('provider', 'Azure'); - $this->Set('redirect_url', OAuthClientProviderFactory::GetRedirectUri()); - - parent::PrefillCreationForm($aContextParam); - } - - /** - * Compute read-only values - * - * @return void - * @throws \ArchivedObjectException - * @throws \CoreException - * @throws \CoreUnexpectedValue - */ - public function ComputeValues() - { - parent::ComputeValues(); - if (empty($this->Get('provider'))) { - $this->Set('provider', 'Azure'); - } - if (empty($this->Get('redirect_url'))) { - $this->Set('redirect_url', OAuthClientProviderFactory::GetRedirectUri()); - } - } - - public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '') - { - if ($sAttCode == 'provider' || $sAttCode == 'redirect_url') { - return OPT_ATT_READONLY; - } - - return parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState); - } - - public function GetInitialStateAttributeFlags($sAttCode, &$aReasons = array()) - { - if ($sAttCode == 'provider' || $sAttCode == 'redirect_url') { - return OPT_ATT_READONLY; - } - - return parent::GetInitialStateAttributeFlags($sAttCode, $aReasons); - } - - public function GetDefaultMailServer() - { - return 'outlook.office365.com'; - } - - public function GetScope() - { - return 'https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send offline_access'; - } -} \ No newline at end of file diff --git a/datamodels/2.x/itop-oauth-client/src/Model/OAuthClientGoogle.php b/datamodels/2.x/itop-oauth-client/src/Model/OAuthClientGoogle.php deleted file mode 100644 index 83c111852..000000000 --- a/datamodels/2.x/itop-oauth-client/src/Model/OAuthClientGoogle.php +++ /dev/null @@ -1,134 +0,0 @@ - 'cloud', - 'key_type' => 'autoincrement', - 'name_attcode' => ['name', 'scope'], - 'state_attcode' => '', - 'reconc_keys' => ['provider', 'name'], - 'db_table' => 'priv_oauth_client_google', - 'db_key_field' => 'id', - 'icon' => utils::GetAbsoluteUrlModulesRoot().'itop-oauth-client/assets/img/icons8-google.svg', - 'db_finalclass_field' => '', - 'uniqueness_rules' => [ - 'Username for scope' => - [ - 'attributes' => ['name', 'scope'], - 'filter' => null, - 'disabled' => false, - 'is_blocking' => true, - ], - 'OAuth Server' => - [ - 'attributes' => ['provider', 'scope', 'client_id', 'client_secret'], - 'filter' => null, - 'disabled' => false, - 'is_blocking' => true, - ], - ], - ); - MetaModel::Init_Params($aParams); - MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeEnum('scope', [ - 'allowed_values' => new ValueSetEnum('EMail'), - 'display_style' => 'list', - 'sql' => 'scope', - 'default_value' => 'EMail', - 'is_null_allowed' => false, - 'depends_on' => [], - 'always_load_in_tables' => true, - ])); - - MetaModel::Init_SetZListItems('details', [ - 'name', - 'status', - 'description', - 'provider', - 'scope', - 'redirect_url', - 'client_id', - 'client_secret', - 'mailbox_list', - ]); - MetaModel::Init_SetZListItems('standard_search', [ - 'name', - 'provider', - 'status', - ]); - MetaModel::Init_SetZListItems('list', [ - 'status', - 'provider', - ]); - } - - public function PrefillCreationForm(&$aContextParam) - { - $this->Set('provider', 'Google'); - $this->Set('scope', 'EMail'); - $this->Set('redirect_url', OAuthClientProviderFactory::GetRedirectUri()); - - parent::PrefillCreationForm($aContextParam); - } - - public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '') - { - if ($sAttCode == 'provider' || $sAttCode == 'scope' || $sAttCode == 'redirect_url') { - return OPT_ATT_READONLY; - } - - return parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState); - } - - public function GetInitialStateAttributeFlags($sAttCode, &$aReasons = array()) - { - if ($sAttCode == 'provider' || $sAttCode == 'scope' || $sAttCode == 'redirect_url') { - return OPT_ATT_READONLY; - } - - return parent::GetInitialStateAttributeFlags($sAttCode, $aReasons); - } - - /** - * Compute read-only values - * - * @return void - * @throws \ArchivedObjectException - * @throws \CoreException - * @throws \CoreUnexpectedValue - */ - public function ComputeValues() - { - parent::ComputeValues(); - if (empty($this->Get('provider'))) { - $this->Set('provider', 'Google'); - } - if (empty($this->Get('redirect_url'))) { - $this->Set('redirect_url', OAuthClientProviderFactory::GetRedirectUri()); - } - if (empty($this->Get('scope'))) { - $this->Set('scope', 'EMail'); - } - } - - - public function GetDefaultMailServer() - { - return 'imap.gmail.com'; - } - - public function GetScope() - { - return 'https://mail.google.com/'; - } -} \ No newline at end of file diff --git a/datamodels/2.x/itop-oauth-client/src/Service/ApplicationUIExtension.php b/datamodels/2.x/itop-oauth-client/src/Service/ApplicationUIExtension.php new file mode 100644 index 000000000..7fb0b8a53 --- /dev/null +++ b/datamodels/2.x/itop-oauth-client/src/Service/ApplicationUIExtension.php @@ -0,0 +1,31 @@ +Get('status') == 'inactive') { + return HILIGHT_CLASS_WARNING; + } elseif ($oObject->Get('used_for_smtp') == 'yes' && $oConfig->Get('email_transport_smtp.username') == $oObject->Get('name')) { + return HILIGHT_CLASS_OK; + } + } + + return HILIGHT_CLASS_NONE; + } +} \ No newline at end of file diff --git a/datamodels/2.x/itop-oauth-client/src/Service/PopupMenuExtension.php b/datamodels/2.x/itop-oauth-client/src/Service/PopupMenuExtension.php index 6ca4783b4..106b0ea83 100644 --- a/datamodels/2.x/itop-oauth-client/src/Service/PopupMenuExtension.php +++ b/datamodels/2.x/itop-oauth-client/src/Service/PopupMenuExtension.php @@ -41,7 +41,7 @@ class PopupMenuExtension implements \iPopupMenuExtension $sId = $oObj->GetKey(); $sAjaxUri = utils::GetAbsoluteUrlModulePage(static::MODULE_CODE, 'ajax.php'); // Add a new menu item that triggers a custom JS function defined in our own javascript file: js/sample.js - $sJSFileUrl = utils::GetAbsoluteUrlModulesRoot().static::MODULE_CODE.'/assets/js/oauth_connect.js'; + $sJSFileUrl = 'env-'.utils::GetCurrentEnvironment().'/'.static::MODULE_CODE.'/assets/js/oauth_connect.js'; $sRedirectUri = OAuthClientProviderFactory::GetRedirectUri(); $aResult[] = new JSPopupMenuItem( $sMenu.' from '.$sObjClass, @@ -51,8 +51,8 @@ class PopupMenuExtension implements \iPopupMenuExtension ); if ($bHasToken) { - $sScope = $oObj->Get('scope'); - if ($sScope == 'EMail') { + $aScopes = $oObj->Get('scope')->GetValues(); + if (in_array('IMAP', $aScopes)) { $aParams = $oAppContext->GetAsHash(); $sMenu = 'Menu:CreateMailbox'; $sObjClass = get_class($oObj); diff --git a/datamodels/2.x/itop-oauth-client/templates/CreateMailbox.html.twig b/datamodels/2.x/itop-oauth-client/templates/CreateMailbox.html.twig new file mode 100644 index 000000000..578bce7b0 --- /dev/null +++ b/datamodels/2.x/itop-oauth-client/templates/CreateMailbox.html.twig @@ -0,0 +1,3 @@ +{# @copyright Copyright (C) 2010-2022 Combodo SARL #} +{# @license http://opensource.org/licenses/AGPL-3.0 #} + diff --git a/datamodels/2.x/itop-oauth-client/templates/CreateMailbox.ready.js.twig b/datamodels/2.x/itop-oauth-client/templates/CreateMailbox.ready.js.twig new file mode 100644 index 000000000..8b3d10330 --- /dev/null +++ b/datamodels/2.x/itop-oauth-client/templates/CreateMailbox.ready.js.twig @@ -0,0 +1,4 @@ +{# @copyright Copyright (C) 2010-2022 Combodo SARL #} +{# @license http://opensource.org/licenses/AGPL-3.0 #} + +window.location.href = '{{ sURL|raw }}' \ No newline at end of file diff --git a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php index 48e5cd1be..d1bd69106 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php @@ -93,7 +93,7 @@ class ObjectFormManager extends FormManager * @since 2.7.6 3.0.0 N°4384 method creation : factorize as this is used twice now * @since 2.7.7 3.0.1 N°4867 now only used once, but we decided to keep this method anyway */ - protected static function DecodeFormManagerData($formManagerData) + public static function DecodeFormManagerData($formManagerData) { if (is_array($formManagerData)) { return $formManagerData; diff --git a/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php b/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php index e77312602..27ba772f5 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php @@ -453,7 +453,7 @@ class ObjectFormHandlerHelper * @throws \OQLException */ public function CheckReadFormDataAllowed($sFormManagerData){ - $aJsonFromData = json_decode($sFormManagerData, true); + $aJsonFromData = ObjectFormManager::DecodeFormManagerData($sFormManagerData); if(isset($aJsonFromData['formobject_class']) && isset($aJsonFromData['formobject_id']) && !$this->oSecurityHelper->IsActionAllowed(UR_ACTION_READ, $aJsonFromData['formobject_class'], $aJsonFromData['formobject_id'])){ diff --git a/js/layouts/tab-container/tab-container.js b/js/layouts/tab-container/tab-container.js index 24c5ae63e..45792e671 100644 --- a/js/layouts/tab-container/tab-container.js +++ b/js/layouts/tab-container/tab-container.js @@ -156,7 +156,7 @@ $(function() }); me._updateExtraTabsList(); }, { - root: $('.ibo-tab-container--tabs-list')[0], + root: this.element.find(this.js_selectors.tabs_list)[0], threshold: [0.9] // N°4783 Should be completely visible, but lowering the threshold prevents a bug in the JS Observer API when the window is zoomed in/out, in which case all items respond as being hidden even when they are not. }); this.element.find(this.js_selectors.tab_header).each(function(){ diff --git a/lib/autoload.php b/lib/autoload.php index f1eeef5ab..460e67535 100644 --- a/lib/autoload.php +++ b/lib/autoload.php @@ -2,11 +2,6 @@ // autoload.php @generated by Composer -if (PHP_VERSION_ID < 50600) { - echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; - exit(1); -} - require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInit7f81b4a2a468a061c306af5e447a9a9f::getLoader(); diff --git a/lib/composer/ClassLoader.php b/lib/composer/ClassLoader.php index afef3fa2a..0cd6055d1 100644 --- a/lib/composer/ClassLoader.php +++ b/lib/composer/ClassLoader.php @@ -149,7 +149,7 @@ class ClassLoader /** * @return string[] Array of classname => path - * @psalm-return array + * @psalm-var array */ public function getClassMap() { diff --git a/lib/composer/InstalledVersions.php b/lib/composer/InstalledVersions.php index c6b54af7b..7c5502ca4 100644 --- a/lib/composer/InstalledVersions.php +++ b/lib/composer/InstalledVersions.php @@ -21,26 +21,11 @@ use Composer\Semver\VersionParser; * See also https://getcomposer.org/doc/07-runtime.md#installed-versions * * To require its presence, you can require `composer-runtime-api ^2.0` - * - * @final */ class InstalledVersions { - /** - * @var mixed[]|null - * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null - */ private static $installed; - - /** - * @var bool|null - */ private static $canGetVendors; - - /** - * @var array[] - * @psalm-var array}> - */ private static $installedByVendor = array(); /** @@ -243,7 +228,7 @@ class InstalledVersions /** * @return array - * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} */ public static function getRootPackage() { @@ -257,7 +242,7 @@ class InstalledVersions * * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. * @return array[] - * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} */ public static function getRawData() { @@ -280,7 +265,7 @@ class InstalledVersions * Returns the raw data of all installed.php which are currently loaded for custom implementations * * @return array[] - * @psalm-return list}> + * @psalm-return list}> */ public static function getAllRawData() { @@ -303,7 +288,7 @@ class InstalledVersions * @param array[] $data A vendor/composer/installed.php data set * @return void * - * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} $data */ public static function reload($data) { @@ -313,7 +298,7 @@ class InstalledVersions /** * @return array[] - * @psalm-return list}> + * @psalm-return list}> */ private static function getInstalled() { diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 274b4d7a8..c37c6a529 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -2,7 +2,7 @@ // autoload_classmap.php @generated by Composer -$vendorDir = dirname(__DIR__); +$vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( diff --git a/lib/composer/autoload_namespaces.php b/lib/composer/autoload_namespaces.php index 6629b7e09..1db5bf646 100644 --- a/lib/composer/autoload_namespaces.php +++ b/lib/composer/autoload_namespaces.php @@ -2,7 +2,7 @@ // autoload_namespaces.php @generated by Composer -$vendorDir = dirname(__DIR__); +$vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( diff --git a/lib/composer/autoload_psr4.php b/lib/composer/autoload_psr4.php index 0a7d4ce20..5a52e9e00 100644 --- a/lib/composer/autoload_psr4.php +++ b/lib/composer/autoload_psr4.php @@ -2,7 +2,7 @@ // autoload_psr4.php @generated by Composer -$vendorDir = dirname(__DIR__); +$vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( diff --git a/lib/composer/include_paths.php b/lib/composer/include_paths.php index af33c1491..d4fb96718 100644 --- a/lib/composer/include_paths.php +++ b/lib/composer/include_paths.php @@ -2,7 +2,7 @@ // include_paths.php @generated by Composer -$vendorDir = dirname(__DIR__); +$vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( diff --git a/lib/composer/platform_check.php b/lib/composer/platform_check.php index 4bf5e61af..afd72fdd1 100644 --- a/lib/composer/platform_check.php +++ b/lib/composer/platform_check.php @@ -11,10 +11,10 @@ if (!(PHP_VERSION_ID >= 70205)) { $missingExtensions = array(); extension_loaded('dom') || $missingExtensions[] = 'dom'; extension_loaded('gd') || $missingExtensions[] = 'gd'; -extension_loaded('iconv') || $missingExtensions[] = 'iconv'; extension_loaded('json') || $missingExtensions[] = 'json'; extension_loaded('libxml') || $missingExtensions[] = 'libxml'; extension_loaded('mysqli') || $missingExtensions[] = 'mysqli'; +extension_loaded('openssl') || $missingExtensions[] = 'openssl'; extension_loaded('soap') || $missingExtensions[] = 'soap'; extension_loaded('tokenizer') || $missingExtensions[] = 'tokenizer'; extension_loaded('xml') || $missingExtensions[] = 'xml'; diff --git a/lib/thenetworg/oauth2-azure/.gitignore b/lib/thenetworg/oauth2-azure/.gitignore index 9c9c8f271..57a23bd06 100644 --- a/lib/thenetworg/oauth2-azure/.gitignore +++ b/lib/thenetworg/oauth2-azure/.gitignore @@ -3,3 +3,7 @@ composer.phar composer.lock .DS_Store + +# IDE +/.idea +/.vscode diff --git a/lib/thenetworg/oauth2-azure/README.md b/lib/thenetworg/oauth2-azure/README.md index fbf296595..ee6f06d3f 100644 --- a/lib/thenetworg/oauth2-azure/README.md +++ b/lib/thenetworg/oauth2-azure/README.md @@ -46,7 +46,7 @@ $provider = new TheNetworg\OAuth2\Client\Provider\Azure([ 'clientSecret' => '{azure-client-secret}', 'redirectUri' => 'https://example.com/callback-url', //Optional - 'scopes' => 'openid', + 'scopes' => ['openid'], //Optional 'defaultEndPointVersion' => '2.0' ]); diff --git a/lib/thenetworg/oauth2-azure/composer.json b/lib/thenetworg/oauth2-azure/composer.json index e2e3b922d..4fb348463 100644 --- a/lib/thenetworg/oauth2-azure/composer.json +++ b/lib/thenetworg/oauth2-azure/composer.json @@ -22,9 +22,11 @@ "sso" ], "require": { - "php": "^5.6|^7.0|^8.0", + "ext-json": "*", + "ext-openssl": "*", + "php": "^7.1|^8.0", "league/oauth2-client": "~2.0", - "firebase/php-jwt": "~3.0||~4.0||~5.0" + "firebase/php-jwt": "~3.0||~4.0||~5.0||~6.0" }, "autoload": { "psr-4": { diff --git a/lib/thenetworg/oauth2-azure/src/Provider/Azure.php b/lib/thenetworg/oauth2-azure/src/Provider/Azure.php index d07fdef34..50aec8235 100644 --- a/lib/thenetworg/oauth2-azure/src/Provider/Azure.php +++ b/lib/thenetworg/oauth2-azure/src/Provider/Azure.php @@ -3,9 +3,13 @@ namespace TheNetworg\OAuth2\Client\Provider; use Firebase\JWT\JWT; +use Firebase\JWT\JWK; +use Firebase\JWT\Key; use League\OAuth2\Client\Grant\AbstractGrant; use League\OAuth2\Client\Provider\AbstractProvider; use League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use League\OAuth2\Client\Provider\ResourceOwnerInterface; +use League\OAuth2\Client\Token\AccessTokenInterface; use League\OAuth2\Client\Tool\BearerAuthorizationTrait; use Psr\Http\Message\ResponseInterface; use TheNetworg\OAuth2\Client\Grant\JwtBearer; @@ -66,7 +70,8 @@ class Azure extends AbstractProvider } if (!array_key_exists($version, $this->openIdConfiguration[$tenant])) { $versionInfix = $this->getVersionUriInfix($version); - $openIdConfigurationUri = 'https://login.microsoftonline.com/' . $tenant . $versionInfix . '/.well-known/openid-configuration'; + $openIdConfigurationUri = $this->urlLogin . $tenant . $versionInfix . '/.well-known/openid-configuration?appid=' . $this->clientId; + $factory = $this->getRequestFactory(); $request = $factory->getRequestWithOptions( 'get', @@ -80,19 +85,28 @@ class Azure extends AbstractProvider return $this->openIdConfiguration[$tenant][$version]; } - public function getBaseAuthorizationUrl() + /** + * @inheritdoc + */ + public function getBaseAuthorizationUrl(): string { $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); return $openIdConfiguration['authorization_endpoint']; } - public function getBaseAccessTokenUrl(array $params) + /** + * @inheritdoc + */ + public function getBaseAccessTokenUrl(array $params): string { $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); return $openIdConfiguration['token_endpoint']; } - public function getAccessToken($grant, array $options = []) + /** + * @inheritdoc + */ + public function getAccessToken($grant, array $options = []): AccessTokenInterface { if ($this->defaultEndPointVersion != self::ENDPOINT_VERSION_2_0) { // Version 2.0 does not support the resources parameter @@ -106,14 +120,21 @@ class Azure extends AbstractProvider return parent::getAccessToken($grant, $options); } - public function getResourceOwner(\League\OAuth2\Client\Token\AccessToken $token) + /** + * @inheritdoc + */ + public function getResourceOwner(\League\OAuth2\Client\Token\AccessToken $token): ResourceOwnerInterface { $data = $token->getIdTokenClaims(); return $this->createResourceOwner($data, $token); } - public function getResourceOwnerDetailsUrl(\League\OAuth2\Client\Token\AccessToken $token) + /** + * @inheritdoc + */ + public function getResourceOwnerDetailsUrl(\League\OAuth2\Client\Token\AccessToken $token): string { + return ''; // shouldn't that return such a URL? } public function getObjects($tenant, $ref, &$accessToken, $headers = []) @@ -164,11 +185,11 @@ class Azure extends AbstractProvider return 'https://' . $openIdConfiguration['msgraph_host']; } - public function get($ref, &$accessToken, $headers = []) + public function get($ref, &$accessToken, $headers = [], $doNotWrap = false) { $response = $this->request('get', $ref, $accessToken, ['headers' => $headers]); - return $this->wrapResponse($response); + return $doNotWrap ? $response : $this->wrapResponse($response); } public function post($ref, $body, &$accessToken, $headers = []) @@ -352,8 +373,24 @@ class Azure extends AbstractProvider $publicKey = $pkey_array ['key']; - $keys[$keyinfo['kid']] = $publicKey; + $keys[$keyinfo['kid']] = new Key($publicKey, 'RS256'); } + } else if (isset($keyinfo['n']) && isset($keyinfo['e'])) { + $pkey_object = JWK::parseKey($keyinfo); + + if ($pkey_object === false) { + throw new \RuntimeException('An attempt to read a public key from a ' . $keyinfo['n'] . ' certificate failed.'); + } + + $pkey_array = openssl_pkey_get_details($pkey_object); + + if ($pkey_array === false) { + throw new \RuntimeException('An attempt to get a public key as an array from a ' . $keyinfo['n'] . ' certificate failed.'); + } + + $publicKey = $pkey_array ['key']; + + $keys[$keyinfo['kid']] = new Key($publicKey, 'RS256');; } } @@ -382,7 +419,10 @@ class Azure extends AbstractProvider return $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); } - protected function checkResponse(ResponseInterface $response, $data) + /** + * @inheritdoc + */ + protected function checkResponse(ResponseInterface $response, $data): void { if (isset($data['odata.error']) || isset($data['error'])) { if (isset($data['odata.error']['message']['value'])) { @@ -402,27 +442,39 @@ class Azure extends AbstractProvider throw new IdentityProviderException( $message, $response->getStatusCode(), - $response + $response->getBody() ); } } - protected function getDefaultScopes() + /** + * @inheritdoc + */ + protected function getDefaultScopes(): array { return $this->scope; } - protected function getScopeSeparator() + /** + * @inheritdoc + */ + protected function getScopeSeparator(): string { return $this->scopeSeparator; } - protected function createAccessToken(array $response, AbstractGrant $grant) + /** + * @inheritdoc + */ + protected function createAccessToken(array $response, AbstractGrant $grant): AccessToken { return new AccessToken($response, $this); } - protected function createResourceOwner(array $response, \League\OAuth2\Client\Token\AccessToken $token) + /** + * @inheritdoc + */ + protected function createResourceOwner(array $response, \League\OAuth2\Client\Token\AccessToken $token): AzureResourceOwner { return new AzureResourceOwner($response); } diff --git a/lib/thenetworg/oauth2-azure/src/Token/AccessToken.php b/lib/thenetworg/oauth2-azure/src/Token/AccessToken.php index 3899f8377..7de80f9e5 100644 --- a/lib/thenetworg/oauth2-azure/src/Token/AccessToken.php +++ b/lib/thenetworg/oauth2-azure/src/Token/AccessToken.php @@ -19,6 +19,8 @@ class AccessToken extends \League\OAuth2\Client\Token\AccessToken if (!empty($options['id_token'])) { $this->idToken = $options['id_token']; + unset($this->values['id_token']); + $keys = $provider->getJwtVerificationKeys(); $idTokenClaims = null; try { @@ -45,8 +47,27 @@ class AccessToken extends \League\OAuth2\Client\Token\AccessToken } } + public function getIdToken() + { + return $this->idToken; + } + public function getIdTokenClaims() { return $this->idTokenClaims; } + + /** + * @inheritdoc + */ + public function jsonSerialize() + { + $parameters = parent::jsonSerialize(); + + if ($this->idToken) { + $parameters['id_token'] = $this->idToken; + } + + return $parameters; + } } diff --git a/setup/licenses/community-licenses.xml b/setup/licenses/community-licenses.xml index 15d187602..d134dc838 100644 --- a/setup/licenses/community-licenses.xml +++ b/setup/licenses/community-licenses.xml @@ -1672,214 +1672,6 @@ Awesome, nor vice versa. **Please do not use brand logos for any purpose except to represent the company, product, or service to which they refer.** ]]> - - apereo/phpcas - Joachim Fritschi - Adam Franco - Apache-2.0 - - C3 js Masayuki Tanaka diff --git a/setup/setuputils.class.inc.php b/setup/setuputils.class.inc.php index 093f9f8ce..359063533 100644 --- a/setup/setuputils.class.inc.php +++ b/setup/setuputils.class.inc.php @@ -684,6 +684,7 @@ class SetupUtils * Emulates sys_get_temp_dir if needed (PHP < 5.2.1) * * @return string Path to the system's temp directory + * @uses \sys_get_temp_dir() */ public static function GetTmpDir() { return realpath(sys_get_temp_dir()); diff --git a/sources/Application/UI/Base/Component/Button/ButtonUIBlockFactory.php b/sources/Application/UI/Base/Component/Button/ButtonUIBlockFactory.php index fa03c643d..0b9d5b875 100644 --- a/sources/Application/UI/Base/Component/Button/ButtonUIBlockFactory.php +++ b/sources/Application/UI/Base/Component/Button/ButtonUIBlockFactory.php @@ -424,11 +424,11 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory $oButton->SetActionType($sActionType) ->SetColor($sColor); - if (utils::StrLen($sValue) > 0) { + if (utils::IsNotNullOrEmptyString($sValue)) { $oButton->SetValue($sValue); } - if (utils::StrLen($sName) > 0) { + if (utils::IsNotNullOrEmptyString($sName)) { $oButton->SetName($sName); } diff --git a/sources/Application/UI/Base/Component/Input/InputWithLabel.php b/sources/Application/UI/Base/Component/Input/InputWithLabel.php index b7ce8f4d9..09931a4f6 100644 --- a/sources/Application/UI/Base/Component/Input/InputWithLabel.php +++ b/sources/Application/UI/Base/Component/Input/InputWithLabel.php @@ -135,7 +135,7 @@ class InputWithLabel extends UIBlock */ public function HasDescription(): bool { - return utils::StrLen($this->sDescription) > 0; + return utils::IsNotNullOrEmptyString($this->sDescription); } } \ No newline at end of file diff --git a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php index c227ce81a..d4d92c935 100644 --- a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php +++ b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php @@ -8,15 +8,8 @@ use OAuthClient; abstract class OAuthClientProviderAbstract implements IOAuthClientProvider { -// /** @var string */ -// static protected $sVendorName = ''; -// /** @var array */ -// static protected $sVendorColors = ['', '', '', '']; -// /** @var string */ -// static protected $sVendorIcon = ''; -// static protected $sRequiredSMTPScope = ''; -// static protected $sRequiredIMAPScope = ''; -// static protected $sRequiredPOPScope = ''; + /** @var string */ + static protected $sVendorName = ''; /** @var \League\OAuth2\Client\Provider\GenericProvider */ protected $oVendorProvider; @@ -69,4 +62,12 @@ abstract class OAuthClientProviderAbstract implements IOAuthClientProvider return $this->oOauthClient->GetScope(); } + /** + * @return string + */ + public static function GetVendorName() + { + return self::$sVendorName; + } + } \ No newline at end of file diff --git a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php index e3c594101..667d5875a 100644 --- a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php +++ b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php @@ -6,16 +6,8 @@ use TheNetworg\OAuth2\Client\Provider\Azure; class OAuthClientProviderAzure extends OAuthClientProviderAbstract { -// /** @var string */ -// static protected $sVendorName = 'Azure'; -// /** @var array */ -// static protected $sVendorColors = ['#0766b7', '#0d396b', '#2893df', '#3ccbf4']; -// /** @var string */ -// static protected $sVendorIcon = '../images/icons/icons8-azure.svg'; -// static protected $sRequiredSMTPScope = 'https://outlook.office.com/SMTP.Send offline_access'; -// static protected $sRequiredIMAPScope = 'https://outlook.office.com/IMAP.AccessAsUser.All offline_access'; -// static protected $sRequiredPOPScope = 'https://outlook.office.com/POP.AccessAsUser.All offline access'; - + /** @var string */ + static protected $sVendorName = 'Azure'; public function __construct($oOAuthClient, array $collaborators = []) { diff --git a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php index 58445c3b7..66230eb3e 100644 --- a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php +++ b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php @@ -43,8 +43,7 @@ class OAuthClientProviderFactory throw new CoreException(Dict::Format('itop-oauth-client:MissingOAuthClient', $sUsername)); } while ($oOAuthClient = $oSet->Fetch()) { - $sScope = $oOAuthClient->Get('scope'); - if ($sScope == 'EMail') { + if ($oOAuthClient->Get('used_for_smtp') == 'yes') { return $oOAuthClient; } } diff --git a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php index 35ddafca8..838a8783e 100644 --- a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php +++ b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php @@ -6,16 +6,8 @@ use League\OAuth2\Client\Provider\Google; class OAuthClientProviderGoogle extends OAuthClientProviderAbstract { -// /** @var string */ -// static protected $sVendorName = 'Google'; -// /** @var array */ -// static protected $sVendorColors = ['#DB4437', '#F4B400', '#0F9D58', '#4285F4']; -// /** @var string */ -// static protected $sVendorIcon = '../images/icons/icons8-google.svg'; -// -// static protected $sRequiredSMTPScope = 'https://mail.google.com/'; -// static protected $sRequiredIMAPScope = 'https://mail.google.com/'; -// static protected $sRequiredPOPScope = 'https://mail.google.com/'; + /** @var string */ + static protected $sVendorName = 'Google'; public function __construct($oOAuthClient, array $collaborators = []) {