g5_authenticatable 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +12 -0
  3. data/CHANGELOG.md +11 -0
  4. data/Gemfile +2 -2
  5. data/README.md +186 -1
  6. data/app/controllers/concerns/g5_authenticatable/authorization.rb +21 -0
  7. data/app/models/g5_authenticatable/role.rb +8 -0
  8. data/app/models/g5_authenticatable/user.rb +41 -0
  9. data/app/policies/g5_authenticatable/base_policy.rb +73 -0
  10. data/config/initializers/rolify.rb +8 -0
  11. data/g5_authenticatable.gemspec +3 -0
  12. data/lib/g5_authenticatable/engine.rb +3 -0
  13. data/lib/g5_authenticatable/rspec.rb +1 -0
  14. data/lib/g5_authenticatable/test/factory.rb +51 -1
  15. data/lib/g5_authenticatable/test/feature_helpers.rb +15 -2
  16. data/lib/g5_authenticatable/version.rb +1 -1
  17. data/lib/generators/g5_authenticatable/install/USAGE +7 -1
  18. data/lib/generators/g5_authenticatable/install/install_generator.rb +33 -6
  19. data/lib/generators/g5_authenticatable/install/templates/403.html +26 -0
  20. data/lib/generators/g5_authenticatable/install/templates/application_policy.rb +4 -0
  21. data/lib/generators/g5_authenticatable/install/templates/{g5_authenticatable.rb → initializer.rb} +0 -0
  22. data/lib/generators/g5_authenticatable/install/templates/migrate/add_g5_authenticatable_users_contact_info.rb +11 -0
  23. data/lib/generators/g5_authenticatable/install/templates/migrate/create_g5_authenticatable_roles.rb +20 -0
  24. data/lib/generators/g5_authenticatable/install/templates/{create_g5_authenticatable_users.rb → migrate/create_g5_authenticatable_users.rb} +0 -0
  25. data/spec/controllers/application_controller_spec.rb +12 -0
  26. data/spec/controllers/concerns/g5_authenticatable/authorization.rb +50 -0
  27. data/spec/dummy/app/assets/javascripts/posts.js +2 -0
  28. data/spec/dummy/app/assets/stylesheets/posts.css +4 -0
  29. data/spec/dummy/app/assets/stylesheets/scaffold.css +56 -0
  30. data/spec/dummy/app/controllers/application_controller.rb +1 -0
  31. data/spec/dummy/app/controllers/posts_controller.rb +74 -0
  32. data/spec/dummy/app/helpers/posts_helper.rb +2 -0
  33. data/spec/dummy/app/models/post.rb +3 -0
  34. data/spec/dummy/app/policies/application_policy.rb +4 -0
  35. data/spec/dummy/app/policies/post_policy.rb +4 -0
  36. data/spec/dummy/app/views/home/index.html.erb +40 -0
  37. data/spec/dummy/app/views/posts/_form.html.erb +21 -0
  38. data/spec/dummy/app/views/posts/edit.html.erb +6 -0
  39. data/spec/dummy/app/views/posts/index.html.erb +30 -0
  40. data/spec/dummy/app/views/posts/new.html.erb +5 -0
  41. data/spec/dummy/app/views/posts/show.html.erb +17 -0
  42. data/spec/dummy/config/database.yml.ci +1 -2
  43. data/spec/dummy/config/initializers/g5_authenticatable.rb +8 -0
  44. data/spec/dummy/config/routes.rb +2 -0
  45. data/spec/dummy/db/migrate/20150428182339_add_g5_authenticatable_users_contact_info.rb +11 -0
  46. data/spec/dummy/db/migrate/20150429212919_create_g5_authenticatable_roles.rb +20 -0
  47. data/spec/dummy/db/migrate/20150509061150_create_posts.rb +9 -0
  48. data/spec/dummy/db/schema.rb +37 -4
  49. data/spec/dummy/public/403.html +26 -0
  50. data/spec/factories/post.rb +6 -0
  51. data/spec/features/default_role_authorization_spec.rb +254 -0
  52. data/spec/features/sign_in_spec.rb +144 -8
  53. data/spec/lib/generators/g5_authenticatable/install_generator_spec.rb +72 -1
  54. data/spec/models/g5_authenticatable/role_spec.rb +81 -0
  55. data/spec/models/g5_authenticatable/user_spec.rb +340 -3
  56. data/spec/models/post_spec.rb +12 -0
  57. data/spec/policies/application_policy_spec.rb +171 -0
  58. data/spec/policies/post_policy_spec.rb +35 -0
  59. data/spec/requests/default_role_authorization_spec.rb +169 -0
  60. data/spec/spec_helper.rb +0 -3
  61. data/spec/support/shared_examples/super_admin_authorizer.rb +33 -0
  62. metadata +109 -5
  63. data/circle.yml +0 -4
@@ -28,4 +28,16 @@ describe ::ApplicationController do
28
28
 
29
29
  it_should_behave_like 'a secure controller'
30
30
  end
31
+
32
+ it 'should mixin pundit authorization' do
33
+ expect(controller).to respond_to(:authorize)
34
+ end
35
+
36
+ it 'should mixin pundit scoping' do
37
+ expect(controller).to respond_to(:policy_scope)
38
+ end
39
+
40
+ it 'should mixin authorization error handling' do
41
+ expect(controller).to respond_to(:user_not_authorized)
42
+ end
31
43
  end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe G5Authenticatable::Authorization, type: :controller do
4
+ controller(ActionController::Base) do
5
+ include G5Authenticatable::Authorization
6
+
7
+ def index
8
+ raise Pundit::NotAuthorizedError.new(query: 'index?', record: 'mock_record')
9
+ end
10
+ end
11
+
12
+ it 'should mixin the authorize method' do
13
+ expect(controller).to respond_to(:authorize)
14
+ end
15
+
16
+ it 'should mixin the policy_scope method' do
17
+ expect(controller).to respond_to(:policy_scope)
18
+ end
19
+
20
+ describe '#user_not_authorized' do
21
+ subject(:user_not_authorized) { get :index, format: format }
22
+
23
+ context 'when format is json' do
24
+ let(:format) { :json }
25
+
26
+ it 'is forbidden' do
27
+ user_not_authorized
28
+ expect(response).to be_forbidden
29
+ end
30
+
31
+ it 'renders the json error message' do
32
+ user_not_authorized
33
+ expect(JSON.parse(response.body)).to eq({'error' => 'Access forbidden'})
34
+ end
35
+ end
36
+
37
+ context 'when format is html' do
38
+ let(:format) { :html }
39
+ it 'is forbidden' do
40
+ user_not_authorized
41
+ expect(response).to be_forbidden
42
+ end
43
+
44
+ it 'renders the static 403.html' do
45
+ user_not_authorized
46
+ expect(response).to render_template(file: "#{Rails.root}/public/403.html")
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,56 @@
1
+ body { background-color: #fff; color: #333; }
2
+
3
+ body, p, ol, ul, td {
4
+ font-family: verdana, arial, helvetica, sans-serif;
5
+ font-size: 13px;
6
+ line-height: 18px;
7
+ }
8
+
9
+ pre {
10
+ background-color: #eee;
11
+ padding: 10px;
12
+ font-size: 11px;
13
+ }
14
+
15
+ a { color: #000; }
16
+ a:visited { color: #666; }
17
+ a:hover { color: #fff; background-color:#000; }
18
+
19
+ div.field, div.actions {
20
+ margin-bottom: 10px;
21
+ }
22
+
23
+ #notice {
24
+ color: green;
25
+ }
26
+
27
+ .field_with_errors {
28
+ padding: 2px;
29
+ background-color: red;
30
+ display: table;
31
+ }
32
+
33
+ #error_explanation {
34
+ width: 450px;
35
+ border: 2px solid red;
36
+ padding: 7px;
37
+ padding-bottom: 0;
38
+ margin-bottom: 20px;
39
+ background-color: #f0f0f0;
40
+ }
41
+
42
+ #error_explanation h2 {
43
+ text-align: left;
44
+ font-weight: bold;
45
+ padding: 5px 5px 5px 15px;
46
+ font-size: 12px;
47
+ margin: -7px;
48
+ margin-bottom: 0px;
49
+ background-color: #c00;
50
+ color: #fff;
51
+ }
52
+
53
+ #error_explanation ul li {
54
+ font-size: 12px;
55
+ list-style: square;
56
+ }
@@ -1,3 +1,4 @@
1
1
  class ApplicationController < ActionController::Base
2
+ include G5Authenticatable::Authorization
2
3
  protect_from_forgery
3
4
  end
@@ -0,0 +1,74 @@
1
+ class PostsController < ApplicationController
2
+ before_action :authenticate_api_user!, unless: :is_navigational_format?
3
+ before_action :authenticate_user!, if: :is_navigational_format?
4
+
5
+ before_action :set_post, only: [:show, :edit, :update, :destroy]
6
+
7
+ respond_to :json, except: [:new, :edit]
8
+ respond_to :html
9
+
10
+ # GET /posts
11
+ def index
12
+ authorize(Post)
13
+ @posts = policy_scope(Post)
14
+ respond_with(@posts)
15
+ end
16
+
17
+ # GET /posts/1
18
+ def show
19
+ authorize(@post)
20
+ respond_with(@post)
21
+ end
22
+
23
+ # GET /posts/new
24
+ def new
25
+ @post = Post.new
26
+ authorize(@post)
27
+ respond_with(@post)
28
+ end
29
+
30
+ # GET /posts/1/edit
31
+ def edit
32
+ authorize(@post)
33
+ respond_with(@post)
34
+ end
35
+
36
+ # POST /posts
37
+ def create
38
+ @post = Post.new(post_params)
39
+ authorize(@post)
40
+ if @post.save
41
+ flash[:notice] = 'Post was successfully created.'
42
+ end
43
+ respond_with(@post)
44
+ end
45
+
46
+ # PATCH/PUT /posts/1
47
+ def update
48
+ authorize(@post)
49
+ if @post.update(post_params)
50
+ flash[:notice] = 'Post was successfully updated.'
51
+ end
52
+ respond_with(@post)
53
+ end
54
+
55
+ # DELETE /posts/1
56
+ def destroy
57
+ authorize(@post)
58
+ if @post.destroy
59
+ flash[:notice] = 'Post was successfully destroyed.'
60
+ end
61
+ respond_with(@post)
62
+ end
63
+
64
+ private
65
+ # Use callbacks to share common setup or constraints between actions.
66
+ def set_post
67
+ @post = Post.find(params[:id])
68
+ end
69
+
70
+ # Only allow a trusted parameter "white list" through.
71
+ def post_params
72
+ params.require(:post).permit(:content, :author_id)
73
+ end
74
+ end
@@ -0,0 +1,2 @@
1
+ module PostsHelper
2
+ end
@@ -0,0 +1,3 @@
1
+ class Post < ActiveRecord::Base
2
+ belongs_to :author, class: G5Authenticatable::User
3
+ end
@@ -0,0 +1,4 @@
1
+ class ApplicationPolicy < G5Authenticatable::BasePolicy
2
+ class Scope < BaseScope
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ class PostPolicy < ApplicationPolicy
2
+ class Scope < Scope
3
+ end
4
+ end
@@ -1 +1,41 @@
1
1
  <p>Welcome to the dummy application!</p>
2
+
3
+ <% if current_user %>
4
+ <div id="email" class="row">
5
+ <h4>Email:</h4>
6
+ <%= current_user.email %>
7
+ </div>
8
+ <div id="first_name" class="row">
9
+ <h4>First Name:</h4>
10
+ <%= current_user.first_name %>
11
+ </div>
12
+ <div id="last_name" class="row">
13
+ <h4>Last Name:</h4>
14
+ <%= current_user.last_name %>
15
+ </div>
16
+ <div id="phone_number" class="row">
17
+ <h4>Phone Number:</h4>
18
+ <%= current_user.phone_number %>
19
+ </div>
20
+ <div id="g5_access_token" class="row">
21
+ <h4>Access Token:</h4>
22
+ <%= current_user.g5_access_token %>
23
+ </div>
24
+ <div id="title" class="row">
25
+ <h4>Title:</h4>
26
+ <%= current_user.title %>
27
+ </div>
28
+ <div id="organization_name" class="row">
29
+ <h4>Organization Name:</h4>
30
+ <%= current_user.organization_name %>
31
+ </div>
32
+ <div id="roles" class="row">
33
+ <h4>Roles:</h4>
34
+ <% current_user.roles.each do |role| %>
35
+ <div>
36
+ <h5>Role Name:<h5>
37
+ <%= role.name %>
38
+ </div>
39
+ <% end %>
40
+ </div>
41
+ <% end %>
@@ -0,0 +1,21 @@
1
+ <%= form_for(@post) do |f| %>
2
+ <% if @post.errors.any? %>
3
+ <div id="error_explanation">
4
+ <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>
5
+
6
+ <ul>
7
+ <% @post.errors.full_messages.each do |message| %>
8
+ <li><%= message %></li>
9
+ <% end %>
10
+ </ul>
11
+ </div>
12
+ <% end %>
13
+
14
+ <%= f.label :content %>:
15
+ <%= f.text_field :content %>
16
+ <input name="post[author_id]" type="hidden" value="<%= current_user.id %>">
17
+
18
+ <div class="actions">
19
+ <%= f.submit %>
20
+ </div>
21
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <h1>Editing Post</h1>
2
+
3
+ <%= render 'form' %>
4
+
5
+ <%= link_to 'Show', @post %> |
6
+ <%= link_to 'Back', posts_path %>
@@ -0,0 +1,30 @@
1
+ <p id="notice"><%= notice %></p>
2
+
3
+ <h1>Listing Posts</h1>
4
+
5
+ <table>
6
+ <thead>
7
+ <tr>
8
+ <th>Content</th>
9
+ <th>Author</th>
10
+ <th colspan="3">Actions</th>
11
+ </tr>
12
+ </thead>
13
+
14
+ <tbody>
15
+
16
+ <% @posts.each do |post| %>
17
+ <tr>
18
+ <td><%= post.content %></td>
19
+ <td><%= post.author.first_name %> <%= post.author.last_name %></td>
20
+ <td><%= link_to 'Show', post %></td>
21
+ <td><%= link_to 'Edit', edit_post_path(post) %></td>
22
+ <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
23
+ </tr>
24
+ <% end %>
25
+ </tbody>
26
+ </table>
27
+
28
+ <br>
29
+
30
+ <%= link_to 'New Post', new_post_path %>
@@ -0,0 +1,5 @@
1
+ <h1>New Post</h1>
2
+
3
+ <%= render 'form' %>
4
+
5
+ <%= link_to 'Back', posts_path %>
@@ -0,0 +1,17 @@
1
+ <p id="notice"><%= notice %></p>
2
+
3
+ <div>
4
+ <h1>Content</h1>
5
+ <p><%= @post.content %></p>
6
+ </div>
7
+ <div>
8
+ <h1>Author</h1>
9
+ <p><%= @post.author.first_name %> <%= @post.author.last_name %></p>
10
+ </div>
11
+ <div>
12
+ <h1>Created At</h1>
13
+ <p><%= @post.created_at %><p>
14
+ </div>
15
+
16
+ <%= link_to 'Edit', edit_post_path(@post) %> |
17
+ <%= link_to 'Back', posts_path %>
@@ -2,5 +2,4 @@ test:
2
2
  adapter: postgresql
3
3
  encoding: unicode
4
4
  database: g5_authenticatable_test
5
- pool: 5
6
- username: ubuntu
5
+ username: postgres
@@ -0,0 +1,8 @@
1
+ # Enable strict token validation to guarantee that an authenticated
2
+ # user's access token is still valid on the global auth server, at the
3
+ # cost of performance. When disabled, the user's access token will
4
+ # not be repeatedly validated, but this means that the local
5
+ # session may persist long after the access token is revoked on the
6
+ # auth server. Disabled by default.
7
+ #
8
+ # G5Authenticatable.strict_token_validation = true
@@ -1,4 +1,6 @@
1
1
  Rails.application.routes.draw do
2
+ resources :posts
3
+
2
4
  resource :home, only: [:index, :show]
3
5
 
4
6
  get '/protected_page', to: 'home#show', as: :protected_page
@@ -0,0 +1,11 @@
1
+ class AddG5AuthenticatableUsersContactInfo < ActiveRecord::Migration
2
+ def change
3
+ change_table(:g5_authenticatable_users) do |t|
4
+ t.string :first_name
5
+ t.string :last_name
6
+ t.string :phone_number
7
+ t.string :title
8
+ t.string :organization_name
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ class CreateG5AuthenticatableRoles < ActiveRecord::Migration
2
+ def change
3
+ create_table(:g5_authenticatable_roles) do |t|
4
+ t.string :name
5
+ t.references :resource, :polymorphic => true
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ create_table(:g5_authenticatable_users_roles, :id => false) do |t|
11
+ t.references :user
12
+ t.references :role
13
+ end
14
+
15
+ add_index(:g5_authenticatable_roles, :name)
16
+ add_index(:g5_authenticatable_roles, [ :name, :resource_type, :resource_id ],
17
+ name: 'index_g5_authenticatable_roles_on_name_and_resource')
18
+ add_index(:g5_authenticatable_users_roles, [ :user_id, :role_id ])
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ class CreatePosts < ActiveRecord::Migration
2
+ def change
3
+ create_table :posts do |t|
4
+ t.integer :author_id
5
+ t.string :content
6
+ t.timestamps null: false
7
+ end
8
+ end
9
+ end
@@ -11,9 +11,23 @@
11
11
  #
12
12
  # It's strongly recommended that you check this file into your version control system.
13
13
 
14
- ActiveRecord::Schema.define(version: 20140206070137) do
14
+ ActiveRecord::Schema.define(version: 20150509061150) do
15
15
 
16
- create_table "g5_authenticatable_users", force: true do |t|
16
+ # These are extensions that must be enabled in order to support this database
17
+ enable_extension "plpgsql"
18
+
19
+ create_table "g5_authenticatable_roles", force: :cascade do |t|
20
+ t.string "name"
21
+ t.integer "resource_id"
22
+ t.string "resource_type"
23
+ t.datetime "created_at"
24
+ t.datetime "updated_at"
25
+ end
26
+
27
+ add_index "g5_authenticatable_roles", ["name", "resource_type", "resource_id"], name: "index_g5_authenticatable_roles_on_name_and_resource", using: :btree
28
+ add_index "g5_authenticatable_roles", ["name"], name: "index_g5_authenticatable_roles_on_name", using: :btree
29
+
30
+ create_table "g5_authenticatable_users", force: :cascade do |t|
17
31
  t.string "email", default: "", null: false
18
32
  t.string "provider", default: "g5", null: false
19
33
  t.string "uid", null: false
@@ -25,9 +39,28 @@ ActiveRecord::Schema.define(version: 20140206070137) do
25
39
  t.string "last_sign_in_ip"
26
40
  t.datetime "created_at"
27
41
  t.datetime "updated_at"
42
+ t.string "first_name"
43
+ t.string "last_name"
44
+ t.string "phone_number"
45
+ t.string "title"
46
+ t.string "organization_name"
28
47
  end
29
48
 
30
- add_index "g5_authenticatable_users", ["email"], name: "index_g5_authenticatable_users_on_email", unique: true
31
- add_index "g5_authenticatable_users", ["provider", "uid"], name: "index_g5_authenticatable_users_on_provider_and_uid", unique: true
49
+ add_index "g5_authenticatable_users", ["email"], name: "index_g5_authenticatable_users_on_email", unique: true, using: :btree
50
+ add_index "g5_authenticatable_users", ["provider", "uid"], name: "index_g5_authenticatable_users_on_provider_and_uid", unique: true, using: :btree
51
+
52
+ create_table "g5_authenticatable_users_roles", id: false, force: :cascade do |t|
53
+ t.integer "user_id"
54
+ t.integer "role_id"
55
+ end
56
+
57
+ add_index "g5_authenticatable_users_roles", ["user_id", "role_id"], name: "index_g5_authenticatable_users_roles_on_user_id_and_role_id", using: :btree
58
+
59
+ create_table "posts", force: :cascade do |t|
60
+ t.integer "author_id"
61
+ t.string "content"
62
+ t.datetime "created_at", null: false
63
+ t.datetime "updated_at", null: false
64
+ end
32
65
 
33
66
  end
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Access forbidden (403)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/403.html -->
21
+ <div class="dialog">
22
+ <h1>Access forbidden</h1>
23
+ <p>You do not have permission to access this page.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,6 @@
1
+ FactoryGirl.define do
2
+ factory :post do
3
+ sequence(:content) { |n| "This is my post #{n}" }
4
+ association :author, factory: :g5_authenticatable_user
5
+ end
6
+ end