[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/auth/provider/ -> PhabricatorAuthProvider.php (source)

   1  <?php
   2  
   3  abstract class PhabricatorAuthProvider {
   4  
   5    private $providerConfig;
   6  
   7    public function attachProviderConfig(PhabricatorAuthProviderConfig $config) {
   8      $this->providerConfig = $config;
   9      return $this;
  10    }
  11  
  12    public function hasProviderConfig() {
  13      return (bool)$this->providerConfig;
  14    }
  15  
  16    public function getProviderConfig() {
  17      if ($this->providerConfig === null) {
  18        throw new Exception(
  19          'Call attachProviderConfig() before getProviderConfig()!');
  20      }
  21      return $this->providerConfig;
  22    }
  23  
  24    public function getConfigurationHelp() {
  25      return null;
  26    }
  27  
  28    public function getDefaultProviderConfig() {
  29      return id(new PhabricatorAuthProviderConfig())
  30        ->setProviderClass(get_class($this))
  31        ->setIsEnabled(1)
  32        ->setShouldAllowLogin(1)
  33        ->setShouldAllowRegistration(1)
  34        ->setShouldAllowLink(1)
  35        ->setShouldAllowUnlink(1);
  36    }
  37  
  38    public function getNameForCreate() {
  39      return $this->getProviderName();
  40    }
  41  
  42    public function getDescriptionForCreate() {
  43      return null;
  44    }
  45  
  46    public function getProviderKey() {
  47      return $this->getAdapter()->getAdapterKey();
  48    }
  49  
  50    public function getProviderType() {
  51      return $this->getAdapter()->getAdapterType();
  52    }
  53  
  54    public function getProviderDomain() {
  55      return $this->getAdapter()->getAdapterDomain();
  56    }
  57  
  58    public static function getAllBaseProviders() {
  59      static $providers;
  60  
  61      if ($providers === null) {
  62        $objects = id(new PhutilSymbolLoader())
  63          ->setAncestorClass(__CLASS__)
  64          ->loadObjects();
  65        $providers = $objects;
  66      }
  67  
  68      return $providers;
  69    }
  70  
  71    public static function getAllProviders() {
  72      static $providers;
  73  
  74      if ($providers === null) {
  75        $objects = self::getAllBaseProviders();
  76  
  77        $configs = id(new PhabricatorAuthProviderConfigQuery())
  78          ->setViewer(PhabricatorUser::getOmnipotentUser())
  79          ->execute();
  80  
  81        $providers = array();
  82        foreach ($configs as $config) {
  83          if (!isset($objects[$config->getProviderClass()])) {
  84            // This configuration is for a provider which is not installed.
  85            continue;
  86          }
  87  
  88          $object = clone $objects[$config->getProviderClass()];
  89          $object->attachProviderConfig($config);
  90  
  91          $key = $object->getProviderKey();
  92          if (isset($providers[$key])) {
  93            throw new Exception(
  94              pht(
  95                "Two authentication providers use the same provider key ".
  96                "('%s'). Each provider must be identified by a unique key.",
  97                $key));
  98          }
  99          $providers[$key] = $object;
 100        }
 101      }
 102  
 103      return $providers;
 104    }
 105  
 106    public static function getAllEnabledProviders() {
 107      $providers = self::getAllProviders();
 108      foreach ($providers as $key => $provider) {
 109        if (!$provider->isEnabled()) {
 110          unset($providers[$key]);
 111        }
 112      }
 113      return $providers;
 114    }
 115  
 116    public static function getEnabledProviderByKey($provider_key) {
 117      return idx(self::getAllEnabledProviders(), $provider_key);
 118    }
 119  
 120    abstract public function getProviderName();
 121    abstract public function getAdapter();
 122  
 123    public function isEnabled() {
 124      return $this->getProviderConfig()->getIsEnabled();
 125    }
 126  
 127    public function shouldAllowLogin() {
 128      return $this->getProviderConfig()->getShouldAllowLogin();
 129    }
 130  
 131    public function shouldAllowRegistration() {
 132      return $this->getProviderConfig()->getShouldAllowRegistration();
 133    }
 134  
 135    public function shouldAllowAccountLink() {
 136      return $this->getProviderConfig()->getShouldAllowLink();
 137    }
 138  
 139    public function shouldAllowAccountUnlink() {
 140      return $this->getProviderConfig()->getShouldAllowUnlink();
 141    }
 142  
 143    public function shouldTrustEmails() {
 144      return $this->shouldAllowEmailTrustConfiguration() &&
 145             $this->getProviderConfig()->getShouldTrustEmails();
 146    }
 147  
 148    /**
 149     * Should we allow the adapter to be marked as "trusted". This is true for
 150     * all adapters except those that allow the user to type in emails (see
 151     * @{class:PhabricatorPasswordAuthProvider}).
 152     */
 153    public function shouldAllowEmailTrustConfiguration() {
 154      return true;
 155    }
 156  
 157    public function buildLoginForm(PhabricatorAuthStartController $controller) {
 158      return $this->renderLoginForm($controller->getRequest(), $mode = 'start');
 159    }
 160  
 161    abstract public function processLoginRequest(
 162      PhabricatorAuthLoginController $controller);
 163  
 164    public function buildLinkForm(PhabricatorAuthLinkController $controller) {
 165      return $this->renderLoginForm($controller->getRequest(), $mode = 'link');
 166    }
 167  
 168    public function shouldAllowAccountRefresh() {
 169      return true;
 170    }
 171  
 172    public function buildRefreshForm(
 173      PhabricatorAuthLinkController $controller) {
 174      return $this->renderLoginForm($controller->getRequest(), $mode = 'refresh');
 175    }
 176  
 177    protected function renderLoginForm(AphrontRequest $request, $mode) {
 178      throw new PhutilMethodNotImplementedException();
 179    }
 180  
 181    public function createProviders() {
 182      return array($this);
 183    }
 184  
 185    protected function willSaveAccount(PhabricatorExternalAccount $account) {
 186      return;
 187    }
 188  
 189    public function willRegisterAccount(PhabricatorExternalAccount $account) {
 190      return;
 191    }
 192  
 193    protected function loadOrCreateAccount($account_id) {
 194      if (!strlen($account_id)) {
 195        throw new Exception('loadOrCreateAccount(...): empty account ID!');
 196      }
 197  
 198      $adapter = $this->getAdapter();
 199      $adapter_class = get_class($adapter);
 200  
 201      if (!strlen($adapter->getAdapterType())) {
 202        throw new Exception(
 203          "AuthAdapter (of class '{$adapter_class}') has an invalid ".
 204          "implementation: no adapter type.");
 205      }
 206  
 207      if (!strlen($adapter->getAdapterDomain())) {
 208        throw new Exception(
 209          "AuthAdapter (of class '{$adapter_class}') has an invalid ".
 210          "implementation: no adapter domain.");
 211      }
 212  
 213      $account = id(new PhabricatorExternalAccount())->loadOneWhere(
 214        'accountType = %s AND accountDomain = %s AND accountID = %s',
 215        $adapter->getAdapterType(),
 216        $adapter->getAdapterDomain(),
 217        $account_id);
 218      if (!$account) {
 219        $account = id(new PhabricatorExternalAccount())
 220          ->setAccountType($adapter->getAdapterType())
 221          ->setAccountDomain($adapter->getAdapterDomain())
 222          ->setAccountID($account_id);
 223      }
 224  
 225      $account->setUsername($adapter->getAccountName());
 226      $account->setRealName($adapter->getAccountRealName());
 227      $account->setEmail($adapter->getAccountEmail());
 228      $account->setAccountURI($adapter->getAccountURI());
 229  
 230      $account->setProfileImagePHID(null);
 231      $image_uri = $adapter->getAccountImageURI();
 232      if ($image_uri) {
 233        try {
 234          $name = PhabricatorSlug::normalize($this->getProviderName());
 235          $name = $name.'-profile.jpg';
 236  
 237          // TODO: If the image has not changed, we do not need to make a new
 238          // file entry for it, but there's no convenient way to do this with
 239          // PhabricatorFile right now. The storage will get shared, so the impact
 240          // here is negligible.
 241          $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
 242            $image_file = PhabricatorFile::newFromFileDownload(
 243              $image_uri,
 244              array(
 245                'name' => $name,
 246                'canCDN' => true,
 247              ));
 248          unset($unguarded);
 249  
 250          if ($image_file) {
 251            $account->setProfileImagePHID($image_file->getPHID());
 252          }
 253        } catch (Exception $ex) {
 254          // Log this but proceed, it's not especially important that we
 255          // be able to pull profile images.
 256          phlog($ex);
 257        }
 258      }
 259  
 260      $this->willSaveAccount($account);
 261  
 262      $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
 263      $account->save();
 264      unset($unguarded);
 265  
 266      return $account;
 267    }
 268  
 269    public function getLoginURI() {
 270      $app = PhabricatorApplication::getByClass('PhabricatorAuthApplication');
 271      return $app->getApplicationURI('/login/'.$this->getProviderKey().'/');
 272    }
 273  
 274    public function getSettingsURI() {
 275      return '/settings/panel/external/';
 276    }
 277  
 278    public function getStartURI() {
 279      $app = PhabricatorApplication::getByClass('PhabricatorAuthApplication');
 280      $uri = $app->getApplicationURI('/start/');
 281      return $uri;
 282    }
 283  
 284    public function isDefaultRegistrationProvider() {
 285      return false;
 286    }
 287  
 288    public function shouldRequireRegistrationPassword() {
 289      return false;
 290    }
 291  
 292    public function getDefaultExternalAccount() {
 293      throw new PhutilMethodNotImplementedException();
 294    }
 295  
 296    public function getLoginOrder() {
 297      return '500-'.$this->getProviderName();
 298    }
 299  
 300    protected function getLoginIcon() {
 301      return 'Generic';
 302    }
 303  
 304    public function isLoginFormAButton() {
 305      return false;
 306    }
 307  
 308    public function renderConfigPropertyTransactionTitle(
 309      PhabricatorAuthProviderConfigTransaction $xaction) {
 310  
 311      return null;
 312    }
 313  
 314    public function readFormValuesFromProvider() {
 315      return array();
 316    }
 317  
 318    public function readFormValuesFromRequest(AphrontRequest $request) {
 319      return array();
 320    }
 321  
 322    public function processEditForm(
 323      AphrontRequest $request,
 324      array $values) {
 325  
 326      $errors = array();
 327      $issues = array();
 328  
 329      return array($errors, $issues, $values);
 330    }
 331  
 332    public function extendEditForm(
 333      AphrontRequest $request,
 334      AphrontFormView $form,
 335      array $values,
 336      array $issues) {
 337  
 338      return;
 339    }
 340  
 341    public function willRenderLinkedAccount(
 342      PhabricatorUser $viewer,
 343      PHUIObjectItemView $item,
 344      PhabricatorExternalAccount $account) {
 345  
 346      $account_view = id(new PhabricatorAuthAccountView())
 347        ->setExternalAccount($account)
 348        ->setAuthProvider($this);
 349  
 350      $item->appendChild(
 351        phutil_tag(
 352          'div',
 353          array(
 354            'class' => 'mmr mml mst mmb',
 355          ),
 356          $account_view));
 357    }
 358  
 359    /**
 360     * Return true to use a two-step configuration (setup, configure) instead of
 361     * the default single-step configuration. In practice, this means that
 362     * creating a new provider instance will redirect back to the edit page
 363     * instead of the provider list.
 364     *
 365     * @return bool True if this provider uses two-step configuration.
 366     */
 367    public function hasSetupStep() {
 368      return false;
 369    }
 370  
 371    /**
 372     * Render a standard login/register button element.
 373     *
 374     * The `$attributes` parameter takes these keys:
 375     *
 376     *   - `uri`: URI the button should take the user to when clicked.
 377     *   - `method`: Optional HTTP method the button should use, defaults to GET.
 378     *
 379     * @param   AphrontRequest  HTTP request.
 380     * @param   string          Request mode string.
 381     * @param   map             Additional parameters, see above.
 382     * @return  wild            Login button.
 383     */
 384    protected function renderStandardLoginButton(
 385      AphrontRequest $request,
 386      $mode,
 387      array $attributes = array()) {
 388  
 389      PhutilTypeSpec::checkMap(
 390        $attributes,
 391        array(
 392          'method' => 'optional string',
 393          'uri' => 'string',
 394          'sigil' => 'optional string',
 395        ));
 396  
 397      $viewer = $request->getUser();
 398      $adapter = $this->getAdapter();
 399  
 400      if ($mode == 'link') {
 401        $button_text = pht('Link External Account');
 402      } else if ($mode == 'refresh') {
 403        $button_text = pht('Refresh Account Link');
 404      } else if ($this->shouldAllowRegistration()) {
 405        $button_text = pht('Login or Register');
 406      } else {
 407        $button_text = pht('Login');
 408      }
 409  
 410      $icon = id(new PHUIIconView())
 411        ->setSpriteSheet(PHUIIconView::SPRITE_LOGIN)
 412        ->setSpriteIcon($this->getLoginIcon());
 413  
 414      $button = id(new PHUIButtonView())
 415        ->setSize(PHUIButtonView::BIG)
 416        ->setColor(PHUIButtonView::GREY)
 417        ->setIcon($icon)
 418        ->setText($button_text)
 419        ->setSubtext($this->getProviderName());
 420  
 421      $uri = $attributes['uri'];
 422      $uri = new PhutilURI($uri);
 423      $params = $uri->getQueryParams();
 424      $uri->setQueryParams(array());
 425  
 426      $content = array($button);
 427  
 428      foreach ($params as $key => $value) {
 429        $content[] = phutil_tag(
 430          'input',
 431          array(
 432            'type' => 'hidden',
 433            'name' => $key,
 434            'value' => $value,
 435          ));
 436      }
 437  
 438      return phabricator_form(
 439        $viewer,
 440        array(
 441          'method' => idx($attributes, 'method', 'GET'),
 442          'action' => (string)$uri,
 443          'sigil'  => idx($attributes, 'sigil'),
 444        ),
 445        $content);
 446    }
 447  
 448    public function renderConfigurationFooter() {
 449      return null;
 450    }
 451  
 452    protected function getAuthCSRFCode(AphrontRequest $request) {
 453      $phcid = $request->getCookie(PhabricatorCookies::COOKIE_CLIENTID);
 454      if (!strlen($phcid)) {
 455        throw new Exception(
 456          pht(
 457            'Your browser did not submit a "%s" cookie with client state '.
 458            'information in the request. Check that cookies are enabled. '.
 459            'If this problem persists, you may need to clear your cookies.',
 460            PhabricatorCookies::COOKIE_CLIENTID));
 461      }
 462  
 463      return PhabricatorHash::digest($phcid);
 464    }
 465  
 466    protected function verifyAuthCSRFCode(AphrontRequest $request, $actual) {
 467      $expect = $this->getAuthCSRFCode($request);
 468  
 469      if (!strlen($actual)) {
 470        throw new Exception(
 471          pht(
 472            'The authentication provider did not return a client state '.
 473            'parameter in its response, but one was expected. If this '.
 474            'problem persists, you may need to clear your cookies.'));
 475      }
 476  
 477      if ($actual !== $expect) {
 478        throw new Exception(
 479          pht(
 480            'The authentication provider did not return the correct client '.
 481            'state parameter in its response. If this problem persists, you may '.
 482            'need to clear your cookies.'));
 483      }
 484    }
 485  
 486  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1