Пакет на базе SleepingOwlAdmin 4-ой версии и добавили к ней управление пользователями, ролями и правами через пакет Sentinel. А также добавили регистрацию и авторизацию через Facebook, Google+, VKontakte и реализовали заготовку под REST/RESTful API с помощью пакета Dingo.
Вопросы пишите на [email protected]
Мы не будем рассматривать вопросы настройки окружения, установки и первоначальной настройки фреймворка. Начнем с той точки, когда у нас установлен laravel версии 5.3.* и подключен к БД. В .env указан URL приложения, например http://mysite.com и домен под API:
APP_URL=http://mysite.com
API_DOMAIN=api.mysite.com
На момент написания материала, последняя стабильная версия фреймворка - 5.3.16.
Для начала установим все необходимые пакеты, используемые в сборке, а после приступим к их настройке.
Склонировав и развернув проект, в .env добавляем следующие строки и задаем свои значения:
FACEBOOK_KEY=yourkeyfortheservice FACEBOOK_SECRET=yoursecretfortheservice FACEBOOK_REDIRECT_URI=http://mysite.com/callback?network=facebook
GOOGLE_KEY=yourkeyfortheservice GOOGLE_SECRET=yoursecretfortheservice GOOGLE_REDIRECT_URI=http://mysite.com/callback?network=google
VKONTAKTE_KEY=yourkeyfortheservice VKONTAKTE_SECRET=yoursecretfortheservice VKONTAKTE_REDIRECT_URI=http://mysite.com/callback?network=vkontakte
APP_URL=http://mysite.com API_DOMAIN=api.mysite.com API_STANDARDS_TREE=vnd API_PREFIX=v1 API_NAME=mysite API_DEBUG=true
В терминале последовательно запускаем команды:
php artisan jwt:generate
php artisan migrate
php artisan db:seed
php artisan config:cache
Открываем для редактирования файл composer.json
В секцию "require" добавляем следующие строки:
"dingo/api": "1.0.x@dev",
"laravelrus/sleepingowl": "4.*@dev",
"cartalyst/sentinel": "2.0.*",
"laravelcollective/html": "^5.3.0",
"socialiteproviders/google": "^2.0",
"socialiteproviders/vkontakte": "^2.0",
"laravel/socialite": "^2.0",
"tymon/jwt-auth": "0.5.*",
"fzaninotto/faker": "^1.6",
"nesbot/carbon": "^1.21"
Запускаем в терминале команду
composer update
В config/app.php, в массив $providers добавляем (до Application Service Providers):
/* * Package Service Providers... */
SleepingOwl\Admin\Providers\SleepingOwlServiceProvider::class, Dingo\Api\Provider\LaravelServiceProvider::class, Cartalyst\Sentinel\Laravel\SentinelServiceProvider::class, Collective\Html\HtmlServiceProvider::class, \SocialiteProviders\Manager\ServiceProvider::class, Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,
в массив $aliases добавляем
'Activation' => Cartalyst\Sentinel\Laravel\Facades\Activation::class,
'Reminder' => Cartalyst\Sentinel\Laravel\Facades\Reminder::class,
'Sentinel' => Cartalyst\Sentinel\Laravel\Facades\Sentinel::class,
'Form' => Collective\Html\FormFacade::class,
'Html' => Collective\Html\HtmlFacade::class,
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,
Там же правим значение для локали приложения:
'locale' => 'ru',
Запускаем в терминале команду, устанавливаем административный интерфейс для Laravel - SleepingOwlAdmin:
php artisan sleepingowl:install
Последовательно запускаем в терминале команды:
php artisan vendor:publish --provider="Dingo\Api\Provider\LaravelServiceProvider"
php artisan vendor:publish --provider="Cartalyst\Sentinel\Laravel\SentinelServiceProvider"
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"
Генерируем ключ для jwt:
php artisan jwt:generate
Во избежание коллизий, сразу удалим дефолтные миграции Laravel:
database/migrations/2014_10_12_000000_create_users_table.php
database/migrations/2014_10_12_100000_create_password_resets_table.php
Итак, все необходимые пакеты установлены. Приступаем к их настройке.
Открываем для редактирования файл миграции database/migrations/2014_07_02_230147_migration_cartalyst_sentinel.php
В секцию с созданием таблицы users, добавляем поля:
$table->string('facebook_id')->nullable();
$table->string('google_id')->nullable();
$table->string('vkontakte_id')->nullable();
Создаем модель Post и миграцию под нее
php artisan make:model -m Post
Добавляем в миграцию 2 поля: title и content
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->text('content');
$table->timestamps();
});
}
Запускаем миграции:
php artisan migrate
Выполняем команду
php artisan make:seeder RoleSeeder
Редактируем файл database/seeds/RoleSeeder.php
Закладываем основу для 3-х ролей пользователей и создаем пользователя администратора.
<?php
use Illuminate\Database\Seeder;
class RoleSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $admin = [ 'email' => '[email protected]', 'password' => 'adminadmin', ]; $adminUser = Sentinel::registerAndActivate($admin); $role = [ 'name' => 'Администратор', 'slug' => 'admin', 'permissions' => [ 'admin' => true, ] ]; $adminRole = Sentinel::getRoleRepository()->createModel()->fill($role)->save(); $adminUser->roles()->attach($adminRole); $role = [ 'name' => 'Пользователь', 'slug' => 'user', ]; $userRole = Sentinel::getRoleRepository()->createModel()->fill($role)->save(); $role = [ 'name' => 'Забанен', 'slug' => 'banned', ]; $banRole = Sentinel::getRoleRepository()->createModel()->fill($role)->save(); } }
Выполняем команду
php artisan make:seeder PostSeeder
Редактируем файл database/seeds/PostSeeder.php
Создаем болванки публикаций с фейковым наполнением:
<?php use Illuminate\Database\Seeder; use Carbon\Carbon;
class PostSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $faker = Faker\Factory::create(); for ($i=0; $i<10; $i++){ DB::table('posts')->insert([ 'title' => $faker->text(10), 'content' => $faker->text(200), 'created_at' => Carbon::now(), 'updated_at' => Carbon::now(), ]); } } }
Редактируем файл database/seeds/DatabaseSeeder.php
<?php use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $this->call(RoleSeeder::class); $this->call(PostSeeder::class); } }
Запускаем команду:
php artisan db:seed
Удаляем директорию app/Http/Controllers/Auth за ненадобностью
Создаем AuthController
php artisan make:controller AuthController
Добавляем роуты в routes\web.php
/**
* Route for auth system
*/
// Вызов страницы регистрации пользователя
Route::get('register', 'AuthController@register');
// Пользователь заполнил форму регистрации и отправил
Route::post('register', 'AuthController@registerProcess');
// Пользователь получил письмо для активации аккаунта со ссылкой сюда
Route::get('activate/{id}/{code}', 'AuthController@activate');
// Вызов страницы авторизации
Route::get('login', 'AuthController@login');
// Пользователь заполнил форму авторизации и отправил
Route::post('login', 'AuthController@loginProcess');
// Выход пользователя из системы
Route::get('logout', 'AuthController@logoutuser');
// Пользователь забыл пароль и запросил сброс пароля. Это начало процесса -
// Страница с запросом E-Mail пользователя
Route::get('reset', 'AuthController@resetOrder');
// Пользователь заполнил и отправил форму с E-Mail в запросе на сброс пароля
Route::post('reset', 'AuthController@resetOrderProcess');
// Пользователю пришло письмо со ссылкой на эту страницу для ввода нового пароля
Route::get('reset/{id}/{code}', 'AuthController@resetComplete');
// Пользователь ввел новый пароль и отправил.
Route::post('reset/{id}/{code}', 'AuthController@resetCompleteProcess');
// Сервисная страничка, показываем после заполнения рег формы, формы сброса и т.
// о том, что письмо отправлено и надо заглянуть в почтовый ящик.
Route::get('wait', 'AuthController@wait');
// Пользователь получил письмо после регистрации в приложении для активации аккаунта со ссылкой
Route::get('activate_app/{id}/{code}', 'AuthController@activateForAppUser');
//Авторизация через соцсети
Route::get('signin', 'AuthController@signin');
//Коллбэк после авторизации через соцсети
Route::get('callback', 'AuthController@callbackSignin');
В директории resources/views создаем следующие поддиректории: auth, footer, header, layouts, mail
В директории resources/views/layouts создаем файл master.blade.php
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
@include('header.head')
</head>
<body>
<!--[if lt IE 7]>
Вы используете слишком старый браузер. Пожалуйста обновите ваш браузер для нормального серфинга по современным сайтам.
<![endif]-->
<header id="header" class="">
@include('header.header')
</header>
<section>
@yield('body')
</section>
@include('footer.footer')
@include('footer.foot_script')
</body>
</html>
Создаем пустые файлы:
resources/views/header/head.blade.php
resources/views/header/header.blade.php
resources/views/footer/footer.blade.php
resources/views/footer/foot_script.blade.php
В resources/views/header/head.blade.php добавляем
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css" rel="stylesheet">
<link href="/css/auth.css" rel="stylesheet">
Создаем файл public/css/auth.css:
@media (min-width: 768px) { .omb_row-sm-offset-3 div:first-child[class*="col-"] { margin-left: 25%; } }
.omb_login .omb_authTitle { text-align: center; line-height: 300%; }
.omb_login .omb_socialButtons a { color: white; // In yourUse @body-bg opacity:0.9; } .omb_login .omb_socialButtons a:hover { color: white; opacity:1; } .omb_login .omb_socialButtons .omb_btn-facebook {background: #3b5998;} .omb_login .omb_socialButtons .omb_btn-vkontakte {background: #00aced;} .omb_login .omb_socialButtons .omb_btn-google {background: #c32f10;}
.omb_login .omb_loginOr { position: relative; font-size: 1.5em; color: #aaa; margin-top: 1em; margin-bottom: 1em; padding-top: 0.5em; padding-bottom: 0.5em; } .omb_login .omb_loginOr .omb_hrOr { background-color: #cdcdcd; height: 1px; margin-top: 0px !important; margin-bottom: 0px !important; } .omb_login .omb_loginOr .omb_spanOr { display: block; position: absolute; left: 50%; top: -0.6em; margin-left: -1.5em; background-color: white; width: 3em; text-align: center; }
.omb_login .omb_loginForm .input-group.i { width: 2em; } .omb_login .omb_loginForm .help-block { color: red; }
@media (min-width: 768px) { .omb_login .omb_forgotPwd { text-align: right; margin-top:10px; } }
.btn-group-lg>.btn, .btn-lg { padding: 10px 7px; }
Создаем resources/views/errors/errmsg.blade.php :
@if ($errors->any())
@endif
@if ($message = Session::get('success'))
@endif
Создаем шаблоны элементов форм:
resources/views/widgets/form/_formitem_text.blade.php :
<?php
if(! isset($value)) $value = null;
$error_class = $errors->has($name) ? ' has-error' : '';
?>
{!! $errors->first($name) !!}
resources/views/widgets/form/_formitem_password.blade.php :
<php $error_class = $errors->has($name) ? ' has-error' : ''; ?>
{!! $errors->first($name) !!}
resources/views/widgets/form/_formitem_btn_submit.blade.php :
{!! Form::submit($title, array('class' => 'btn btn-lg btn-primary btn-block')) !!}
Создаем resources/views/auth/login.blade.php :
@extends('layouts.master')
@section('body')
или
Забыли пароль?
@stop
Создаем resources/views/auth/register.blade.php :
@extends('layouts.master')
@section('body')
или
Забыли пароль?
@stop
Создаем resources/views/auth/reset_order.blade.php :
@extends('layouts.master')
@section('body')
@stop
Создаем resources/views/auth/reset_complete.blade.php :
@extends('layouts.master')
@section('body')
@stop
Создаем resources/views/auth/wait.blade.php :
@extends('layouts.master')
@section('body')
@stop
Создаем resources/views/auth/social_buttons.blade.php :
Готовим представления E-Mail:
resources/views/mail/account_activate.blade.php :
Для активации аккаунта пройдите по getUserId()}/{$code}") }}">ссылке
resources/views/mail/account_reminder.blade :
Для активации аккаунта пройдите по getUserId()}/{$code}") }}">ссылке
resources/views/mail/account_activate_app.blade.php :
Для создания нового пароля пройдите по getUserId()}/{$code}") }}">ссылке
В resources/views/welcome.blade.php произвльно, в любом месте добавим временную болванку:
<?php
if ($user = Sentinel::getUser())
{
echo 'Привет, ' . $user->email;
}else{
echo 'Привет, гость';
}
?>
Наполняем app/Http/Controllers/AuthController.php :
<?php
namespace App\Http\Controllers;
use Cartalyst\Sentinel\Checkpoints\NotActivatedException; use Cartalyst\Sentinel\Checkpoints\ThrottlingException;
use Illuminate\Http\Request; use App\Http\Requests; use Redirect; use Sentinel; use Activation; use Reminder; use Validator; use Mail; use Storage; use CurlHttp; use Illuminate\Support\Facades\Input; use Socialite; use Session;
class AuthController extends Controller {
public $networks = ['vkontakte', 'facebook', 'google']; /** * Show login page * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ public function login() { return view('auth.login'); } /** * Show Register page * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ public function register() { $email = Session::get('network_user') != '' ? Session::get('network_user')->getEmail() : ''; return view('auth.register', ['email' => $email]); } /** * Show wait page * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ public function wait() { return view('auth.wait'); } /** * Process login users * * @param Request $request * @return $this|\Illuminate\Http\RedirectResponse */ public function loginProcess(Request $request) { try { $messages = [ 'email.required' => 'Введите email', 'email.email' => 'Похоже, что email введен с ошибкой', 'password.required' => 'Введидите пароль', 'password.between' => 'Минимальная длина пароля - 8 символов', ]; $this->validate($request, [ 'email' => 'required|email', 'password' => 'required|between:8,100', ], $messages); if (Sentinel::authenticate($request->all())) { if(Sentinel::inRole('admin')) return redirect('admin'); return Redirect::intended('/'); } $errors = 'Неправильный логин или пароль.'; return Redirect::back() ->withInput() ->withErrors($errors); } catch (NotActivatedException $e) { $sentuser= $e->getUser(); $activation = Activation::create($sentuser); $code = $activation->code; $sent = Mail::send('mail.account_activate', compact('sentuser', 'code'), function($m) use ($sentuser) { $m->from('[email protected]', 'MySite'); $m->to($sentuser->email)->subject('Активация аккаунта'); }); if ($sent === 0) { return Redirect::to('login') ->withErrors('Ошибка отправки письма активации.'); } $errors = 'Ваш аккаунт не ативирован! Поищите в своем почтовом ящике письмо со ссылкой для активации (Вам отправлено повторное письмо). '; return view('auth.login')->withErrors($errors); } catch (ThrottlingException $e) { $delay = $e->getDelay(); $errors = "Ваш аккаунт блокирован на {$delay} секунд."; } return Redirect::back() ->withInput() ->withErrors($errors); } /** * Process register user from site * * @param Request $request * @return $this */ public function registerProcess(Request $request) { $messages = [ 'email.required' => 'Введите email', 'email.email' => 'Похоже, что email введен с ошибкой', 'password.required' => 'Введидите пароль', 'password_confirm.required' => 'Введидите подтверждение пароля', 'password_confirm.same' => 'Введенные пароли не одинаковы', 'password.between' => 'Минимальная длина пароля - 8 символов', ]; $this->validate($request, [ 'email' => 'required|email', 'password' => 'required|between:8,100', 'password_confirm' => 'required|same:password', ], $messages); $input = $request->all(); if (Session::get('network_user') && Session::get('network_user')->id) { $input[Session::get('network') . '_id'] = Session::get('network_user')->id; } $credentials = [ 'email' => $request->email ]; if($user = Sentinel::findByCredentials($credentials)) { return Redirect::to('register') ->withErrors('Такой Email уже зарегистрирован.'); } if ($sentuser = Sentinel::register($input)) { $activation = Activation::create($sentuser); $code = $activation->code; $sent = Mail::send('mail.account_activate', compact('sentuser', 'code'), function($m) use ($sentuser) { $m->from('[email protected]', 'MySite'); $m->to($sentuser->email)->subject('Активация аккаунта'); }); if ($sent === 0) { return Redirect::to('register') ->withErrors('Ошибка отправки письма активации.'); } $role = Sentinel::findRoleBySlug('user'); $role->users()->attach($sentuser); Session::forget('network_user'); Session::forget('network'); return Redirect::to('login') ->withSuccess('Ваш аккаунт создан. Проверьте Email для активации.') ->with('userId', $sentuser->getUserId()); } return Redirect::to('register') ->withInput() ->withErrors('Failed to register.'); } /** * Activate user account by user id and activation code * * @param $id * @param $code * @return $this */ public function activate($id, $code) { $sentuser = Sentinel::findById($id); if ( ! Activation::complete($sentuser, $code)) { return Redirect::to("login") ->withErrors('Неверный или просроченный код активации.'); } return Redirect::to('login') ->withSuccess('Аккаунт активирован.'); } /** * Activate user account by user id and activation code * * @param $id * @param $code * @return $this */ public function activateForAppUser($id, $code) { $sentuser = Sentinel::findById($id); if ( ! Activation::complete($sentuser, $code)) { return view('auth.activate_result', ['result' => 'Invalid or expired activation code.']); } Sentinel::update($sentuser, ['email_confirmed' => 1]); return view('auth.activate_result', ['result' => 'Account activated.']); } /** * Show form for begin process reset password * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ public function resetOrder() { return view('auth.reset_order'); } /** * Begin process reset password by email * * @param Request $request * @return $this|\Illuminate\Http\RedirectResponse */ public function resetOrderProcess(Request $request) { $messages = [ 'email.required' => 'Введите email', 'email.email' => 'Похоже, что email введен с ошибкой', ]; $this->validate($request, [ 'email' => 'required|email', ], $messages); $email = $request->email; $sentuser = Sentinel::findByCredentials(compact('email')); if ( ! $sentuser) { return Redirect::back() ->withInput() ->withErrors('Пользователь с таким E-Mail в системе не найден.'); } $reminder = Reminder::exists($sentuser) ?: Reminder::create($sentuser); $code = $reminder->code; $sent = Mail::send('mail.account_reminder', compact('sentuser', 'code'), function($m) use ($sentuser) { $m->from('[email protected]', 'MySite'); $m->to($sentuser->email)->subject('Сброс пароля'); }); if ($sent === 0) { return Redirect::to('reset') ->withErrors('Ошибка отправки email.'); } return Redirect::to('wait'); } /** * Show form for complete reset password * * @param $id * @param $code * @return mixed */ public function resetComplete($id, $code) { $user = Sentinel::findById($id); return view('auth.reset_complete'); } /** * Complete reset password * * @param Request $request * @param $id * @param $code * @return $this */ public function resetCompleteProcess(Request $request, $id, $code) { $messages = [ 'password.required' => 'Введидите новый пароль', 'password_confirm.required' => 'Введидите подтверждение нового пароля', 'password_confirm.same' => 'Введенные пароли не одинаковы', 'password.between' => 'Минимальная длина пароля - 8 символов', ]; $this->validate($request, [ 'password' => 'required|between:8,100', 'password_confirm' => 'required|same:password', ], $messages); $user = Sentinel::findById($id); if ( ! $user) { return Redirect::back() ->withInput() ->withErrors('Такого пользователя не существует.'); } if ( ! Reminder::complete($user, $code, $request->password)) { return Redirect::to('login') ->withErrors('Неверный или просроченный код сброса пароля.'); } return Redirect::to('login') ->withSuccess("Пароль сброшен."); } /** * @return mixed */ public function logoutuser() { Sentinel::logout(); return Redirect::intended('/'); } /** * @return mixed */ public function signin() { if (Input::get('network') && in_array(Input::get('network'), $this->networks)) { return Socialite::with(Input::get('network'))->redirect(); } } public function callbackSignin() { //проверяем, есть ли параметр network, и присутствует ли он в массиве доступных соц. сетей if (Input::get('network') && in_array(Input::get('network'), $this->networks)) { //получили данные пользователя из соц. сети $network_user = Socialite::with(Input::get('network'))->stateless()->user(); //проверяем, есть ли в полученных данных значение email if ($network_user->getEmail() != '') { //если email получен - проверяем, есть ли в системе пользователь с таким email $credentials = ['email' => $network_user->getEmail()]; $user = Sentinel::findByCredentials($credentials); if (!$user) { //если нет в системе пользователя с таким email, //проверяем есть ли в системе пользователь с таким id в конкретной соцсети $credentials = [Input::get('network') . '_id' => $network_user->getId()]; $user = Sentinel::findByCredentials($credentials); } else { //если есть в системе пользователь с таким email, //проверяем заполнен ли id сети if ($user[Input::get('network') . '_id'] == '') { $credentials = [Input::get('network') . '_id' => $network_user->getId()]; $user = Sentinel::update($user, $credentials); } } } else { //в полученных данных нет email //проверяем есть ли в системе пользователь с таким id в конкретной соцсети $credentials = [Input::get('network') . '_id' => $network_user->getId()]; $user = Sentinel::findByCredentials($credentials); } if (!$user) { //пользователь в системе не найден - //отправляем на страницу регистрации со всеми полученными из соц. сети данными Session::put('network_user', $network_user); Session::put('network', Input::get('network')); return Redirect::to('/register'); } else { //если найден - логиним в систему и редиректим на главную if ($activation = Activation::completed($user)) { Sentinel::login($user); return Redirect::intended('/'); } else { return Redirect::to('login') ->withErrors('Ранее, на Вашу почту было отправлено письмо с дальнейшими инструкциями по активации аккаунта.'); } } } }
}
Приводим модель app/User.php к виду:
<?php namespace App;
use Cartalyst\Sentinel\Users\EloquentUser as CartalystUser; use Hash;
class User extends CartalystUser { /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'email', 'password', 'first_name', 'last_name', 'facebook_id', 'google_id', 'vkontakte_id' ];
/** * The attributes excluded from the model's JSON form. * * @var array */ protected $hidden = [ 'password', 'remember_token', 'created_at', 'updated_at' ]; protected $loginNames = ['email', 'facebook_id', 'google_id', 'vkontakte_id']; public function roles() { return $this->belongsToMany('App\Role', 'role_users', 'user_id', 'role_id'); } public function theroles() { return $this->belongsToMany('App\Role', 'role_users', 'user_id', 'role_id'); } public function setTherolesAttribute($roles) { $this->theroles()->detach(); if ( ! $roles) return; if ( ! $this->exists) $this->save(); $this->theroles()->attach($roles); } public function getTherolesAttribute($roles) { return array_pluck($this->theroles()->get(['id'])->toArray(), 'id'); }
}
В конфиге config/cartalyst.sentinel.php
'users' => [
'model' => 'Cartalyst\Sentinel\Users\EloquentUser',
],</code></pre>
заменяем на
'users' => [
'model' => 'App\User',
],
Выполняем команду:
php artisan config:cache
Правим app/Providers/EventServiceProvider.php
protected $listen = [
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
'SocialiteProviders\VKontakte\VKontakteExtendSocialite@handle',
'SocialiteProviders\Google\GoogleExtendSocialite@handle',
],
];
Заводим приложения в Facebook, Google, VKontakte
Добавляем в config/services.php :
'facebook' => [
'client_id' => env('FACEBOOK_KEY'),
'client_secret' => env('FACEBOOK_SECRET'),
'redirect' => env('FACEBOOK_REDIRECT_URI'),
],
'google' => [
'client_id' => env('GOOGLE_KEY'),
'client_secret' => env('GOOGLE_SECRET'),
'redirect' => env('GOOGLE_REDIRECT_URI'),
],
'vkontakte' => [
'client_id' => env('VKONTAKTE_KEY'),
'client_secret' => env('VKONTAKTE_SECRET'),
'redirect' => env('VKONTAKTE_REDIRECT_URI'),
],</code></pre>
В .env добавляем:
FACEBOOK_KEY=yourkeyfortheservice
FACEBOOK_SECRET=yoursecretfortheservice
FACEBOOK_REDIRECT_URI=http://mysite.com/callback?network=facebook
GOOGLE_KEY=yourkeyfortheservice
GOOGLE_SECRET=yoursecretfortheservice
GOOGLE_REDIRECT_URI=http://mysite.com/callback?network=google
VKONTAKTE_KEY=yourkeyfortheservice
VKONTAKTE_SECRET=yoursecretfortheservice
VKONTAKTE_REDIRECT_URI=http://mysite.com/callback?network=vkontakte
Значения yourkeyfortheservice и yoursecretfortheservice заменяем на значения из настроек приложений в указанных соц. сетях
С регистацией, логином, авторизацией закончили. Приступаем к настройке панели администратора.
В файле app\Http\Kernel.php изменяем значение $routeMiddleware :
protected $routeMiddleware = [
// 'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
// 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
// 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
// 'can' => \Illuminate\Auth\Middleware\Authorize::class,
// 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'api.throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'isadmin' => \App\Http\Middleware\AdminPanel::class,
'jwt.auth' => 'Tymon\JWTAuth\Middleware\GetUserFromToken',
'jwt.refresh' => 'Tymon\JWTAuth\Middleware\RefreshToken',
];
В config\sleeping_owl.php :
'middleware' => ['web'],
заменяем на
'middleware' => ['web', 'isadmin'],
Создаем файл app/Http/Middleware/AdminPanel.php :
<?php
namespace App\Http\Middleware;
use Closure;
use Cartalyst\Sentinel\Laravel\Facades\Sentinel;
use Redirect;
use Request;
use Response;
use AdminSection;
class AdminPanel
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (Sentinel::guest()) return redirect('login');
if(Sentinel::inRole('admin')) return $next($request);
$arrSlugs=str_getcsv($request->path(), '/');
$method=Request::method();
$user = Sentinel::check();
if($method == 'DELETE')
{
$permit = $arrSlugs[0] . '.' . $arrSlugs[1] . '.delete';
if ($user->hasAccess([$permit])) return $next($request);
if ($user->hasAccess(['admin']))
{
$content = 'Для удаления объекта необходимы установленные права <b>'. $permit .'</b> Для получения прав обратитесь к администратору';
return Response::make(AdminSection::view($content, 'Попытка несанкционированного доступа'), 403);
}
return Redirect::back();
}
$permit=$arrSlugs[0];
if(isset($arrSlugs[1])) $permit = $permit . '.' . $arrSlugs[1];
if(isset($arrSlugs[2]) && ($arrSlugs[2] == "create")) $permit = $permit . '.' . $arrSlugs[2];
if(isset($arrSlugs[3])) $permit = $permit . '.' . $arrSlugs[3];
if ($user->hasAccess([$permit])) return $next($request);
if ($user->hasAccess(['admin']))
{
$content = 'Для данного действия необходимы установленные права <b>'. $permit .'</b> Для получения прав обратитесь к администратору';
return Response::make(AdminSection::view($content, 'Попытка несанкционированного доступа'), 403);
}
return Redirect::back();
}
}
Создаем модель app/Role.php :
php artisan make:model Role
<?php
namespace App;
use Cartalyst\Sentinel\Roles\EloquentRole;
class Role extends EloquentRole
{
public function permits()
{
return $this->belongsToMany('App\Permit');
}
}
Создаем модель и миграцию для сущности Permit :
php artisan make:model -m Permit
в файле миграции:
public function up()
{
Schema::create('permits', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('slug');
$table->timestamps();
});
}
Далее:
php artisan make:migration --create="permit_role" create_pivot_permit_role
в файле миграции:
Schema::create('permit_role', function (Blueprint $table) {
$table->increments('id');
$table->integer('permit_id');
$table->integer('role_id');
$table->timestamps();
});
В модели Permit.php:
public function roles()
{
return $this->belongsToMany('App\Role');
}
В терминале:
php artisan migrate
Создаем файлы конфигураций моделей для админки:
app/Admin/User.php :
<?php
use App\User;
use App\Role;
use SleepingOwl\Admin\Model\ModelConfiguration;
AdminSection::registerModel(User::class, function (ModelConfiguration $model) {
$model->setTitle('Пользователи');
$model->onDisplay(function () {
$display = AdminDisplay::table()->setColumns([
AdminColumn::link('email')->setLabel('email')->setWidth('400px'),
AdminColumn::text('first_name')->setLabel('Имя'),
AdminColumn::text('last_name')->setLabel('Фамилия'),
]);
$display->paginate(15);
return $display;
});
$model->onEdit(function () {
$form = AdminForm::panel()->addBody(
AdminFormElement::text('first_name', 'Имя'),
AdminFormElement::text('last_name', 'Фамилия'),
AdminFormElement::text('facebook_id', 'Facebook аккаунт'),
AdminFormElement::text('google_id', 'Google+ аккаунт'),
AdminFormElement::text('vkontakte_id', 'VKontakte аккаунт'),
AdminFormElement::text('email', 'Email')->unique()->required()->addValidationRule('email'),
AdminFormElement::multiselect('theroles', 'Роли')->setModelForOptions(new Role())->setDisplay('name')
);
return $form;
});
});
app/Admin/Role.php :
<?php
use App\Role;
use App\Permit;
use SleepingOwl\Admin\Model\ModelConfiguration;
AdminSection::registerModel(Role::class, function (ModelConfiguration $model) {
$model->setTitle('Роли');
// Display
$model->onDisplay(function () {
$display = AdminDisplay::table()->setColumns([
AdminColumn::text('name')->setLabel('Название роли'),
AdminColumn::text('slug')->setLabel('Роль'),
]);
$display->paginate(15);
return $display;
});
// Create And Edit
$model->onCreate(function () {
$form = AdminForm::panel()->addBody(
AdminFormElement::text('name', 'Название роли')->required()->unique(),
AdminFormElement::text('slug', 'Роль')->required()->unique(),
AdminFormElement::multiselect('permits', 'Права доступа')->setModelForOptions(new Permit())->setDisplay('name')
);
return $form;
});
$model->onEdit(function () {
$form = AdminForm::panel()->addBody(
AdminFormElement::text('name', 'Название роли')->required()->unique(),//->setReadOnly(true)
AdminFormElement::text('slug', 'Роль')->required()->unique(),//->setReadOnly(true)
//AdminFormElement::multiselect('permissions', 'permissions')->setModelForOptions('App\Permit')->setDisplay('name')
//AdminFormElement::multiselect('permissions', 'permissi', Role::getPermitsOptions())->setDefaultValue(array(0 => ''))->nullable()
AdminFormElement::multiselect('permits', 'Права доступа')->setModelForOptions(new Permit())->setDisplay('name')
);
return $form;
});
$model->disableDeleting();
});
app/Admin/Permit.php :
<?php
use App\Role;
use App\Permit;
use SleepingOwl\Admin\Model\ModelConfiguration;
AdminSection::registerModel(Permit::class, function (ModelConfiguration $model) {
$model->setTitle('Права');
// Display
$model->onDisplay(function () {
$display = AdminDisplay::table()->setColumns([
AdminColumn::text('name')->setLabel('Название права'),
AdminColumn::text('slug')->setLabel('Slug'),
]);
$display->paginate(15);
return $display;
});
// Create And Edit
$model->onCreateAndEdit(function () {
$form = AdminForm::panel()->addBody(
AdminFormElement::text('name', 'Название права')->required()->unique(),
AdminFormElement::text('slug', 'Slug')->required()->unique()
);
return $form;
});
});
app/Admin/Post.php :
<?php
use App\Post;
use SleepingOwl\Admin\Model\ModelConfiguration;
AdminSection::registerModel(Post::class, function (ModelConfiguration $model) {
$model->setTitle('Публикации');
// Display
$model->onDisplay(function () {
$display = AdminDisplay::table()->setColumns([
AdminColumn::text('title')->setLabel('Заголовок'),
]);
$display->paginate(15);
return $display;
});
// Create And Edit
$model->onCreateAndEdit(function () {
$form = AdminForm::panel()->addBody(
AdminFormElement::text('title', 'Заголовок')->required(),
AdminFormElement::textarea('content', 'Содержимое')->required()
);
return $form;
});
})
->addMenuPage(Post::class, 0)
->setIcon('fa fa-pencil');
Вносим изменения в app\Providers\AppServiceProvider.php :
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Request;
use App\Role;
use App\Permit;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Role::saving(function ($role) {
$permits = Request::get('permits');
$role->setPermissions([]);
if (isset($permits)){
foreach($permits as $permitid)
{
$permit = Permit::find($permitid);
$role->addPermission($permit->slug);
}
}
if ( ! $permits) return;
});
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
}
Поправим навигацию app/Admin/navigation.php:
<?php
use SleepingOwl\Admin\Navigation\Page;
return [
[
'title' => 'Dashboard',
'icon' => 'fa fa-dashboard',
'url' => route('admin.dashboard'),
],
[
'title' => 'Пользователи',
'icon' => 'fa fa-group',
'pages' => [
[
'title' => 'Пользователи',
'icon' => 'fa fa-group',
'url' => 'admin/users',
],
[
'title' => 'Роли',
'icon' => 'fa fa-graduation-cap',
'url' => 'admin/roles',
],
[
'title' => 'Права',
'icon' => 'fa fa-key',
'url' => 'admin/permits',
],
]
],
[
'title' => 'Выйти',
'icon' => 'fa fa-sign-out',
'url' => '/logout',
],
];
Создаем через админку новую роль "Модератор" со slug`ом moderator mysite.com/admin/roles/create
После этого создаем права для работы с публикациями:
Просмотр админки admin
Просмотр списка публикаций admin.posts
Просмотр публикации admin.posts.edit
Создание публикации admin.posts.create
Удаление публикации admin.posts.delete
В итоге, список прав у нас выглядит так:
Добавим для роли "Модератор" созданные права:
Выполним в терминале
php artisan config:cache
В значениях slug'ов прав, с помощью точечной нотации указаны роуты, в отдельных случаях - последним элементом действие - create, edit, delete. Таким образом, пользователь с ролью "Модератор", сможет выполнять указанные действия в админке, в соответствии с роутами, указанными в правах. Если пользователь с ролью "Модератор" перейдет на страницу, без назначенных прав на этот роут, от увидит примерно следующее:
С первоначальной настройкой админки закончили.
Приступаем к настройке заготовки под API.
Создадим контроллер app\Http\Controllers\ApiAuthController.php :
php artisan make:controller ApiAuthController
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use Redirect;
use Sentinel;
use Activation;
use Reminder;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use Validator;
use Mail;
use Storage;
use CurlHttp;
use Dingo\Api\Routing\Helpers;
class ApiAuthController extends Controller
{
use Helpers;
public function authenticate(Request $request)
{
$credentials = $request->only('email', 'password');
try {
if (!$token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
return response()->json(['error' => 'could_not_create_token'], 500);
}
return response()->json(compact('token'), 200);
}
public function refreshToken(){
$token = JWTAuth::getToken();
if(!$token){
return $this->response->error("Token is invalid")->setStatusCode(401);
}
try {
$refreshed_token = JWTAuth::refresh($token);
} catch (JWTException $ex) {
return $this->response->error("Something went wrong with token")->setStatusCode(401);
}
return $this->response->array(compact('refreshed_token'))->setStatusCode(200);
}
public function register(Request $request)
{
$input = $request->all();
$messages = [
'email.required' => 'Email required',
'email.email' => 'It appears that the email entered with error',
'password.required' => 'Enter password',
'password.between' => 'Minimum password length of 8 characters',
'password_confirm.required' => 'Enter password',
'password_confirm.same' => 'The entered passwords do not match',
'password_confirm.between' => 'Minimum password length of 8 characters',
];
$validator = Validator::make($input, [
'email' => 'required|email',
'password' => 'required|between:8,20',
'password_confirm' => 'required|same:password|between:8,20',
], $messages);
if(count($validator->errors()->all()) > 0){
return $this->response->array(['validation_error' => 1, 'error_msgs' => $validator->errors()->all()])->setStatusCode(422);
}
$credentials = [ 'email' => $request->email ];
if($user = Sentinel::findByCredentials($credentials))
{
return $this->response->error("This Email is already registered", 200);
}
if ($sentuser = Sentinel::register($input))
{
$activation = Activation::create($sentuser);
$code = $activation->code;
$sent = Mail::send('mail.account_activate_app', compact('sentuser', 'code'), function($m) use ($sentuser)
{
$m->from('[email protected]', 'MySite');
$m->to($sentuser->email)->subject('Account activation');
});
if ($sent === 0)
{
return $this->response->error("Activation email is not sending", 200);
}
$role = Sentinel::findRoleBySlug('user');
$role->users()->attach($sentuser);
return $this->response->array(['success' => 1, 'msg' => 'Your account has been created. Check Email to activate.'])->setStatusCode(200);
}
return $this->response->error("Failed to register", 200);
}
}
Создаём контроллер app\Http\Controllers\PostController.php :
php artisan make:controller PostController
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use App\Post;
use Dingo\Api\Routing\Helpers;
class PostController extends Controller
{
use Helpers;
public function getPosts(){
$user = JWTAuth::parseToken()->authenticate();
if(!$user){
return $this->response->errorNotFound("authenticate");
}
$data = Post::all();
return $this->response->array(compact('data'))->setStatusCode(200);
}
}
Наполняем routes\api.php :
$api = app('Dingo\Api\Routing\Router');
$api->version('v1', function ($api){
$api->post('authenticate', 'App\Http\Controllers\ApiAuthController@authenticate');
$api->post('register', 'App\Http\Controllers\ApiAuthController@register');
});
$api->version('v1',