Pull to refresh

Zend Framework: подключаем OpenID

Reading time 8 min
Views 2.1K
В своем проекте (Вопросы и ответы для программистов) на Zend Framework мне стало необходимо подключить OpenID и после часа работы я успешно подключил стандартный зендовский сервис. Думаю класс, как легко и удобно(как и все в зенде), но как оказалось этот сервис не работает с OpenId 2.0, да — он просто не дописан.

Немного порывшись в исходниках я это подтвердил — Consumer.php * todo OpenID 2.0 (7.3) XRI and Yadis discovery
Потом посмотрел по багтрекеру и оказалось что это весит уже давно(очень) и никто не спешит доделывать. Тогда я и начал искать альтернативу. Выбор попал на openidenabled.com/php-openid.

Далее приведу пример, который позволит тем кто еще только собирается подключать сделать это минут за 15.

Качаем библиотеку с openidenabled.com, подключаем ее в php include path, или вручную, кому как удобно.

Что бы не делать то что уже сделано до меня, я взял за основу компонент из фреймфорка CakePhp.
Ниже код модифицированного под мои нужны компонента.

<?php

class OpenidComponent {
  private $controller = null;

  public function __construct($controller) {
    $this->controller = $controller;
    define('Auth_Yadis_CURL_OVERRIDE',true);
  }

  public function authenticate($openidUrl, $returnTo, $realm, $required = array(), $optional = array()) {
    if (trim($openidUrl) != '') {
      if ($this->isEmail($openidUrl)) {
        $openidUrl = $this->transformEmailToOpenID($openidUrl);
      }

      $consumer = $this->getConsumer();

      $authRequest = $consumer->begin($openidUrl);

      if (!isset($authRequest) || !$authRequest) {
        throw new InvalidArgumentException('Invalid OpenID');
      }

      if ($authRequest->shouldSendRedirect()) {
        $redirectUrl = $authRequest->redirectUrl($realm, $returnTo);

        if (Auth_OpenID::isFailure($redirectUrl)) {
          throw new Exception('Could not redirect to server: '.$redirectUrl->message);
        } else {
          $this->controller->redirect($redirectUrl);
        }
      } else {
        $formId = 'openid_message';
        $formHtml = $authRequest->formMarkup($realm, $returnTo, false , array('id' => $formId));

        if (Auth_OpenID::isFailure($formHtml)) {
          throw new Exception('Could not redirect to server: '.$formHtml->message);
        } else {
          return '<html><head><title>редирект на страницу OpenId сервера</title></head>'.
           "<body onload='document.getElementById(\"".$formId."\").submit()'>".
          $formHtml.'</body></html>';
        }
      }
    }
  }

  public function getResponse($currentUrl) {
    $consumer = $this->getConsumer();
    $response = $consumer->complete($currentUrl, $this->getQuery());

    return $response;
  }

  private function getConsumer() {
    require_once 'Auth/OpenID/Consumer.php';
    return new Auth_OpenID_Consumer($this->getFileStore());
  }

  private function getQuery() {
    $query = Auth_OpenID::getQuery();

    // unset the url parameter automatically added by app/webroot/.htaccess
    // as it causes problems with the verification of the return_to url
    unset($query['url']);
      
    return $query;
  }

  private function isEmail($string) {
    return strpos($string, '@');
  }

  private function transformEmailToOpenID($email) {
    if (include_once 'My/Auth/Yadis/Email.php') {
      return Auth_Yadis_Email_getID($email);
    }

    throw new InvalidArgumentException('Invalid OpenID');
  }

  private function getFileStore() {

    require_once 'Auth/OpenID/FileStore.php';

    $storePath = Zend_Registry::getInstance()->configuration->openidFileStore;

    if (!file_exists($storePath) && !mkdir($storePath,0777)) {
      throw new Exception('Could not create the FileStore directory '.$storePath.'. Please check the effective permissions.');
    }

    return new Auth_OpenID_FileStore($storePath);
  }
}


* This source code was highlighted with Source Code Highlighter.


public function openid(){
    if (null === $this->_openid) {
      require_once APPLICATION_PATH . '/models/openid.php';
      $this->_openid = new OpenidComponent($this);
    }
    return $this->_openid;

  }


* This source code was highlighted with Source Code Highlighter.


Как параметр передаем контроллер для вызова редиректа(openId предыдущей версии), но так как $this->_redirect(); это protected метод и его нельзя вызывать из других классов, я добавил обертку для него в классе контроллера(это лучше конечно делать через интерфейсы, но это дело лично каждого).

public function redirect($url){
    $this->_redirect($url);
  }


* This source code was highlighted with Source Code Highlighter.


define('Auth_Yadis_CURL_OVERRIDE',true); — значит что будет использоваться file_get_contents а не curl(для discovery). У меня curl установлен но при работе с https, как у гугля, библиотека не выдает никаких ошибок, но и не работает. На это я еще потрачу свое время, но пока настроил что бы работало, скорее всего проблема именно в моих настройках или конфигурации сервера.

И так, как это использовать.

В LoginController делаем action, в который попадем после того как пользователь ввел свой ID в поле ввода и нажал логин.

public function openidAction(){
    error_reporting(E_ERROR);
    $auth  = Zend_Auth::getInstance();
    $flashMessenger = $this->_helper->FlashMessenger;
    $this->_helper->layout->disableLayout();
    $this->_helper->viewRenderer->setNoRender();
    $identifier = trim($this->getRequest()->getParam("openid_identifier"));
    $openidComponent = $this->openid();
    try{
      $ret = $openidComponent->authenticate($identifier,Zend_Registry::getInstance()->configuration->webhost.'/login/openidcallback/',Zend_Registry::getInstance()->configuration->webhost, $required = array(), $optional = array());
      if ($ret){
        echo $ret;
      }

    }catch(Exception $e){
        
      Zend_Registry::getInstance()->logger->ERR("openid error:".$e->getMessage().$e->getTraceAsString());
      $flashMessenger->addMessage("Неправильный openID!");
      return $this->_redirect('/login/');
    }
  }


* This source code was highlighted with Source Code Highlighter.


Zend_Registry::getInstance()->configuration->webhost = это имя моего хоста, можно его получить из переменной SERVER(но мне он нужен и в других местах)

Zend_Registry::getInstance()->configuration->webhost.'/login/openidcallback/ — это куда OpendId сервер должен сделать редирект после логина(удачного или по нажатию отмена). Можно возвращаться обратно на /login/ с каким то параметром, и обрабатывать его, опять же дело личное.

Далее код который проверяет логин по возвращению с сервера OpenId:

public function openidcallbackAction(){

    $openidComponent = $this->openid();
    $response = $openidComponent->getResponse(Zend_Registry::getInstance()->configuration->webhost.'/login/openidcallback/');
    $flashMessenger = $this->_helper->FlashMessenger;

    if ($response->status == Auth_OpenID_CANCEL) {

      $flashMessenger->addMessage('Верификация была отменена!');
      return $this->_redirect('/login/');
    } else if ($response->status == Auth_OpenID_FAILURE) {
      $flashMessenger->addMessage("Ошибка авторизации: $response->message !");
      return $this->_redirect('/login/');
    } else if ($response->status == Auth_OpenID_SUCCESS) {

      $auth = Zend_Auth::getInstance();
      $openid = $response->getDisplayIdentifier();
      $model = $this->getUserModel();
      //look for user, if not found suggest to choose userName on site
      $user = $model->findByOpenid($openid);
      if ($user){
        $auth->getStorage()->write($user);
        
        return $this->_redirect("/");//на главную
      }

      $flashMessenger->addMessage('Вы первый раз на сайте. Пожалуйста, выберите имя пользователя!');

      $model = Lookup::get()->user();
      $newUser = $model->create();
      $newUser->save();

      $auth->getStorage()->write($newUser);
      $openid = $response->getDisplayIdentifier();
      $model->addUserOpenidURL($newUser, $openid);
      return $this->_redirect('/settings/choosename/');

    }

  }


* This source code was highlighted with Source Code Highlighter.


Для хранения ассоциации между пользователем базы и OpenID идентификатором я использую таблицу. findByOpenid и addUserOpenidURL работают с ней. Я посчитал что «неизвестный» или «гость» неподходит для моего сайта, и если пользователь успешно прилогинился, но первый раз на сайте, я предлагаю выбрать имя пользователя, чтобы продолжить(этот пункт возможно окажется лишним для тех кто так не считает).

Для хранения OpenId ассоциация и nonces(не знаю как перевести получше) используется файловое хранилище, т.к для хранения в БД эта билиотека требует соединение к базе от PEAR, а мне не хотелось лишние объекты.

Надеюсь разработчики зенда все же решатся доделать реализацию OpenId и тогда прийдется писать перенос данных из формата этой библиотеки в формат зенда, думаю это не создаст особых проблем, и может послужит темой для отдельного топика.

Возможно кто то самостоятельно сделал патчи к zend framework и уже успешно использует родные зендовские сервисы, был бы очень благодарен если Вы ними поделитесь.

Если кому-то топик полезным, но недостаточно детальным, пишите комментарии, постараюсь дополнить.

P.S. прошу очень не придираться к Naming Conventions, я знаю я их не очень то соблюдаю :)
Tags:
Hubs:
+19
Comments 28
Comments Comments 28

Articles