vorst.ru - Статьи о задачах возникающих при создании сайта и их решении
Статьи из рубрики about sites

Рефакторинг кода авторизации

Использование аккаунта социальной сети для авторизации

Авторизация с помощью социальной сети не должна зависеть от типа аккаунта. В предыдущем посте используется код, где в конструкторе классa common/components/SocialContact.php выполняется оператор switch. При добавлении обработчика для новой социальной сети, придется дописывать код, при этом меняя класс.

Перепишем конструктор так, чтобы класс не нужно было менять и он не зависел от типов социальных сетей.


Как подключить авторизацию через аккаунт

OAuth2 авторизация с использованием аккаунта

Допустим, что вы уже зарегистрированы в известной социальной сети. То есть вы уже сообщили о себе, все, что считали нужным. Очевидно вам не хочется проходить процедуру еще раз на другом ресурсе.

Авторизация с помощью социальной сети позволяет избежать повторного заполнения формы регистрации. При этом защищенность вашего аккаунта в социальной сети никак не страдает. Вы просто подтверждаете, что вы это вы. Как подключить эту услугу к проекту используя расширение yiisoft/yii2-authclient?


Модерация и вывод комментариев

Ответ на комментарии и рекурсивный вывод

На сайте оставили комментарий, вы ответили. Оппонент что-то еще уточнил, вы опять ответили. Цепочка из нескольких вопросов/ответов, которую нужно представить в виде лестницы.

Отступы можно выводить в обычном цикле, но если отступы формируются с помощью блочных элементов и css, то проще использовать рекурсию.


Модуль для управления блогом

Блог включает управление комментариями, пользователями, rbac

Новости, сообщения, описания - это все статьи, а статьи - это блог. Блог является важной составляющей любого сайта.

Блог настолько важен, что самая популярная CMS на планете WordPress - это блог.

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


Как определить правило для RBAC

Выполнять действие или нет

В предыдущей статье были определены две роли - Админ и Автор, как в обычном блоге. А обычно блог используется при создании любого сайта. Для ролей Админ и Автор были определены разрешения.

Если вы Админ, то у вас есть общее разрешение create. Оно дает право добавлять данные к любым моделям. Если вы Автор, то такое разрешение давать не стоит. Автор не может, например, добавить новую рубрику.


Определение разрешений на доступ и ролей для пользователей

Задание доступа только к определенным действиям

Разработка бекенда сайта часто подразумевает ограничение прав при редактировании контента.

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


Как подключить RBAC

Подключение ролевого доступа

В большом количестве задач необходимо в backend-части приложения разрешать одним группам пользователей выполнять только определенные действия. Другим может быть разрешено чуть больше. И так далее, до админа.

Именно распределение прав для групп пользователей и будет рассмотрено в паре-тройке статей. В качестве примера будет использоваться всего две роли.


Как определить и вызвать событие

Пример события

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

В первом случае обработчик события один, во-втором их несколько. Но принцип один - наступило событие, надо действовать.


Пример работы с сессией

Если читатель хочет вспомнить, что просмотрено и прочитано

В магазинах это давно норма. Все, что вы просмотрели, у вас перед глазами. Почему бы не сделать тоже самое для блога - вывести заголовки просмотренных постов и время, которое потрачено на листание и чтение каждого поста.


Как разместить контент на двух языках

Перевод контента сайта

Для перевода интерфейса в Yii применяется таблица из двух колонок. В первой колонке записываются слова или фразы на английском, во второй - перевод на русском. Строки отсортированы по первой колонке. В тексте программы вместо нужного слова или фразы вызывается функция t('Word or Phrase'). Если текущий язык сайта английский, функция возвращает параметр, иначе соответствующий перевод.

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



Поиск



Рефакторинг кода авторизации

Использование аккаунта социальной сети для авторизации

Авторизация с помощью социальной сети не должна зависеть от типа аккаунта. В предыдущем посте используется код, где в конструкторе классa common/components/SocialContact.php выполняется оператор switch. При добавлении обработчика для новой социальной сети, придется дописывать код, при этом меняя класс.

Перепишем конструктор так, чтобы класс не нужно было менять и он не зависел от типов социальных сетей.


    Поделиться

Convertor

После запроса социальной сети программа получает объект $client. Метод getId() объекта возвращает строку "yandex", "vkontakte" и прочее. Можно использовать эту строку в качестве имени класса, который и будет выполнять необходимый код. Классы можно хранить в подкаталоге, например common/components/convertor/Yandex.php.

  public function __construct($client, $config = [])
  {
    $client_id = $client->getId();
    $attributes = $client->getUserAttributes();
    $this->id = (string)$attributes['id'];
    // convert from individual to a single view of attributes
    $class_name = 'common\\components\\convertor\\' . 
      ucfirst($client_id);
    $convertor = new $class_name();
    $convertor->set($this, $attributes);
    // if email not setted then make it
    if (!$this->email)
      $this->email = "{$attributes['id']}@{$client_id}.net";
    parent::__construct($config);
  }

Собственно сам конвертор, например common/components/convertor/Yandex.php должен реализовать метод set.

namespace commmon\components\convertor;
class Yandex implements Convertor {
  public function set($obj, $attributes)
  {
    $obj->name = $attributes['first_name'] . ' ' . $attributes['last_name'];
    $obj->email = isset($attributes['default_email'])
      ? $attributes['default_email']
      : false;
  }
}

SocialLink

При первом входе с помощью аккаунта социальной сети добавляется запись в модель User. Добавленная запись связывается с социальной сетью. Для этого создается запись в модели SocialLink.

Если же пользователь уже вошел с помощью имени и пароля, а потом нажал еще и на кнопку социальной сети, то можно связать существующую запись в модели User, добавив запись в модель SocialLink.

Таким образом действие выполняется два раза, поэтому стоит выделить его в отдельный метод. Полный текст common/components/SocialContact.php теперь следующий.

namespace common\components;
use Yii;
use yii\base\BaseObject;
use yii\base\InvalidValueException;
use common\models\SocialLink;
use common\models\User;
class SocialContact extends BaseObject
{
  public $id;
  public $name;
  public $email;
  private $_link;
  /**
   * Retrieve id, name, email and, may be more.
   * 
   * @param string social client
   * @param array attributes (OAuth2 response)
   */
  public function __construct($client, $config = [])
  {
    $client_id = $client->getId();
    $attributes = $client->getUserAttributes();
    $this->id = (string)$attributes['id'];
    // convert from individual to a single view of attributes
    $class_name = 'common\\components\\convertor\\' .
      ucfirst($client_id);
    $convertor = new $class_name();
    $convertor->set($this, $attributes);
    // if email not setted then make it
    if (!$this->email)
      $this->email = "{$attributes['id']}@{$client_id}.net";
    parent::__construct($config);
  }
  public function registration($client_id)
  {
    if (User::find()->where(['email' => $this->email])->exists()) {
      Yii::$app->getSession()->setFlash('error', [
        Yii::t('app', 
          'User with {email} have been exist, but not linked to {client}. ' . 
          'Try to login with other social network or with name and password.', [
          'email' => $this->email,
          'client' => $client_id,
        ]),
      ]);
    } else {
      if(User::find()->where(['name' => $this->name])->exists()) {
        Yii::$app->getSession()->setFlash('error', [
          Yii::t('app', 
            'A user named {name} already exists. ' .
            'Try logging in if you have registered before.', [
            'name' => $this->name,
          ]),
        ]);
      } else {
        $password = Yii::$app->security->generateRandomString(6);
        $user = new User([
          'name' => $this->name,
          'email' => $this->email,
          'password' => $password,
          'status' => User::STATUS_ACTIVE,
        ]);
        $user->generateAuthKey();
        $user->generatePasswordResetToken();
        $transaction = $user->getDb()->beginTransaction();
        if ($user->save()) {
          if ($this->makeLink($client_id, $user->id)) {
            $transaction->commit();
            Yii::$app->user->login($user);
          } else {
            throw new InvalidValueException($this->showErrors($this->_link)); 
          }
        } else {
          throw new InvalidValueException($this->showErrors($user)); 
        }
      }
    }
  }
  public function makeLink($client_id, $user_id)
  {
    $this->_link = new SocialLink([
      'user_id' => $user_id,
      'source' => $client_id,
      'source_id' => $this->id,
    ]);
    return $this->_link->save();
  }
  /**
  * Show $model errors in dev mode
  * @param object model
  * @return string errors message 
  */   
  private function showErrors($model)
  {
    $out = 'Can\'t save ' . $model->tableName() . "\n";
    foreach($model->getErrors() as $field => $messages) {
      $out .= "«{$field}»\n";
      foreach($messages as $message) {
        $out .= "{$message}\n";
      }
      $out .= "\n";
    }
    return $out;
  }
}

Теперь действие авторизации в контроллере frontend/controllers/SiteController.php стало проще и понятнее, а значит мы достигли цели :).

  public function onAuthSuccess($client)
  {
    $social_contact = new SocialContact($client);
    $social_link = SocialLink::find()->where([
      'source' => $client->getId(),
      'source_id' => $social_contact->id,
    ])->one();
    if (Yii::$app->user->isGuest) {
      if ($social_link) { // authorization
        Yii::$app->user->login($social_link->user);
      } else { // registration
        $social_contact->registration($client->getId());
      }
    } else { // the user is already registered
      if (!$social_link) { // add external service of authentification
        $social_contact->makeLink($client->getId(), Yii::$app->user->id);
      }
    }
  }

Заключение

Полная версия с аватаром в расширении sergmoro1/yii2-user.

Leave a comment

Only authorized users can leave comments. Please log in or pass a registration.