- Read Tutorial
Now that we have our bright and shiny Admin Dashboard, now it's time for us to ensure that only authorized users are able to access it. If you remember back a few guides ago we implemented a new AdminUser
class using single table inheritance. In this guide we'll use that class in order to have a clean way to interface with our permission structure. The components that we'll use to build this feature are:
RSpec
for testingAdminUser
class for ensuring only authorized users access the dashboardAdministrate
for the dashboard itself
In connecting each of these items it would be easy to create a messy implementation that would be difficult to scale. Therefore our goal should be to ensure that there is a low level of coupling between the modules. For example, if we decided to add another type of users that could access the dashboard, such as a SuperAdmin
class. We should not have to make changes throughout the application. So let's keep this in mind while building.
Implementing Tests
Let's begin with writing RSpec tests for what we want our feature to accomplish. We'll start with ensuring that the dashboard can be reached, essentially giving our tests a base case. Let's first create a file that will keep all of the specs related to the admin dashboard:
touch spec/features/admin_dashboard_spec.rb
Finding the Admin Path in Administrate
Before we can create an expectation, we need to know what naming structure that the Administrate
gem uses for the dashboard. We can find this by running the command rake routes | grep admin
and we'll see the full set of routes.
That admin_root
looks promising, let's try that in our expectation.
# spec/features/admin_dashboard_spec.rb require 'rails_helper' describe 'admin dashboard' do it 'can be reached successfully' do visit admin_root_path expect(page.status_code).to eq(200) end end
Running the tests will show that it's working, so at this stage we're all green.
Next we'll create a test that checks that a user not of the type AdminUser
is able to access the page.
# spec/features/admin_dashboard_spec.rb it 'cannot be reached by a non admin users' do user = FactoryGirl.create(:user) login_as(user, :scope => :user) visit admin_root_path expect(current_path).to eq(root_path) end
This test logs in a non admin user and then attempts to visit the admin dashboard. From that point it checks to see if the user is redirected to the homepage. As expected this test fails since right now we haven't implemented any permission structure for the dashboard route.
Integrating Permission Structure
Thankfully, the Administrate
gem provides a way to easily check if a users is an admin or not. Let's open the Admin::ApplicationController
file and add a check for the admin inside of the authenticate_admin
method:
# app/controllers/admin/application_controller.rb module Admin class ApplicationController < Administrate::ApplicationController before_filter :authenticate_admin def authenticate_admin unless current_user.try(:type) == 'AdminUser' flash[:alert] = "You are not authorized to access this page." redirect_to(root_path) end end end end
This is one of the rare times when I'll use the Ruby unless
conditional. However in this case I like how it reads, essentially explaining what we want to happen in nearly plain language. The method is saying unless the user is an AdminUser
, give them this message and redirect them to the homepage.
This is a handy way to implement authentication for the full app dashboard. If you run the tests now you'll see that they're all passing. Let's also test this in the browser. Let's first log in as a regular user. As you'll see if we try to access the admin
dashboard we'll discover that we're redirected to the homepage and have been informed that we're not authorized to access that page.
Now if we log out and log back in as an admin user we'll see that we can access the page, so this is working properly.
Updating the tests
Because this is such an important feature, let's add another test to ensure that authorized admins are able to access the dashboard. Right now we're only checking for the negative case: where an unauthorized user tries to access the admin site. But in order for our tests to be comprehensive we should test for both types of users. Let add in the following spec:
# spec/features/admin_dashboard_spec.rb it 'cannot be reached by a non admin users' do user = FactoryGirl.create(:admin_user) login_as(user, :scope => :user) visit admin_root_path expect(current_path).to eq(admin_root_path) end
After running rspec
you'll see that all of our tests are passing.
Refactor
Everything is working nicely. I can only see one element that I want to refactor right now. If you remember to the start of the lesson, I said that our implementation should be scalable. I explicitly said that we should have the ability to have multiple admin types. However our current setup would require us to add multiple conditionals in the Admin::ApplicationController
, which would be a poor practice. Let's create a new method that can store a list of admin types, and place it in the Admin
module itself.
The ApplicationController
is a data flow class, and therefore it would be a poor choice for placing a list of admin types, however the Admin
module seems like a good fit for this method. As a rule of thumb, in OOP, classes should be able to be described in a single sentence. For example, our Admin::ApplicationController
file can describe itself by saying:
"I control the data flow for the admin dashboard"
However if we choose to list our admin user types in it, the class would say:
"I control the data flow for the admin dashboard and I list the admin user types"
As soon as a class description includes an and
or or
, it means that it's probably time to create a new class or module. stepping off OOP design soapbox...
Next we'll want to have the authenticate_admin
method look inside our new list of admin types to see if the current user matches any of them. The final code should look like this:
# app/controllers/admin/application_controller.rb module Admin def self.admin_types ['AdminUser'] end class ApplicationController < Administrate::ApplicationController before_filter :authenticate_admin def authenticate_admin unless Admin.admin_types.include?(current_user.try(:type)) flash[:alert] = "You are not authorized to access this page." redirect_to(root_path) end end end end
If you run the tests you'll see that they're all passing and now we have a more scalable approach to admin types.
What's Next?
Everything is looking good with this feature and our permission structure looks solid for the admin dashboard. In the next guide we'll walk through the various features and customizations available with the Administrate gem.