Таким образом, этот пример включает ограничение того, что может быть обновлено в объекте, на основе разрешения пользователя. Мы рассмотрели примеры, когда сущность либо можно обновить, либо нельзя. Что, если вы хотите, чтобы только части объекта обновлялись одним разрешением, а другим частям добавлялось другое разрешение? В этом примере мы хотим, чтобы владельцы кафе обновляли метаданные кафе (название, веб-сайт и т. д.), но мы хотим, чтобы администраторы и суперадминистраторы меняли владельцев. Теперь не имеет смысла иметь для этого два отдельных экрана, мы должны сделать его красивым и организованным, где можно редактировать больше информации, если у пользователя более высокий уровень разрешений.

Давайте начнем.

Поэтому мы добавили административный маршрут для компаний в /admin/companies. Все модули, маршруты и код API можно посмотреть здесь: GitHub — serversideup/roastandbrew. По сути, на экране перечислены все компании, видимые пользователю. Это означает, что если пользователь является администратором или суперадминистратором, все компании в приложении перечислены, а если бы они были владельцами, то перечислены только компании, которым они принадлежат. Затем вы можете нажать на компанию, просмотреть кафе и отредактировать информацию на уровне компании. На странице /admin/companies/{company} вы можете нажать на отдельное кафе и отредактировать данные о кафе.

Давайте сосредоточимся на маршруте /admin/companies/{company}. Здесь мы хотим запретить владельцам кофеен добавлять или удалять владельцев, но показать возможность администраторам и суперадминистраторам добавлять и удалять владельцев.

Откройте файл /resources/assets/js/pages/admin/Company.vue. В этот файл мы загружаем просматриваемую/редактируемую компанию. На чем мы хотим сосредоточиться:

<div class="grid-x">
  <div class="large-8 medium-12 cell">
    <label>Owners</label>
    <div class="no-owners" v-show="owners.length == 0">N/A</div>
    <div class="owner" v-for="(owner, key) in owners">
      <router-link v-if="user.permission > 1" :to="{name: 'admin-user', params: { 'id': owner.id } }">{{ owner.name }}</router-link>
      <span v-if="user.permission == 1">{{ owner.name }}</span>

      <a class="remove-owner" v-if="user.permission > 1" v-on:click="removeOwner( key )">Remove</a>
    </div>

    <div class="user-selection-container" v-if="user.permission > 1">
      <input type="text" class="new-owner" v-model="newOwner" v-on:keyup="searchUsers()" placeholder="Add An Owner"/>

      <div class="user-autocomplete-container" v-show="newOwner.length > 0 && showAutocomplete">
        <div class="user-autocomplete" v-for="user in newOwnerResults" v-on:click="selectUser( user )">
          <span class="user-name">{{ user.name }}</span>
        </div>
      </div>
    </div>
  </div>
</div>

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

Однако мы скрываем кнопку Remove, когда у пользователя есть разрешение меньше 1. Также мы скрываем ввод для поиска пользователей, если права загруженного пользователя меньше 1. Теперь это хорошо и здорово на переднем конце, но нам нужно сделать его более безопасным. Как я уже упоминал ранее, это интерфейсный код. По сути, самый высокий уровень уверенности, который вы можете получить, — это заблокировать этот код, это упрощает UX для аутентифицированного пользователя. Они могли крутить и изменять javascript интерфейса по своему желанию. Вам также нужно будет заблокировать серверную часть для реальной безопасности.

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

Давайте посмотрим на маршрут, который обрабатывает обновление компании. Если вы откроете /routes/api.php, вы увидите маршрут:

/*
  |-------------------------------------------------------------------------------
  | Updates An Individual Company
  |-------------------------------------------------------------------------------
  | URL:            /api/v1/admin/companies/{id}
  | Controller:     API\Admin\CompaniesController@putUpdateCompany
  | Method:         PUT
  | Description:    Updates an individual company.
  */
  Route::put('/companies/{company}', 'API\Admin\CompaniesController@putUpdateCompany')
       ->middleware('can:update,company');

Теперь этот маршрут обернут промежуточным программным обеспечением owner, поэтому, по крайней мере, для доступа к маршруту вы должны быть владельцем. Следующее промежуточное ПО применяется через наш app/Policies/CompanyPolicy.php, то есть «может ли пользователь обновить компанию»? Если они могут, то они должны быть владельцами компании, поэтому дайте им доступ к маршруту. Маршрут обрабатывается app/Http/Controllers/API/Admin/CompaniesController@putUpdateCompany. Если вы посмотрите на этот метод, у нас есть CompanyService, который обрабатывает обновление компании.

Откройте app/Services/CompanyService.php и найдите метод updateCompany(). Здесь мы заблокируем доступ пользователям, пытающимся обновить владельцев, у которых нет разрешения.

Для этого мы обернули функциональность в проверку политик следующим образом:

if( Auth::user()->can('updateOwners', $company ) ){
    ...
}

и передал компанию методу.

В нашем app/Policies/CompanyPolicy.php мы добавили следующий метод:

/**
* If the user is an admin they can update owners on a company.
*
* @param \App\Models\User $user
*/
public function updateOwners( User $user ){
  if( $user->permission == 2 || $user->permission == 3 ){
    return true;
  }else{
    return false;
  }
}

Это проверяет, имеет ли пользователь (первый параметр) уровень разрешений 2 или 3. Это означает, что он является администратором или суперадминистратором. Если это так, мы позволяем им обновлять владельцев.

Итак, что мы сделали, так это заблокировали настройку владельца на стороне внешнего интерфейса, если у пользователя не было разрешений. Что касается API, мы сначала навсегда заблокировали пользователя через промежуточное ПО, чтобы отфильтровать пользователей, которые хотя бы не были владельцами. Затем в политике компании мы создали политику, определяющую, могут ли пользователи обновлять информацию о владельцах, определяя, был ли пользователь администратором или суперадминистратором. Затем мы применили эту политику в методе фиксации изменений в базе данных.

Таким образом, ДАЖЕ если пользователь представит владельцев без прав на их изменение, они не будут зафиксированы в базе данных, а код будет проигнорирован.

Подобные ситуации могут возникать очень часто. Еще больше мест в нашем приложении. Если вы просматриваете код, мы не хотим, чтобы администраторы повышались до суперадминистраторов, но мы хотим, чтобы администраторы повышались до администраторов. Очень похожая функциональность имеет место, когда у нас есть политика, которая либо разрешает, либо блокирует, может ли пользователь повышаться до администратора/суперадминистратора или нет.

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

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

Сначала мы блокируем главную страницу, что очень похоже на блокировку владельцем. У нас есть свойство meta в маршруте, которое устанавливает permission равным super-admin следующим образом:

{
  path: 'brew-methods',
  name: 'admin-brew-methods',
  component: Vue.component( 'AdminBrewMethods', require( './pages/admin/BrewMethods.vue' ) ),
  meta: {
    permission: 'super-admin'
  }
},
{
  path: 'brew-methods/:id',
  name: 'admin-brew-method',
  component: Vue.component( 'AdminBrewMethod', require( './pages/admin/BrewMethod.vue' ) ),
  meta: {
    permission: 'super-admin'
  }
},

Оттуда мы блокируем каждый раз, когда текущий вошедший в систему пользователь пытается получить доступ к странице, если он не является суперадминистратором. Затем нам нужно заблокировать маршруты API на стороне администратора, чтобы разрешить доступ только суперадминистратора. В этом случае мы создали промежуточное ПО для блокировки маршрутов и отсутствия политики. Это полезно, потому что мы просто хотим заблокировать весь маршрут, если пользователь не является администратором. Мы могли бы сделать политику, но мне больше нравится использовать политики для мелкозернистого контроля.

Если вы откроете app/Http/Middleware/SuperAdmin.php, вы увидите промежуточное ПО, которое блокирует маршрут. Метод выглядит так:

/**
 * Handle an incoming request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Closure  $next
 * @param  string|null  $guard
 * @return mixed
 */
public function handle($request, Closure $next, $guard = null)
{
    /*
      Any user with a permission less than 3 is not a super admin and
      receives a 403 un authorized action response.
    */
    if (Auth::user()->permission < 3 ) {
        abort(403, 'Unauthorized action.');
    }

    return $next($request);
}

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

Далее мы углубимся в динамическую регистрацию модулей Vuex.

Регистрация модуля Vuex на основе разрешения

В последнем уроке Экраны администрирования приложения VueJS мы коснулись динамической регистрации модулей vue для административной части. Это позволяет нам регистрировать модули только в том случае, если пользователь является администратором и ему необходимо использовать данные администратора. Мы отменяем регистрацию этих модулей, когда пользователь возвращается в приложение.

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

Если мы откроем наш файл resources/assets/js/layouts/Admin.vue, мы будем работать над импортом наших модулей и их динамической регистрацией на основе уровня разрешений аутентифицированного пользователя.

Мы будем работать с двумя модулями уровня администратора. Модуль users, для которого требуется как минимум администратор, и модуль brewMethods, для которого требуется суперадминистратор.

Во-первых, мы импортируем модули, которые будут использоваться следующим образом:

import { users } from '../modules/admin/users.js';
import { brewMethods } from '../modules/admin/brewMethods.js';

Далее, в наш созданный хук жизненного цикла мы добавим следующий код:

/*
  Checks to see if the user has permissions and if the
  Vuex users module is loaded.
*/
if( !this.$store._modules.get(['admin', 'users'] ) && this.user.permission >= 2 ){
  this.$store.registerModule( ['admin', 'users'], users );
}

/*
  Checks to see if the user has permissions and if the
  Vuex brew methods module is loaded.
*/
if( !this.$store._modules.get(['admin', 'brewMethods'] ) && this.user.permission == 3 ){
  this.$store.registerModule( ['admin', 'brewMethods'], brewMethods );
}

Что это делает, так это проверяет разрешения пользователя и регистрирует модуль, только если у него есть разрешение. Причина, по которой мне нравится это делать, заключается в том, что неавторизованные пользователи не могут легко увидеть структуру данных. Мы также очищаем все данные на стороне администратора, когда пользователь возвращается в приложение. Это обе вещи, которые кто-то может настроить во внешнем интерфейсе с помощью кода Javascript (почему мы защищаем API), но это также помогает просто с UX и чистотой, не загружая то, что нам не нужно.

Обновления

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

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

Мы также обновили внешний запрос, чтобы он не возвращал компании, которые были удалены при добавлении и редактировании кафе.

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

Подводить итоги

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

Если у вас есть какие-либо вопросы о том, как добавить дополнительные функции администратора в ваше приложение, пожалуйста, свяжитесь с нами в комментариях ниже.

Для получения дополнительных примеров и более масштабного приложения мы пишем книгу о том, как выполнять разработку на основе API в одностраничном приложении, и рассмотрим гораздо более подробный процесс. Подпишитесь на уведомления о разработке книги здесь: Server Side Up General List.