Статьи

Авторизация с Pundit

Pundit — это инструмент, который позволяет вам ограничивать доступ к определенным частям вашего Rails-приложения только авторизованным пользователям. Это достигается путем предоставления вам определенных помощников.

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

Начните с создания нового приложения Rails.

1
rails new pundit-blog -T

Флаг -T указывает Rails генерировать новое приложение без набора тестов по умолчанию. Выполнение команды сгенерирует ваше Rails-приложение и установит гемы по умолчанию.

Идите вперед и добавьте следующие драгоценные камни в свой Gemfile. Вы будете использовать bootstrap-sass для макета вашего приложения, а Devise будет обрабатывать аутентификацию пользователя.

1
2
3
4
5
#Gemfile
 
gem ‘bootstrap-sass’
gem ‘devise’

Запустите команду, чтобы установить гем.

1
bundle install

Теперь переименуйте app/assets/stylesheets/application.css в app/assets/stylesheets/application.scss . Добавьте следующие строки кода для импорта начальной загрузки.

1
2
3
4
5
#app/assets/stylesheets/application.scss
 
@import ‘bootstrap-sprockets’;
@import ‘bootstrap’;

Создайте _navigation.html.erb с именем _navigation.html.erb для хранения вашего кода навигации; частичное должно быть расположено в каталоге app / views / layouts . Сделайте частичное похожее на то, что у меня ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#app/views/layouts/_navigation.html.erb
 
<nav class=»navbar navbar-inverse»>
  <div class=»container»>
    <div class=»navbar-header»>
      <%= link_to ‘Pundit Blog’, root_path, class: ‘navbar-brand’ %>
    </div>
    <div id=»navbar»>
  
    <ul class=»nav navbar-nav pull-right»>
      <li><% link_to ‘Home’, root_path %></li>
      <ul class=»nav navbar-nav pull-right»>
        <% if user_signed_in?
        <li><%= current_user.email %></li>
        <li><%= link_to ‘Log out’, destroy_user_session_path, method: :delete %></li>
        <% else %>
          <li><%= link_to ‘Log In’, new_user_session_path %></li>
          <li><%= link_to ‘Sign Up’, new_user_registration_path %></li>
        <% end %>
      </ul>
    </ul>
  </div>
</nav>

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#app/views/layouts/application.html.erb
 
<!DOCTYPE html>
<html>
  <head>
    <title>Pundit-Blog</title>
    <%= csrf_meta_tags %>
 
    <%= stylesheet_link_tag ‘application’, media: ‘all’, ‘data-turbolinks-track’: ‘reload’ %>
    <%= javascript_include_tag ‘application’, ‘data-turbolinks-track’: ‘reload’ %>
  </head>
 
  <body>
    <%= render «layouts/navigation» %>
    <div id=»flash»>
      <% flash.each do |key, value|
        <div class=»flash <%= key %>»><%= value %></div>
      <% end %>
    </div>
    <div class=»container-fluid»>
      <%= yield %>
    </div>
  </body>
</html>

Запустите команду для установки Devise.

1
rails generate devise:install

Теперь создайте свою модель пользователя.

1
rails generate devise User

Перенос вашей базы данных.

1
rake db:migrate

Запустите команду для генерации ваших ресурсов статьи.

1
rails generate scaffold Articles title:string body:text

Это сгенерирует ваш ArticlesController и Article Model. Это также сгенерирует необходимые представления.

Теперь перенесите вашу базу данных, запустив:

1
rake db:migrate

Откройте app/views/articles/_form.html.erb и сделайте так, как app/views/articles/_form.html.erb ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#app/views/articles/_form.html.erb
 
<%= form_for(article) do |f|
  <% if article.errors.any?
    <div id=»error_explanation»>
      <h2><%= pluralize(article.errors.count, «error») %> prohibited this article from being saved:</h2>
 
      <ul>
      <% article.errors.full_messages.each do |message|
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>
 
  <div class=»field»>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>
 
  <div class=»field»>
    <%= f.label :body %>
    <%= f.text_area :body %>
  </div>
 
  <div class=»actions»>
    <%= f.submit %>
  </div>
<% end %>

Для вашего индексного файла это должно выглядеть так.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#app/views/articles/index.html.erb
 
<table class=»table table-bordered table-striped table-condensed table-hover»>
  <thead>
  <tr>
    <th>Title</th>
    <th>Body</th>
    <th colspan=»3″></th>
  </tr>
  </thead>
 
  <tbody>
    <% @articles.each do |article|
    <tr>
      <td><%= article.title %></td>
      <td><%= article.body %></td>
      <td><%= link_to ‘Show’, article %></td>
      <td><%= link_to ‘Edit’, edit_article_path(article) %></td>
      <td><%= link_to ‘Destroy’, article, method: :delete, data: { confirm: ‘Are you sure?’
    </tr>
    <% end %>
  </tbody>
</table>
 
<br>
 
<%= link_to ‘New article’, new_article_path %>

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

Откройте файл маршрутов и добавьте маршрут к статьям ресурсов.

1
2
3
4
5
#config/routes.rb
 
  resources :articles
  root to: «articles#index»

Добавьте драгоценный камень Pundit в свой Gemfile.

1
2
3
4
#Gemfile
 
gem ‘pundit’

Запустите команду для установки.

1
bundle install

Интегрируйте Pundit в свое приложение, добавив следующую строку в ApplicationController .

1
2
3
4
5
#app/controllers/application_controller.rb
 
  include Pundit

Запустите генератор Pundit.

1
rails g pundit:install

Это создаст папку приложения / политики, которая содержит базовый класс с политиками. Каждая политика является базовым классом Ruby.

Так выглядит политика базового класса.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#app/policies/application_policy.rb
 
class ApplicationPolicy
  attr_reader :user, :record
 
  def initialize(user, record)
    @user = user
    @record = record
  end
 
  def index?
    false
  end
 
  def show?
    scope.where(:id => record.id).exists?
  end
 
  def create?
    false
  end
 
  def new?
    create?
  end
 
  def update?
    false
  end
 
  def edit?
    update?
  end
 
  def destroy?
    false
  end
 
  def scope
    Pundit.policy_scope!(user, record.class)
  end
 
  class Scope
    attr_reader :user, :scope
 
    def initialize(user, scope)
      @user = user
      @scope = scope
    end
 
    def resolve
      scope
    end
  end
end

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#app/policies/article_policy.rb
 
class ArticlePolicy < ApplicationPolicy
  def index?
    true
  end
 
  def create?
    user.present?
  end
 
  def update?
    return true if user.present?
  end
 
  def destroy?
    return true if user.present?
  end
 
  private
 
    def article
      record
    end
end

Выше вы разрешаете всем (зарегистрированным и незарегистрированным пользователям) видеть страницу указателя. Чтобы создать новую статью, пользователь должен быть зарегистрирован. Вы используете user.present? узнать, зарегистрирован ли пользователь, пытающийся выполнить действие.

Для обновления и удаления вы должны убедиться, что только пользователь, создавший статью, может выполнять эти действия.

На этом этапе вам необходимо установить связь между вашей статьей и моделью пользователя.

Вы делаете это, генерируя новую миграцию.

1
rails generate migration add_user_id_to_articles user:references

Затем перенесите базу данных, выполнив команду:

1
rake db:migrate

Откройте модель User и добавьте линию, которая запечатывает отношения.

1
2
3
4
#app/models/user.rb
 
  has_many :articles

Ваша модель статьи должна иметь это.

1
2
3
4
#app/models/article.rb
 
  belongs_to :user

Теперь вам нужно обновить ваш ArticlesController чтобы он синхронизировался с тем, что вы уже сделали.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#app/controllers/articles_controller.rb
 
class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]
 
  # GET /articles
  # GET /articles.json
  def index
    @articles = Article.all
    authorize @articles
  end
 
  # GET /articles/1
  # GET /articles/1.json
  def show
  end
 
  # GET /articles/new
  def new
    @article = Article.new
    authorize @article
  end
 
  # GET /articles/1/edit
  def edit
  end
 
  # POST /articles
  # POST /articles.json
  def create
    @article = Article.new(article_params)
    @article.user = current_user
    authorize @article
 
    respond_to do |format|
      if @article.save
        format.html { redirect_to @article, notice: ‘Article was successfully created.’
        format.json { render :show, status: :created, location: @article }
      else
        format.html { render :new }
        format.json { render json: @article.errors, status: :unprocessable_entity }
      end
    end
  end
 
  # PATCH/PUT /articles/1
  # PATCH/PUT /articles/1.json
  def update
    respond_to do |format|
      if @article.update(article_params)
        format.html { redirect_to @article, notice: ‘Article was successfully updated.’
        format.json { render :show, status: :ok, location: @article }
      else
        format.html { render :edit }
        format.json { render json: @article.errors, status: :unprocessable_entity }
      end
    end
  end
 
  # DELETE /articles/1
  # DELETE /articles/1.json
  def destroy
    @article.destroy
    respond_to do |format|
      format.html { redirect_to articles_url, notice: ‘Article was successfully destroyed.’
      format.json { head :no_content }
    end
  end
 
  private
    # Use callbacks to share common setup or constraints between actions.
    def set_article
      @article = Article.find(params[:id])
      authorize @article
    end
 
    # Never trust parameters from the scary internet, only allow the white list through.
    def article_params
      params.require(:article).permit(:title, :body, :user_id)
    end
end

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

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

01
02
03
04
05
06
07
08
09
10
11
#app/controllers/application_controller.rb
 
  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
 
  private
 
    def user_not_authorized
      flash[:warning] = «You are not authorized to perform this action.»
      redirect_to(request.referrer || root_path)
    end

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

Бегать:

1
$ rails server

Чтобы запустить свой сервер Rails, укажите в браузере https://localhost:3000 чтобы увидеть, что у вас есть.

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

Вы можете узнать больше о Pundit, посетив страницу GitHub .