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

    В своем проекте (Вопросы и ответы для программистов) на 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, я знаю я их не очень то соблюдаю :)
    Поделиться публикацией
    Похожие публикации
    Ой, у вас баннер убежал!

    Ну, и что?
    Реклама
    Комментарии 28
    • 0
      У родной библиотеки Zend (Zend_Openid) есть большие проблемы по работе с livejournal и с blogger :(
      • +1
        у нее как бы сказать нет проблем — она вообще не пригодная, поэтому можно выразиться что ее просто «нет».
        • 0
          Поддерживает компонент вроде как наш русский, но на баг уже больше недели не реагирует, а так не хочется читать спецификации и доделывать.
          • 0
            странно, у меня работает с Clickpass сервисом отлично
            • 0
              могу сделать вывод что clickpass работает на openid 1.0(либо и тот и тот) и что важно с html discovery.
              В OpenID 2.0 используется XRDS discovery, реализация которого просто отсутствует в zend-framework, о чем я и указал в топике(см. @todo OpenID 2.0 (7.3) XRI and Yadis discovery )

              То что он не работает с 2.0 приводит к тому что не будет работать с гуглем и еще несколькими крупными сайтами.
        • 0
          Супер, то, что надо. Всегда интересно почитать про zend.
          • 0
            буду писать еще :) проект не стоит на месте, проблемы появляются, буду описывать их на хабре.
          • 0
            Недавно скачав компонент для работы с Google Calendar из Zend Framework обнаружил, что там не поддерживается некоторое количество параметров, управляющих запросами к означенному сервису Google.

            Из начала вашей статьи понял, что не только «календари» они забыли дописать :(
            • 0
              Само ядро всегда работает стабильно, но сервисы(компоненты) пишут волонтеры, а волонтер такое дело — необязательное.

              Вот отсюда и корни ростут. Ушел в запой, или занялся своими делами, и нет времени уже поддерживать то что начал.

              Плохо то, что плохо следят за тем что актуально и не работает, а OpenID после того как гиганты вроде гугля объявили о поддержке уж просто очень актуален.
            • +1
              Ко всему прочиму у Zend много чего не дописано, кеширование (теги в бекэндах), практически все сервисы и т.д. А оно и не зачем. Все равно на рабочих проектах практически все классы необходимо расширять наследованием, а это как раз делается превосходно.

              На мой взгляд в этом и есть основная задача фреймворка.

              Вот к примеру, у вас получилось превосходное расширение под OpenID, за что вам большое спасибо.

              К сожалению я не смог найти нормальную песочницу расширений для ZF (т.е. расширения без тестов поставляемые как есть). Кто нибудь знает подобные ресурсы? Вроде в русскоязычной группе был разговор по этому поводу, но он походу ничем не закончился (
              • 0
                Теги вроде ж поддерживаются там где это возможно (зависит от бекенда).

                Да, был разговор, закончился тем, что дождались пока кто-то сделал западный ресурс. — www.zfsnippets.com/
                • 0
                  тэги поддерживаются в Zend_Cache_Backend_File. Но что такое файл? если проект большой то тут либо Memcached либо Apc. Все что угодно но не файл :)
                  • 0
                    на самом деле и файлы отлично работают в больших проектах.
                    APC и мемкешед это очень разное, не находите?

                    Ну в некоторых системах теги приводят к очень большому усложнению или просто невозможно (подозреваю в АРС просто нельзя, в мемкешеде сложно и нетривиально делается, хотя у котерова видел).

                    просто если чего-то нет — то это не обязательно «недоделано, ибо ушли в запой», а потому что нельзя сделать, не надо или портит что-то другое, что не очевидно

                    Но это же опенсорс, так вперед, смело, дописывайте :)
                    • 0
                      в случае с openid я считаю что можно использовать слово — недоделано. TODO как раз так и можно перевести.

                      В случае с кэшами — если проект скажем на шаред хостинге, тогда лучше файловый кэш чем никакого. А если есть возможность установить скажем APC, тогда нет смысла использовать файл, т.к APC ускорит работу сайта в целом, + как бонус работа с кэшем.

                      Насчет тэгов рассуждать не буду, т.к еще не использовал, и навряд ли буду, т.к возможность установить apc есть.

                      опенсорс — да, возможно буду дописывать, нужно было развернуть быстро, я это сделал, а дописать уже могу попозже.
                  • 0
                    Да в общем то даже поддерживаются там где не возможно, например в бекенде от Dklab для memcache, но это же не официальное расширение, а ведь если ребята из Dklab это сделали, то почему же ребята из Zend это не могли сделать.

                    Правда у Dklab немного странная реализация, я бы сделал по другому, ну уж как есть.
                    • 0
                      Кстати, данный ресурс не понравился, мало чего то там всего, у меня в рабочем репозитории и того больше.
                      • 0
                        ну он не для нравиться сделан то. толку от того, что у вас или у меня или у кого-то дома чего-то больше, если этого никто не видит. выкладывайте, чего же ждать пока кто-то другой сделает
                  • 0
                    > В своем проекте (когда то дел

                    дальше не читал
                    • 0
                      Вы знаете, я ваших топиков не читал тоже.
                      • –1
                        Вы меня, конечно, извините, я имел в виду, что, кажется, на Хабре принято перед постингом проверять хотя бы орфографические ошибки в посте. Прошу прощения, что не написал в личку.
                        • 0
                          Я не принимаю близко к сердцу. И мне очень стыдно от своей безграмотности. Спасибо за ваш голос и за отзыв.
                    • 0
                      Спасибо за интересную информацию. Вот только вот этой вещи в My/Auth/Yadis/Email.php в openidenabled.com/php-openid/ не нашёл? Насколько я понял её там быть и должно:)
                      • 0
                        Спасибо :)

                        По этой ссылке openidenabled.com/php-openid/

                        Читайте что снизу(PHP Yadis Library там ссылка) —

                        Current stable release series: 2.x.x

                        The 1.x.x release series is provided for applications that have not yet been upgraded to use the 2.x.x library. If you need to download the 1.x.x library, you will also need the PHP Yadis Library.

                      • 0
                        спасибо за ответ, не заметил ссылки
                        • 0
                          вот только Email.php там нет?:) и в том наборе, что идёт с openid тоже. Там есть вот эти файлы openidenabled.com/files/php-openid/repos/2.x.x/Auth/Yadis/.
                          • 0
                            у меня был(только незнаю точно откуда и какой версии). Впрочем как я понял, эта фитча преобразования email в ID не совсем нужная, а если и нужная то будет редко использоваться.
                            • 0
                              верно подмечено. Раньше может и был, но сейчас и у меня нет. Просто isEmail всегда возвращает false. Надо исправить код себе, и чуть статью :) Спасибо!
                            • 0
                              два замечания

                              1. компонента привязана к FileStore, что недопустимо при работе на кластере

                              2. Yadis в недрах своих хочет массив сессий, что тоже недопустимо при работе на кластере

                              а в целом понятно написано

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое