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.
- checksums.yaml +4 -4
- data/.travis.yml +12 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +2 -2
- data/README.md +186 -1
- data/app/controllers/concerns/g5_authenticatable/authorization.rb +21 -0
- data/app/models/g5_authenticatable/role.rb +8 -0
- data/app/models/g5_authenticatable/user.rb +41 -0
- data/app/policies/g5_authenticatable/base_policy.rb +73 -0
- data/config/initializers/rolify.rb +8 -0
- data/g5_authenticatable.gemspec +3 -0
- data/lib/g5_authenticatable/engine.rb +3 -0
- data/lib/g5_authenticatable/rspec.rb +1 -0
- data/lib/g5_authenticatable/test/factory.rb +51 -1
- data/lib/g5_authenticatable/test/feature_helpers.rb +15 -2
- data/lib/g5_authenticatable/version.rb +1 -1
- data/lib/generators/g5_authenticatable/install/USAGE +7 -1
- data/lib/generators/g5_authenticatable/install/install_generator.rb +33 -6
- data/lib/generators/g5_authenticatable/install/templates/403.html +26 -0
- data/lib/generators/g5_authenticatable/install/templates/application_policy.rb +4 -0
- data/lib/generators/g5_authenticatable/install/templates/{g5_authenticatable.rb → initializer.rb} +0 -0
- data/lib/generators/g5_authenticatable/install/templates/migrate/add_g5_authenticatable_users_contact_info.rb +11 -0
- data/lib/generators/g5_authenticatable/install/templates/migrate/create_g5_authenticatable_roles.rb +20 -0
- data/lib/generators/g5_authenticatable/install/templates/{create_g5_authenticatable_users.rb → migrate/create_g5_authenticatable_users.rb} +0 -0
- data/spec/controllers/application_controller_spec.rb +12 -0
- data/spec/controllers/concerns/g5_authenticatable/authorization.rb +50 -0
- data/spec/dummy/app/assets/javascripts/posts.js +2 -0
- data/spec/dummy/app/assets/stylesheets/posts.css +4 -0
- data/spec/dummy/app/assets/stylesheets/scaffold.css +56 -0
- data/spec/dummy/app/controllers/application_controller.rb +1 -0
- data/spec/dummy/app/controllers/posts_controller.rb +74 -0
- data/spec/dummy/app/helpers/posts_helper.rb +2 -0
- data/spec/dummy/app/models/post.rb +3 -0
- data/spec/dummy/app/policies/application_policy.rb +4 -0
- data/spec/dummy/app/policies/post_policy.rb +4 -0
- data/spec/dummy/app/views/home/index.html.erb +40 -0
- data/spec/dummy/app/views/posts/_form.html.erb +21 -0
- data/spec/dummy/app/views/posts/edit.html.erb +6 -0
- data/spec/dummy/app/views/posts/index.html.erb +30 -0
- data/spec/dummy/app/views/posts/new.html.erb +5 -0
- data/spec/dummy/app/views/posts/show.html.erb +17 -0
- data/spec/dummy/config/database.yml.ci +1 -2
- data/spec/dummy/config/initializers/g5_authenticatable.rb +8 -0
- data/spec/dummy/config/routes.rb +2 -0
- data/spec/dummy/db/migrate/20150428182339_add_g5_authenticatable_users_contact_info.rb +11 -0
- data/spec/dummy/db/migrate/20150429212919_create_g5_authenticatable_roles.rb +20 -0
- data/spec/dummy/db/migrate/20150509061150_create_posts.rb +9 -0
- data/spec/dummy/db/schema.rb +37 -4
- data/spec/dummy/public/403.html +26 -0
- data/spec/factories/post.rb +6 -0
- data/spec/features/default_role_authorization_spec.rb +254 -0
- data/spec/features/sign_in_spec.rb +144 -8
- data/spec/lib/generators/g5_authenticatable/install_generator_spec.rb +72 -1
- data/spec/models/g5_authenticatable/role_spec.rb +81 -0
- data/spec/models/g5_authenticatable/user_spec.rb +340 -3
- data/spec/models/post_spec.rb +12 -0
- data/spec/policies/application_policy_spec.rb +171 -0
- data/spec/policies/post_policy_spec.rb +35 -0
- data/spec/requests/default_role_authorization_spec.rb +169 -0
- data/spec/spec_helper.rb +0 -3
- data/spec/support/shared_examples/super_admin_authorizer.rb +33 -0
- metadata +109 -5
- 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,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
|
+
}
|
@@ -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
|
@@ -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,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,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 %>
|
@@ -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
|
data/spec/dummy/config/routes.rb
CHANGED
@@ -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
|
data/spec/dummy/db/schema.rb
CHANGED
@@ -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:
|
14
|
+
ActiveRecord::Schema.define(version: 20150509061150) do
|
15
15
|
|
16
|
-
|
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>
|