diff --git a/src/extensions/PhabricatorAuthProviderShibboleth.php b/src/extensions/PhabricatorAuthProviderShibboleth.php index 1ff135632..94158bba3 100644 --- a/src/extensions/PhabricatorAuthProviderShibboleth.php +++ b/src/extensions/PhabricatorAuthProviderShibboleth.php @@ -1,536 +1,602 @@ adapter) { $conf = $this->getProviderConfig(); $adapter = id(new PhutilAuthAdapterShibboleth()) ->setShibSessionIdField( $conf->getProperty(self::KEY_SHIB_SESSION_ID_FIELD)) ->setShibApplicationIdField( $conf->getProperty(self::KEY_SHIB_APPLICATION_ID_FIELD)) ->setUseridField( $conf->getProperty(self::KEY_USERID_FIELD)) ->setUsernameField( $conf->getProperty(self::KEY_USERNAME_FIELD)) ->setRealnameField( $conf->getProperty(self::KEY_REALNAME_FIELD)) ->setFirstnameField( $conf->getProperty(self::KEY_FIRSTNAME_FIELD)) ->setLastnameField( $conf->getProperty(self::KEY_LASTNAME_FIELD)) ->setEmailField( $conf->getProperty(self::KEY_EMAIL_FIELD)) ->setPageURIPattern( $conf->getProperty(self::KEY_PAGE_URI_PATTERN)) ->setImageURIPattern( $conf->getProperty(self::KEY_IMAGE_URI_PATTERN)) ->setAddUserToProject( $conf->getProperty(self::KEY_ADD_USER_TO_PROJECT)) ->setUserProject( $conf->getProperty(self::KEY_USER_PROJECT)) ->setIsGeneratedUsername( $conf->getProperty(self::KEY_USERNAME_FROM_REALNAME)) ->setOrgField( $conf->getProperty(self::KEY_ORG_FIELD)) ->setOrgCustomField( $conf->getProperty(self::KEY_ORG_CUSTOM_FIELD)) ->setOrgTypeField( $conf->getProperty(self::KEY_ORG_TYPE_FIELD)); $this->adapter = $adapter; } return $this->adapter; } protected function renderLoginForm(AphrontRequest $request, $mode) { $viewer = $request->getUser(); $dialog = id(new AphrontDialogView()) ->setSubmitURI($this->getLoginURI()) ->setUser($viewer) ->setTitle(pht('Login for Swiss Universities')) ->addSubmitButton(pht('Login or Register')); $remarkup = new PHUIRemarkupView($viewer, pht(<<addPadding(PHUI::PADDING_LARGE) ->appendChild($remarkup); $dialog->appendChild($frame); $static_response = CelerityAPI::getStaticResourceResponse(); - $static_response->addContentSecurityPolicyURI( - 'form-action', 'https://idp.epfl.ch'); - $static_response->addContentSecurityPolicyURI( - 'form-action', 'https://wayf.switch.ch'); - $static_response->addContentSecurityPolicyURI( - 'form-action', 'https://aai-idp.unibe.ch'); + + $static_response->addContentSecurityPolicyURI('form-action', 'https://wayf.switch.ch'); + + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.balgrist.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.bfh.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.careum.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://idp.chuv.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai.eawag.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://eduid.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.ehb-schweiz.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.ehsm.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai.empa.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://idp.epfl.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.ethz.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.euresearch.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://idp.fernuni.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://idp.ffhs.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.fh-hwz.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.fhgr.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.fhnw.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'http://adfs3.fmi.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://idp.graduateinstitute.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai.hcuge.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.hep-bejune.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-loginhep.edufr.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.hepl.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.hepvs.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.hes-so.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.hfh.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai.hftm.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://idp.hslu.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.hsr.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai.idiap.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai.insel.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://login-idp.libraries.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai.ntb.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.ost.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.ph-gr.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.phbern.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://idp.phlu.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.phsg.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://idp.phsz.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.phtg.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai.phzg.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai01.phzh.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.pmodwrc.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.psi.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.slsp.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.snf.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://login2.supsi.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.swissuniversities.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.switch.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://login.teologialugano.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.uni.li'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.unibas.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-idp.unibe.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai.unifr.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://idp.unige.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai.unil.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.unilu.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-login.unine.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai.unisg.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://login2.usi.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.unispital.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-idp.uzh.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.vho-switchaai.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.wsl.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai.zhaw.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.zhbluzern.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://aai-logon.zhdk.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'https://cern.ch'); + $static_response->addContentSecurityPolicyURI('form-action', 'http://www.w3.org'); return $dialog; } public function isLoginFormAButton() { return false; } private function populateAdapter() { $adapter = $this->getAdapter(); $env = array(); $env_names = $adapter->getEnvNames(); foreach ($env_names as $h) { if(array_key_exists($h, $_SERVER)) { $env[$h] = $_SERVER[$h]; } } return $adapter->setUserDataFromRequest($env); } public function processLoginRequest( PhabricatorAuthLoginController $controller) { $adapter = $this->getAdapter(); $request = $controller->getRequest(); $response = null; $account = null; if (!$this->populateAdapter()) { $response = $controller->buildProviderPageResponse( $this, id(new PHUIInfoView()) ->setErrors(array(pht('Invalid Shibboleth session.'))) ->addButton(id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Return to home')) ->setHref('/') ) ); return array($account, $response); } $account_id = $adapter->getAccountID(); return array($this->loadOrCreateAccount_custom($account_id, $request), $response); } private function updateOrganization($userPHID, $adapter) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); if($adapter == null){ $adapter = $this->getAdapter(); } if(strlen($adapter->getOrg()) && strlen($adapter->getOrgType()) && strlen($adapter->getOrgCustom()) ) { $user = id(new PhabricatorUser()) ->loadOneWhere('phid = %s', $userPHID); $admin = id(new PhabricatorUser()) ->loadOneWhere('username = %s', 'admin'); $field_list = PhabricatorCustomField::getObjectFields( $user, PhabricatorCustomField::ROLE_VIEW); $field_list->readFieldsFromStorage($user); $orgvalue = null; foreach($field_list->getFields() as $field){ try { if($field->getFieldKey()=='std:user:' . $adapter->getOrgCustom()){ $orgvalue = $field->getValueForStorage(); break; } } catch (Exception $e) { } } // Create projects for organization $org = $adapter->getOrg(); $proj = id(new PhabricatorProjectQuery()) ->setViewer($user) ->withNames(array($org)) ->executeOne(); // Create the project if(!$proj) { $proj = PhabricatorProject::initializeNewProject($user) ->setViewPolicy('users') ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN); $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) ->setMetadataValue('c4s:hide', true) ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($org); $xactions[] = id(new PhabricatorProjectTransaction()) ->setMetadataValue('c4s:hide', true) ->setTransactionType(PhabricatorProjectIconTransaction::TRANSACTIONTYPE) ->setNewValue('organization'); $xactions[] = id(new PhabricatorProjectTransaction()) ->setMetadataValue('c4s:hide', true) ->setTransactionType(PhabricatorProjectStatusTransaction::TRANSACTIONTYPE) ->setNewValue(PhabricatorProjectStatus::STATUS_ACTIVE); $editor = id(new PhabricatorProjectTransactionEditor()) ->setActor($admin) ->setContinueOnNoEffect(true) ->setContentSource(PhabricatorContentSource::newForSource('web')) ->applyTransactions($proj, $xactions); } // Add the user to the project $xactions = array(); $type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; $xactions[] = id(new PhabricatorProjectTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $type_member) ->setMetadataValue('c4s:hide', true) ->setNewValue(array('+' => array($userPHID => $userPHID))); $editor = id(new PhabricatorProjectTransactionEditor()) ->setActor($admin) ->setContentSource(PhabricatorContentSource::newForSource('web')) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) ->applyTransactions($proj, $xactions); // Add organization to custom field in user profile if(empty($orgvalue)) { $xactions = array(); $xactions[] = id(new PhabricatorUserTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) ->setMetadataValue('customfield:key', 'std:user:' . $adapter->getOrgCustom()) ->setOldValue('') ->setNewValue($adapter->getOrg() . ' (' . $adapter->getOrgType() . ')'); $xactions[] = id(new PhabricatorUserTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) ->setMetadataValue('customfield:key', 'user:title') ->setOldValue('') ->setNewValue(' '); $xactions[] = id(new PhabricatorUserTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) ->setMetadataValue('customfield:key', 'user:blurb') ->setOldValue('') ->setNewValue(' '); id(new PhabricatorUserTransactionEditor()) ->setActor($user) ->setContentSource(PhabricatorContentSource::newForSource('console')) ->setContinueOnNoEffect(true) ->applyTransactions($user, $xactions); } } unset($unguarded); } private function updateProjects($userPHID, $adapter) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); if($adapter == null){ $adapter = $this->getAdapter(); } $projects = $adapter->getUserProject(); if(count($projects)) { $admin = id(new PhabricatorUser()) ->loadOneWhere('username = %s', 'admin'); $user_projects = id(new PhabricatorProjectQuery()) ->setViewer($admin) ->withMemberPHIDs(array($userPHID)) ->withPHIDs($projects) ->execute(); $check_proj = array(); foreach($user_projects as $r){ $check_proj[] = $r->getPHID(); } $results = id(new PhabricatorProjectQuery()) ->setViewer($admin) ->withPHIDs($projects) ->execute(); // Add user to project foreach($results as $project) { if(in_array($project->getPHID(), $check_proj)){ continue; } $edge_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $edge_type) ->setMetadataValue('c4s:hide', true) ->setNewValue(array('+' => array($userPHID => $userPHID))); $editor = id(new PhabricatorProjectTransactionEditor()) ->setActor($admin) ->setContentSource(PhabricatorContentSource::newForSource('web')) ->setContinueOnNoEffect(true) ->applyTransactions($project, $xactions); } } unset($unguarded); } protected function loadOrCreateAccount_custom($account_id, $request) { $account = parent::loadOrCreateAccount($account_id); $adapter = $this->getAdapter(); if(!$adapter) { return $account; } if(!$account->getUserPHID()){ if ($account instanceof PhabricatorExternalAccount && $request->getViewer()->getPHID()) { $this->updateProjects($request->getViewer()->getPHID(), $adapter); } // User account not yet created return $account; } $this->updateOrganization($account->getUserPHID(), $adapter); $this->updateProjects($account->getUserPHID(), $adapter); return $account; } public function willRegisterAccount(PhabricatorExternalAccount $account) { parent::willRegisterAccount($account); //$this->updateOrganization($account); // no shib env variables here :/ $this->updateProjects($account->getUserPHID(), null); } const KEY_SHIB_SESSION_ID_FIELD = 'shibboleth:session_id_field'; const KEY_SHIB_APPLICATION_ID_FIELD = 'shibboleth:application_id_field'; const KEY_USERID_FIELD = 'shibboleth:userid_field'; const KEY_USERNAME_FIELD = 'shibboleth:username_field'; const KEY_REALNAME_FIELD = 'shibboleth:realname_field'; const KEY_FIRSTNAME_FIELD = 'shibboleth:firstname_field'; const KEY_LASTNAME_FIELD = 'shibboleth:lastname_field'; const KEY_EMAIL_FIELD = 'shibboleth:email_field'; const KEY_ORG_FIELD = 'shibboleth:org'; const KEY_ORG_TYPE_FIELD = 'shibboleth:org_type'; const KEY_ORG_CUSTOM_FIELD = 'shibboleth:org_custom'; const KEY_PAGE_URI_PATTERN = 'shibboleth:page_uri_pattern'; const KEY_IMAGE_URI_PATTERN = 'shibboleth:image_uri_pattern'; const KEY_USERNAME_FROM_REALNAME = 'shibboleth:username_from_realname'; const KEY_ADD_USER_TO_PROJECT = 'shibboleth:add_user_to_project'; const KEY_USER_PROJECT = 'shibboleth:user_project'; private function getPropertyKeys() { return array( self::KEY_SHIB_SESSION_ID_FIELD, self::KEY_SHIB_APPLICATION_ID_FIELD, self::KEY_USERID_FIELD, self::KEY_USERNAME_FIELD, self::KEY_REALNAME_FIELD, self::KEY_FIRSTNAME_FIELD, self::KEY_LASTNAME_FIELD, self::KEY_EMAIL_FIELD, self::KEY_ORG_FIELD, self::KEY_ORG_CUSTOM_FIELD, self::KEY_ORG_TYPE_FIELD, self::KEY_PAGE_URI_PATTERN, self::KEY_IMAGE_URI_PATTERN, self::KEY_USERNAME_FROM_REALNAME, self::KEY_ADD_USER_TO_PROJECT, self::KEY_USER_PROJECT ); } private function getPropertyLabels() { return array( self::KEY_SHIB_SESSION_ID_FIELD => pht('Session ID'), self::KEY_SHIB_APPLICATION_ID_FIELD => pht('Application ID'), self::KEY_USERID_FIELD => pht('User ID'), self::KEY_USERNAME_FIELD => pht('Username'), self::KEY_REALNAME_FIELD => pht('Real name'), self::KEY_FIRSTNAME_FIELD => pht('Firstname'), self::KEY_LASTNAME_FIELD => pht('Lastname'), self::KEY_EMAIL_FIELD => pht('User emailname'), self::KEY_ORG_FIELD => pht('Organization'), self::KEY_ORG_CUSTOM_FIELD => pht('Organization custom field'), self::KEY_ORG_TYPE_FIELD => pht('Organization type'), self::KEY_PAGE_URI_PATTERN => pht('User page URI pattern'), self::KEY_IMAGE_URI_PATTERN => pht('User image URI pattern'), ); } public function readFormValuesFromProvider() { $properties = array(); foreach ($this->getPropertyKeys() as $key) { $properties[$key] = $this->getProviderConfig()->getProperty($key); } return $properties; } public function readFormValuesFromRequest(AphrontRequest $request) { $values = array(); foreach ($this->getPropertyKeys() as $key) { if($key == self::KEY_USER_PROJECT) { $values[$key] = $request->getArr($key); } else { $values[$key] = $request->getStr($key); } } return $values; } public function processEditForm( AphrontRequest $request, array $values) { $errors = array(); $issues = array(); return array($errors, $issues, $values); } public function extendEditForm( AphrontRequest $request, AphrontFormView $form, array $values, array $issues) { $labels = $this->getPropertyLabels(); $captions = array( self::KEY_SHIB_SESSION_ID_FIELD => pht('Shibboleth Session ID, e.g.: Shib-Session-ID'), self::KEY_SHIB_APPLICATION_ID_FIELD => pht('Shibboleth application id, e.g.: Shib-Application-ID'), self::KEY_USERID_FIELD => pht('Unique user id for internal Phabricator use. e.g.: uniqueID'), self::KEY_USERNAME_FIELD => pht('Visible username, can be left empty if you choose to autogenerate it. e.g.: username'), self::KEY_REALNAME_FIELD => pht('Visible in the user profile. e.g.: displayName'), self::KEY_FIRSTNAME_FIELD => pht('Use this only when you autogenerate username. e.g.: givenName'), self::KEY_LASTNAME_FIELD => pht('Use this only when you autogenerate username. e.g.: surname'), self::KEY_EMAIL_FIELD => pht('Unique email address. e.g.: email'), self::KEY_ORG_FIELD => pht('Organization name. e.g.: homeOrganization'), self::KEY_ORG_CUSTOM_FIELD => pht('Organization name custom field in Phabricator. e.g.: mycompany:org'), self::KEY_ORG_TYPE_FIELD => pht('Organization type. e.g.: homeOrganizationType'), self::KEY_PAGE_URI_PATTERN => pht('URI pattern to a user pag. Add %%s for replacement with the username'), self::KEY_IMAGE_URI_PATTERN => pht('URI pattern to an image for the user. Add %%s for replacement with the username'), self::KEY_USER_PROJECT => pht('Project ID to add the user to. e.g.: PHID-PROJ-itwdg3fgxutsrdnqjklb'), ); // Text fields foreach ($labels as $key => $label) { $caption = idx($captions, $key); $value = idx($values, $key); $control = null; $control = id(new AphrontFormTextControl()) ->setName($key) ->setLabel($label) ->setCaption($caption) ->setValue($value); $form->appendChild($control); } // Add to project $form->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( self::KEY_ADD_USER_TO_PROJECT, 1, hsprintf('%s: %s', "Add to default Project", "Automatically add the new user to a default project"), idx($values, self::KEY_ADD_USER_TO_PROJECT)) ); $projects = idx($values, self::KEY_USER_PROJECT, array()); $viewer = $request->getViewer(); $form->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) ->setName(self::KEY_USER_PROJECT) ->setValue($projects) ->setUser($viewer) //->setDisabled(!idx($values, self::KEY_ADD_USER_TO_PROJECT)) ->setDatasource(new PhabricatorProjectDatasource()) ); // Generate username $form->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( self::KEY_USERNAME_FROM_REALNAME, 1, hsprintf('%s: %s', "Generated username", "Create a unique username from the surname and firstname which complies with Phabricator policies."), idx($values, self::KEY_USERNAME_FROM_REALNAME)) ); } public function renderConfigPropertyTransactionTitle( PhabricatorAuthProviderConfigTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $key = $xaction->getMetadataValue( PhabricatorAuthProviderConfigTransaction::PROPERTY_KEY); $labels = $this->getPropertyLabels(); if (isset($labels[$key])) { $label = $labels[$key]; if (!strlen($old)) { return pht( '%s set the "%s" value to "%s".', $xaction->renderHandleLink($author_phid), $label, $new); } else { return pht( '%s changed the "%s" value from "%s" to "%s".', $xaction->renderHandleLink($author_phid), $label, $old, $new); } } return parent::renderConfigPropertyTransactionTitle($xaction); } public static function getShibbolethProvider() { $providers = self::getAllEnabledProviders(); foreach ($providers as $provider) { if ($provider instanceof PhabricatorAuthProviderShibboleth) { return $provider; } } return null; } }