Регистрация и авторизация через социальные сети

Обсуждение документации второй версии фреймворка. Переводы Cookbook и авторские рецепты.
Ответить
sergmoro1
Сообщения: 112
Зарегистрирован: 2012.11.08, 13:07

Регистрация и авторизация через социальные сети

Сообщение sergmoro1 » 2018.06.19, 15:10

Какие классы и модели стоит определить, чтобы быстро подключить расширение https://github.com/yiisoft/yii2-authclient.

1. Нужна дополнительная (к User) таблица.

Код: Выделить всё

<?php
use yii\db\Migration;
class m180608_123330_create_social_link extends Migration
{
  public $table = '{{%social_link}}';
  public function up()
  {
    $tableOptions = null;
    if ($this->db->driverName === 'mysql') {
      $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
    }
    $this->createTable($this->table, [
      'id' => $this->primaryKey(),
      'user_id' => $this->integer()->notNull(),
      'source' => $this->string(255)->notNull(),
      'source_id' => $this->string(255)->notNull(),
    ], $tableOptions);
    $this->addForeignKey ('FK_social_link_user', 
      $this->table, 'user_id', '{{%user}}', 'id', 'CASCADE');
  }
  public function down()
  {
    $this->dropTable($this->table);
  }
}
И модель к ней.

Код: Выделить всё

<?php
namespace common\models;
use yii\db\ActiveRecord;
use common\models\User;
class SocialLink extends ActiveRecord
{
  public static function tableName()
  {
    return '{{%social_link}}';
  }
  public function rules()
  {
    return [
      [['user_id', 'source', 'source_id'], 'required'],
      ['user_id', 'integer'],
      [['source', 'source_id'], 'string', 'max'=>255],
    ];
  }
  public function getUser()
  {
    return User::findOne($this->user_id);
  }
}
2. При обращении к социальной сети, последняя возвращает объект client, в котором передаются имя и прочие атрибуты пользователя.
Но формат данных разный, поэтому нужно сначала привести значения к общему виду (в конструкторе) и потом регистрировать контакт.

Код: Выделить всё

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 obj social client (OAuth2 response)
   * @param array config
   */
  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;
  }
}
Идентификатор сети служит именем класса, который производит конвертацию в конструкторе. Например для "yandex".

Код: Выделить всё

namespace common\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;
  }
}
3. В результате в контроллере 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);
      }
    }
  }
Использовано руководство "Быстрый старт" - https://github.com/yiisoft/yii2-authcli ... k-start.md.
Полное описание подключения расширения yiisoft/yii2-authclient - http://vorst.ru/post/authorization-social-network.
Расширение, включающее авторизацию через социальные сети - https://github.com/sergmoro1/yii2-user.

Ответить