g5_authenticatable 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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