mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 00:42:41 +01:00
Remove all readers and all nontrivial writers for "accountType" and "accountDomain" on "ExternalAccount"
Summary: Depends on D21018. Ref T13493. Ref T6703. The "ExternalAccount" table has a unique key on `<accountType, accountDomain, accountID>` but this no longer matches our model of reality and changes in this sequence end writes to `accountID`. Remove this key. Then, remove all readers of `accountType` and `accountDomain` (and all nontrivial writers) because none of these callsites are well-aligned with plans in T6703. This change has no user-facing impact today: all the rules about linking/unlinking/etc remain unchanged, because other rules currently prevent creation of more than one provider with a given "accountType". Test Plan: - Linked an OAuth1 account (JIRA). - Linked an OAuth2 account (Asana). - Used `bin/auth refresh` to cycle OAuth tokens. - Grepped for affected symbols. - Published an Asana update. - Published a JIRA link. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13493, T6703 Differential Revision: https://secure.phabricator.com/D21019
This commit is contained in:
parent
b8f0613b30
commit
84b5ad09e6
12 changed files with 104 additions and 121 deletions
21
resources/sql/autopatches/20200222.xident.02.dropkey.php
Normal file
21
resources/sql/autopatches/20200222.xident.02.dropkey.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// See T13493. This table previously had a UNIQUE KEY on "<accountType,
|
||||||
|
// accountDomain, accountID>", which is obsolete. The application now violates
|
||||||
|
// this key, so make sure it gets dropped.
|
||||||
|
|
||||||
|
// There's no "IF EXISTS" modifier for "ALTER TABLE" so run this as a PHP patch
|
||||||
|
// instead of an SQL patch.
|
||||||
|
|
||||||
|
$table = new PhabricatorExternalAccount();
|
||||||
|
$conn = $table->establishConnection('w');
|
||||||
|
|
||||||
|
try {
|
||||||
|
queryfx(
|
||||||
|
$conn,
|
||||||
|
'ALTER TABLE %R DROP KEY %T',
|
||||||
|
$table,
|
||||||
|
'account_details');
|
||||||
|
} catch (AphrontQueryException $ex) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
|
@ -116,14 +116,21 @@ final class PhabricatorAuthLoginController
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// If the user already has a linked account of this type, prevent them
|
// If the user already has a linked account on this provider, prevent
|
||||||
// from linking a second account. This can happen if they swap logins
|
// them from linking a second account. This can happen if they swap
|
||||||
// and then refresh the account link. See T6707. We will eventually
|
// logins and then refresh the account link.
|
||||||
// allow this after T2549.
|
|
||||||
|
// There's no technical reason we can't allow you to link multiple
|
||||||
|
// accounts from a single provider; disallowing this is currently a
|
||||||
|
// product deciison. See T2549.
|
||||||
|
|
||||||
$existing_accounts = id(new PhabricatorExternalAccountQuery())
|
$existing_accounts = id(new PhabricatorExternalAccountQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withUserPHIDs(array($viewer->getPHID()))
|
->withUserPHIDs(array($viewer->getPHID()))
|
||||||
->withAccountTypes(array($account->getAccountType()))
|
->withProviderConfigPHIDs(
|
||||||
|
array(
|
||||||
|
$provider->getProviderConfigPHID(),
|
||||||
|
))
|
||||||
->execute();
|
->execute();
|
||||||
if ($existing_accounts) {
|
if ($existing_accounts) {
|
||||||
return $this->renderError(
|
return $this->renderError(
|
||||||
|
|
|
@ -18,16 +18,6 @@ final class PhabricatorAuthManagementRefreshWorkflow
|
||||||
'param' => 'user',
|
'param' => 'user',
|
||||||
'help' => pht('Refresh tokens for a given user.'),
|
'help' => pht('Refresh tokens for a given user.'),
|
||||||
),
|
),
|
||||||
array(
|
|
||||||
'name' => 'type',
|
|
||||||
'param' => 'provider',
|
|
||||||
'help' => pht('Refresh tokens for a given provider type.'),
|
|
||||||
),
|
|
||||||
array(
|
|
||||||
'name' => 'domain',
|
|
||||||
'param' => 'domain',
|
|
||||||
'help' => pht('Refresh tokens for a given domain.'),
|
|
||||||
),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,17 +47,6 @@ final class PhabricatorAuthManagementRefreshWorkflow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$type = $args->getArg('type');
|
|
||||||
if (strlen($type)) {
|
|
||||||
$query->withAccountTypes(array($type));
|
|
||||||
}
|
|
||||||
|
|
||||||
$domain = $args->getArg('domain');
|
|
||||||
if (strlen($domain)) {
|
|
||||||
$query->withAccountDomains(array($domain));
|
|
||||||
}
|
|
||||||
|
|
||||||
$accounts = $query->execute();
|
$accounts = $query->execute();
|
||||||
|
|
||||||
if (!$accounts) {
|
if (!$accounts) {
|
||||||
|
@ -82,25 +61,24 @@ final class PhabricatorAuthManagementRefreshWorkflow
|
||||||
}
|
}
|
||||||
|
|
||||||
$providers = PhabricatorAuthProvider::getAllEnabledProviders();
|
$providers = PhabricatorAuthProvider::getAllEnabledProviders();
|
||||||
|
$providers = mpull($providers, null, 'getProviderConfigPHID');
|
||||||
|
|
||||||
foreach ($accounts as $account) {
|
foreach ($accounts as $account) {
|
||||||
$console->writeOut(
|
$console->writeOut(
|
||||||
"%s\n",
|
"%s\n",
|
||||||
pht(
|
pht(
|
||||||
'Refreshing account #%d (%s/%s).',
|
'Refreshing account #%d.',
|
||||||
$account->getID(),
|
$account->getID()));
|
||||||
$account->getAccountType(),
|
|
||||||
$account->getAccountDomain()));
|
|
||||||
|
|
||||||
$key = $account->getProviderKey();
|
$config_phid = $account->getProviderConfigPHID();
|
||||||
if (empty($providers[$key])) {
|
if (empty($providers[$config_phid])) {
|
||||||
$console->writeOut(
|
$console->writeOut(
|
||||||
"> %s\n",
|
"> %s\n",
|
||||||
pht('Skipping, provider is not enabled or does not exist.'));
|
pht('Skipping, provider is not enabled or does not exist.'));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$provider = $providers[$key];
|
$provider = $providers[$config_phid];
|
||||||
if (!($provider instanceof PhabricatorOAuth2AuthProvider)) {
|
if (!($provider instanceof PhabricatorOAuth2AuthProvider)) {
|
||||||
$console->writeOut(
|
$console->writeOut(
|
||||||
"> %s\n",
|
"> %s\n",
|
||||||
|
|
|
@ -20,6 +20,10 @@ abstract class PhabricatorAuthProvider extends Phobject {
|
||||||
return $this->providerConfig;
|
return $this->providerConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getProviderConfigPHID() {
|
||||||
|
return $this->getProviderConfig()->getPHID();
|
||||||
|
}
|
||||||
|
|
||||||
public function getConfigurationHelp() {
|
public function getConfigurationHelp() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -360,11 +364,18 @@ abstract class PhabricatorAuthProvider extends Phobject {
|
||||||
$config = $this->getProviderConfig();
|
$config = $this->getProviderConfig();
|
||||||
$adapter = $this->getAdapter();
|
$adapter = $this->getAdapter();
|
||||||
|
|
||||||
return id(new PhabricatorExternalAccount())
|
$account = id(new PhabricatorExternalAccount())
|
||||||
->setAccountType($adapter->getAdapterType())
|
|
||||||
->setAccountDomain($adapter->getAdapterDomain())
|
|
||||||
->setProviderConfigPHID($config->getPHID())
|
->setProviderConfigPHID($config->getPHID())
|
||||||
->attachAccountIdentifiers(array());
|
->attachAccountIdentifiers(array());
|
||||||
|
|
||||||
|
// TODO: Remove this when these columns are removed. They no longer have
|
||||||
|
// readers or writers (other than this callsite).
|
||||||
|
|
||||||
|
$account
|
||||||
|
->setAccountType($adapter->getAdapterType())
|
||||||
|
->setAccountDomain($adapter->getAdapterDomain());
|
||||||
|
|
||||||
|
return $account;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLoginOrder() {
|
public function getLoginOrder() {
|
||||||
|
|
|
@ -199,7 +199,7 @@ abstract class PhabricatorOAuth2AuthProvider
|
||||||
PhabricatorExternalAccount $account,
|
PhabricatorExternalAccount $account,
|
||||||
$force_refresh = false) {
|
$force_refresh = false) {
|
||||||
|
|
||||||
if ($account->getProviderKey() !== $this->getProviderKey()) {
|
if ($account->getProviderConfigPHID() !== $this->getProviderConfigPHID()) {
|
||||||
throw new Exception(pht('Account does not match provider!'));
|
throw new Exception(pht('Account does not match provider!'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,6 @@ final class PhabricatorExternalAccountQuery
|
||||||
|
|
||||||
private $ids;
|
private $ids;
|
||||||
private $phids;
|
private $phids;
|
||||||
private $accountTypes;
|
|
||||||
private $accountDomains;
|
|
||||||
private $accountIDs;
|
private $accountIDs;
|
||||||
private $userPHIDs;
|
private $userPHIDs;
|
||||||
private $needImages;
|
private $needImages;
|
||||||
|
@ -34,16 +32,6 @@ final class PhabricatorExternalAccountQuery
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function withAccountDomains(array $account_domains) {
|
|
||||||
$this->accountDomains = $account_domains;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function withAccountTypes(array $account_types) {
|
|
||||||
$this->accountTypes = $account_types;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function withPHIDs(array $phids) {
|
public function withPHIDs(array $phids) {
|
||||||
$this->phids = $phids;
|
$this->phids = $phids;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -175,20 +163,6 @@ final class PhabricatorExternalAccountQuery
|
||||||
$this->phids);
|
$this->phids);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->accountTypes !== null) {
|
|
||||||
$where[] = qsprintf(
|
|
||||||
$conn,
|
|
||||||
'accountType IN (%Ls)',
|
|
||||||
$this->accountTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->accountDomains !== null) {
|
|
||||||
$where[] = qsprintf(
|
|
||||||
$conn,
|
|
||||||
'accountDomain IN (%Ls)',
|
|
||||||
$this->accountDomains);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->accountIDs !== null) {
|
if ($this->accountIDs !== null) {
|
||||||
$where[] = qsprintf(
|
$where[] = qsprintf(
|
||||||
$conn,
|
$conn,
|
||||||
|
|
|
@ -35,8 +35,10 @@ final class DoorkeeperBridgeAsana extends DoorkeeperBridge {
|
||||||
$accounts = id(new PhabricatorExternalAccountQuery())
|
$accounts = id(new PhabricatorExternalAccountQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withUserPHIDs(array($viewer->getPHID()))
|
->withUserPHIDs(array($viewer->getPHID()))
|
||||||
->withAccountTypes(array($provider->getProviderType()))
|
->withProviderConfigPHIDs(
|
||||||
->withAccountDomains(array($provider->getProviderDomain()))
|
array(
|
||||||
|
$provider->getProviderConfigPHID(),
|
||||||
|
))
|
||||||
->requireCapabilities(
|
->requireCapabilities(
|
||||||
array(
|
array(
|
||||||
PhabricatorPolicyCapability::CAN_VIEW,
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
|
|
@ -30,7 +30,10 @@ final class DoorkeeperBridgeJIRA extends DoorkeeperBridge {
|
||||||
$accounts = id(new PhabricatorExternalAccountQuery())
|
$accounts = id(new PhabricatorExternalAccountQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withUserPHIDs(array($viewer->getPHID()))
|
->withUserPHIDs(array($viewer->getPHID()))
|
||||||
->withAccountTypes(array($provider->getProviderType()))
|
->withProviderConfigPHIDs(
|
||||||
|
array(
|
||||||
|
$provider->getProviderConfigPHID(),
|
||||||
|
))
|
||||||
->requireCapabilities(
|
->requireCapabilities(
|
||||||
array(
|
array(
|
||||||
PhabricatorPolicyCapability::CAN_VIEW,
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
|
|
@ -65,8 +65,10 @@ final class PhabricatorAsanaConfigOptions
|
||||||
$account = id(new PhabricatorExternalAccountQuery())
|
$account = id(new PhabricatorExternalAccountQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withUserPHIDs(array($viewer->getPHID()))
|
->withUserPHIDs(array($viewer->getPHID()))
|
||||||
->withAccountTypes(array($provider->getProviderType()))
|
->withProviderConfigPHIDs(
|
||||||
->withAccountDomains(array($provider->getProviderDomain()))
|
array(
|
||||||
|
$provider->getProviderConfigPHID(),
|
||||||
|
))
|
||||||
->requireCapabilities(
|
->requireCapabilities(
|
||||||
array(
|
array(
|
||||||
PhabricatorPolicyCapability::CAN_VIEW,
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
|
|
@ -525,20 +525,7 @@ final class DoorkeeperAsanaFeedWorker extends DoorkeeperFeedWorker {
|
||||||
return $phid_map;
|
return $phid_map;
|
||||||
}
|
}
|
||||||
|
|
||||||
$provider = PhabricatorAsanaAuthProvider::getAsanaProvider();
|
$accounts = $this->loadAsanaExternalAccounts($all_phids);
|
||||||
|
|
||||||
$accounts = id(new PhabricatorExternalAccountQuery())
|
|
||||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
|
||||||
->withUserPHIDs($all_phids)
|
|
||||||
->withAccountTypes(array($provider->getProviderType()))
|
|
||||||
->withAccountDomains(array($provider->getProviderDomain()))
|
|
||||||
->needAccountIdentifiers(true)
|
|
||||||
->requireCapabilities(
|
|
||||||
array(
|
|
||||||
PhabricatorPolicyCapability::CAN_VIEW,
|
|
||||||
PhabricatorPolicyCapability::CAN_EDIT,
|
|
||||||
))
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
foreach ($accounts as $account) {
|
foreach ($accounts as $account) {
|
||||||
$phid_map[$account->getUserPHID()] = $this->getAsanaAccountID($account);
|
$phid_map[$account->getUserPHID()] = $this->getAsanaAccountID($account);
|
||||||
|
@ -550,19 +537,21 @@ final class DoorkeeperAsanaFeedWorker extends DoorkeeperFeedWorker {
|
||||||
return $phid_map;
|
return $phid_map;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function findAnyValidAsanaAccessToken(array $user_phids) {
|
private function loadAsanaExternalAccounts(array $user_phids) {
|
||||||
if (!$user_phids) {
|
|
||||||
return array(null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
$provider = $this->getProvider();
|
$provider = $this->getProvider();
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
if (!$user_phids) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
$accounts = id(new PhabricatorExternalAccountQuery())
|
$accounts = id(new PhabricatorExternalAccountQuery())
|
||||||
->setViewer($viewer)
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
->withUserPHIDs($user_phids)
|
->withUserPHIDs($user_phids)
|
||||||
->withAccountTypes(array($provider->getProviderType()))
|
->withProviderConfigPHIDs(
|
||||||
->withAccountDomains(array($provider->getProviderDomain()))
|
array(
|
||||||
|
$provider->getProviderConfigPHID(),
|
||||||
|
))
|
||||||
->needAccountIdentifiers(true)
|
->needAccountIdentifiers(true)
|
||||||
->requireCapabilities(
|
->requireCapabilities(
|
||||||
array(
|
array(
|
||||||
|
@ -571,6 +560,19 @@ final class DoorkeeperAsanaFeedWorker extends DoorkeeperFeedWorker {
|
||||||
))
|
))
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
|
return $accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findAnyValidAsanaAccessToken(array $user_phids) {
|
||||||
|
$provider = $this->getProvider();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
if (!$user_phids) {
|
||||||
|
return array(null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
$accounts = $this->loadAsanaExternalAccounts($user_phids);
|
||||||
|
|
||||||
// Reorder accounts in the original order.
|
// Reorder accounts in the original order.
|
||||||
// TODO: This needs to be adjusted if/when we allow you to link multiple
|
// TODO: This needs to be adjusted if/when we allow you to link multiple
|
||||||
// accounts.
|
// accounts.
|
||||||
|
|
|
@ -74,14 +74,17 @@ final class DoorkeeperJIRAFeedWorker extends DoorkeeperFeedWorker {
|
||||||
$accounts = id(new PhabricatorExternalAccountQuery())
|
$accounts = id(new PhabricatorExternalAccountQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withUserPHIDs($try_users)
|
->withUserPHIDs($try_users)
|
||||||
->withAccountTypes(array($provider->getProviderType()))
|
->withProviderConfigPHIDs(
|
||||||
->withAccountDomains(array($domain))
|
array(
|
||||||
|
$provider->getProviderConfigPHID(),
|
||||||
|
))
|
||||||
->requireCapabilities(
|
->requireCapabilities(
|
||||||
array(
|
array(
|
||||||
PhabricatorPolicyCapability::CAN_VIEW,
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
PhabricatorPolicyCapability::CAN_EDIT,
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
))
|
))
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
// Reorder accounts in the original order.
|
// Reorder accounts in the original order.
|
||||||
// TODO: This needs to be adjusted if/when we allow you to link multiple
|
// TODO: This needs to be adjusted if/when we allow you to link multiple
|
||||||
// accounts.
|
// accounts.
|
||||||
|
|
|
@ -7,10 +7,7 @@ final class PhabricatorExternalAccount
|
||||||
PhabricatorDestructibleInterface {
|
PhabricatorDestructibleInterface {
|
||||||
|
|
||||||
protected $userPHID;
|
protected $userPHID;
|
||||||
protected $accountType;
|
|
||||||
protected $accountDomain;
|
|
||||||
protected $accountSecret;
|
protected $accountSecret;
|
||||||
protected $accountID;
|
|
||||||
protected $displayName;
|
protected $displayName;
|
||||||
protected $username;
|
protected $username;
|
||||||
protected $realName;
|
protected $realName;
|
||||||
|
@ -21,6 +18,12 @@ final class PhabricatorExternalAccount
|
||||||
protected $properties = array();
|
protected $properties = array();
|
||||||
protected $providerConfigPHID;
|
protected $providerConfigPHID;
|
||||||
|
|
||||||
|
// TODO: Remove these (see T13493). These columns are obsolete and have
|
||||||
|
// no readers and only trivial writers.
|
||||||
|
protected $accountType;
|
||||||
|
protected $accountDomain;
|
||||||
|
protected $accountID;
|
||||||
|
|
||||||
private $profileImageFile = self::ATTACHABLE;
|
private $profileImageFile = self::ATTACHABLE;
|
||||||
private $providerConfig = self::ATTACHABLE;
|
private $providerConfig = self::ATTACHABLE;
|
||||||
private $accountIdentifiers = self::ATTACHABLE;
|
private $accountIdentifiers = self::ATTACHABLE;
|
||||||
|
@ -60,21 +63,16 @@ final class PhabricatorExternalAccount
|
||||||
'accountURI' => 'text255?',
|
'accountURI' => 'text255?',
|
||||||
),
|
),
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
self::CONFIG_KEY_SCHEMA => array(
|
||||||
'account_details' => array(
|
|
||||||
'columns' => array('accountType', 'accountDomain', 'accountID'),
|
|
||||||
'unique' => true,
|
|
||||||
),
|
|
||||||
'key_user' => array(
|
'key_user' => array(
|
||||||
'columns' => array('userPHID'),
|
'columns' => array('userPHID'),
|
||||||
),
|
),
|
||||||
|
'key_provider' => array(
|
||||||
|
'columns' => array('providerConfigPHID', 'userPHID'),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
) + parent::getConfiguration();
|
) + parent::getConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getProviderKey() {
|
|
||||||
return $this->getAccountType().':'.$this->getAccountDomain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function save() {
|
public function save() {
|
||||||
if (!$this->getAccountSecret()) {
|
if (!$this->getAccountSecret()) {
|
||||||
$this->setAccountSecret(Filesystem::readRandomCharacters(32));
|
$this->setAccountSecret(Filesystem::readRandomCharacters(32));
|
||||||
|
@ -146,24 +144,6 @@ final class PhabricatorExternalAccount
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDisplayName() {
|
|
||||||
if (strlen($this->displayName)) {
|
|
||||||
return $this->displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Figure out how much identifying information we're going to show
|
|
||||||
// to users about external accounts. For now, just show a string which is
|
|
||||||
// clearly not an error, but don't disclose any identifying information.
|
|
||||||
|
|
||||||
$map = array(
|
|
||||||
'email' => pht('Email User'),
|
|
||||||
);
|
|
||||||
|
|
||||||
$type = $this->getAccountType();
|
|
||||||
|
|
||||||
return idx($map, $type, pht('"%s" User', $type));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function attachProviderConfig(PhabricatorAuthProviderConfig $config) {
|
public function attachProviderConfig(PhabricatorAuthProviderConfig $config) {
|
||||||
$this->providerConfig = $config;
|
$this->providerConfig = $config;
|
||||||
return $this;
|
return $this;
|
||||||
|
|
Loading…
Reference in a new issue