bsm_oa 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +180 -0
  6. data/Rakefile +21 -0
  7. data/app/controllers/bsm_oa/accounts_controller.rb +12 -0
  8. data/app/controllers/bsm_oa/applications_controller.rb +35 -0
  9. data/app/controllers/bsm_oa/authorizations_controller.rb +80 -0
  10. data/app/controllers/bsm_oa/roles_controller.rb +58 -0
  11. data/app/views/bsm_oa/accounts/show.json.jbuilder +7 -0
  12. data/app/views/bsm_oa/applications/_application.html.erb +15 -0
  13. data/app/views/bsm_oa/applications/_application.json.jbuilder +1 -0
  14. data/app/views/bsm_oa/applications/_inputs.html.erb +11 -0
  15. data/app/views/bsm_oa/applications/create.json.jbuilder +1 -0
  16. data/app/views/bsm_oa/applications/edit.html.erb +10 -0
  17. data/app/views/bsm_oa/applications/index.html.erb +24 -0
  18. data/app/views/bsm_oa/applications/index.json.jbuilder +1 -0
  19. data/app/views/bsm_oa/applications/new.html.erb +10 -0
  20. data/app/views/bsm_oa/applications/show.json.jbuilder +1 -0
  21. data/app/views/bsm_oa/applications/update.json.jbuilder +1 -0
  22. data/app/views/bsm_oa/authorizations/_authorization.json.jbuilder +1 -0
  23. data/app/views/bsm_oa/authorizations/_inputs.html.erb +2 -0
  24. data/app/views/bsm_oa/authorizations/edit.html.erb +10 -0
  25. data/app/views/bsm_oa/authorizations/index.html.erb +0 -0
  26. data/app/views/bsm_oa/authorizations/index.json.jbuilder +1 -0
  27. data/app/views/bsm_oa/authorizations/new.html.erb +11 -0
  28. data/app/views/bsm_oa/authorizations/toggle.js.erb +9 -0
  29. data/app/views/bsm_oa/authorizations/toggle.json.jbuilder +1 -0
  30. data/app/views/bsm_oa/roles/_authorization.html.erb +17 -0
  31. data/app/views/bsm_oa/roles/_inputs.html.erb +5 -0
  32. data/app/views/bsm_oa/roles/_role.html.erb +16 -0
  33. data/app/views/bsm_oa/roles/_role.json.jbuilder +2 -0
  34. data/app/views/bsm_oa/roles/create.json.jbuilder +1 -0
  35. data/app/views/bsm_oa/roles/edit.html.erb +10 -0
  36. data/app/views/bsm_oa/roles/index.html.erb +19 -0
  37. data/app/views/bsm_oa/roles/index.json.jbuilder +2 -0
  38. data/app/views/bsm_oa/roles/new.html.erb +10 -0
  39. data/app/views/bsm_oa/roles/show.html.erb +41 -0
  40. data/app/views/bsm_oa/roles/show.json.jbuilder +1 -0
  41. data/app/views/bsm_oa/roles/update.json.jbuilder +1 -0
  42. data/bsm_oa.gemspec +37 -0
  43. data/config.ru +7 -0
  44. data/db/migrate/20150507113313_bsm_oa_create_doorkeeper_tables.rb +43 -0
  45. data/db/migrate/20150513155732_bsm_oa_create_tables.rb +16 -0
  46. data/lib/bsm_oa/application_mixin.rb +37 -0
  47. data/lib/bsm_oa/authorization.rb +49 -0
  48. data/lib/bsm_oa/config.rb +19 -0
  49. data/lib/bsm_oa/engine.rb +27 -0
  50. data/lib/bsm_oa/role.rb +28 -0
  51. data/lib/bsm_oa/routes.rb +24 -0
  52. data/lib/bsm_oa/version.rb +3 -0
  53. data/lib/bsm_oa.rb +25 -0
  54. data/spec/controllers/bsm_oa/accounts_controller_spec.rb +35 -0
  55. data/spec/controllers/bsm_oa/applications_controller_spec.rb +114 -0
  56. data/spec/controllers/bsm_oa/authorizations_controller_spec.rb +164 -0
  57. data/spec/controllers/bsm_oa/roles_controller_spec.rb +140 -0
  58. data/spec/factories.rb +23 -0
  59. data/spec/internal/config/database.yml +3 -0
  60. data/spec/internal/config/routes.rb +3 -0
  61. data/spec/internal/db/combustion_test.sqlite +0 -0
  62. data/spec/internal/db/schema.rb +13 -0
  63. data/spec/internal/log/.gitignore +1 -0
  64. data/spec/internal/public/favicon.ico +0 -0
  65. data/spec/lib/bsm_oa/application_mixin_spec.rb +48 -0
  66. data/spec/lib/bsm_oa/authorization_spec.rb +53 -0
  67. data/spec/lib/bsm_oa/config_spec.rb +20 -0
  68. data/spec/lib/bsm_oa/role_spec.rb +22 -0
  69. data/spec/spec_helper.rb +64 -0
  70. metadata +372 -0
@@ -0,0 +1,41 @@
1
+ <div class="page-header">
2
+ <div class="pull-right">
3
+ <%= link_to "&larr; Back".html_safe, bsm_oa_roles_path, class: 'btn btn-lg btn-default'%>
4
+ </div>
5
+ <h1><%= @role.name %></h1>
6
+ </div>
7
+
8
+ <h3>Details</h3>
9
+ <div class="table-responsive">
10
+ <table class="table table-hover">
11
+ <tr>
12
+ <th>Name:</th>
13
+ <td><%= @role.name %></td>
14
+ </tr>
15
+ <tr>
16
+ <th>Description:</th>
17
+ <td>
18
+ <%= '&ndash;'.html_safe unless @role.description %>
19
+ <%= @role.description %>
20
+ </td>
21
+ </tr>
22
+ </table>
23
+ </div>
24
+
25
+ <h3>
26
+ Authorizations
27
+ <%= link_to 'New', new_bsm_oa_role_bsm_oa_authorization_path(@role), class: 'btn btn-sm btn-primary' %>
28
+ </h3>
29
+ <div class="table-responsive">
30
+ <table class="table table-hover">
31
+ <thead>
32
+ <tr><th>Application</th><th>Permissions</th><th>&nbsp;</th></tr>
33
+ </thead>
34
+ <tbody>
35
+ <% @role.authorizations.includes(:application).each do |auth| %>
36
+ <%= render 'authorization', auth: auth %>
37
+ <% end -%>
38
+ </tbody>
39
+ </table>
40
+ </div>
41
+
@@ -0,0 +1 @@
1
+ json.partial! 'role', role: @role
@@ -0,0 +1 @@
1
+ json.partial! 'role', role: @role
data/bsm_oa.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ $:.push File.expand_path('../lib', __FILE__)
2
+
3
+ require 'bsm_oa/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'bsm_oa'
7
+ s.version = BsmOa::VERSION
8
+ s.authors = ['Andy Born', 'Dimitrij Denissenko']
9
+ s.email = 'info@blacksquaremedia.com'
10
+ s.homepage = 'https://github.org/bsm/oa'
11
+ s.summary = 'Rails Open Authority engine'
12
+ s.description = 'Opinionated toolbox for building centralised authorities'
13
+ s.licenses = ['MIT']
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- spec/*`.split("\n")
17
+ s.require_paths = ['lib']
18
+
19
+ s.add_dependency 'railties', '>= 4.1', '< 5.0'
20
+ s.add_dependency 'doorkeeper', '~> 3.0.0.rc'
21
+ s.add_dependency 'responders', '~> 2.0'
22
+ s.add_dependency 'jbuilder', '~> 2.2'
23
+ s.add_dependency 'bsm-models'
24
+ s.add_dependency 'has_scope', '~> 0.6'
25
+ s.add_dependency 'simple_form', '~> 3.1'
26
+ s.add_dependency 'jquery-rails'
27
+
28
+ s.add_development_dependency 'rails', '>= 4.1'
29
+ s.add_development_dependency 'combustion', '~> 0.5.3'
30
+ s.add_development_dependency 'rspec-rails'
31
+ s.add_development_dependency 'factory_girl'
32
+ s.add_development_dependency 'json_spec'
33
+ s.add_development_dependency 'faker'
34
+ s.add_development_dependency 'shoulda-matchers'
35
+ s.add_development_dependency 'database_cleaner'
36
+ s.add_development_dependency 'sqlite3'
37
+ end
data/config.ru ADDED
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.require :default, :development
5
+
6
+ Combustion.initialize! :all
7
+ run Combustion::Application
@@ -0,0 +1,43 @@
1
+ class BsmOaCreateDoorkeeperTables < ActiveRecord::Migration
2
+ def change
3
+ create_table :oauth_applications do |t|
4
+ t.string :name, null: false
5
+ t.string :uid, null: false
6
+ t.string :secret, null: false
7
+ t.text :redirect_uri, null: false
8
+ t.string :scopes, null: false, default: ''
9
+ t.text :permissions
10
+ t.timestamps null: false
11
+ end
12
+
13
+ add_index :oauth_applications, :uid, unique: true
14
+
15
+ create_table :oauth_access_grants do |t|
16
+ t.integer :resource_owner_id, null: false
17
+ t.integer :application_id, null: false
18
+ t.string :token, null: false
19
+ t.integer :expires_in, null: false
20
+ t.text :redirect_uri, null: false
21
+ t.datetime :created_at, null: false
22
+ t.datetime :revoked_at
23
+ t.string :scopes
24
+ end
25
+
26
+ add_index :oauth_access_grants, :token, unique: true
27
+
28
+ create_table :oauth_access_tokens do |t|
29
+ t.integer :resource_owner_id
30
+ t.integer :application_id
31
+ t.string :token, null: false
32
+ t.string :refresh_token
33
+ t.integer :expires_in
34
+ t.datetime :revoked_at
35
+ t.datetime :created_at, null: false
36
+ t.string :scopes
37
+ end
38
+
39
+ add_index :oauth_access_tokens, :token, unique: true
40
+ add_index :oauth_access_tokens, :resource_owner_id
41
+ add_index :oauth_access_tokens, :refresh_token, unique: true
42
+ end
43
+ end
@@ -0,0 +1,16 @@
1
+ class BsmOaCreateTables < ActiveRecord::Migration
2
+ def change
3
+ create_table :bsm_oa_authorizations do |t|
4
+ t.integer :role_id, null: false
5
+ t.integer :application_id, null: false
6
+ t.text :permissions
7
+ end
8
+ add_index :bsm_oa_authorizations, [:role_id, :application_id], unique: :true
9
+
10
+ create_table :bsm_oa_roles do |t|
11
+ t.string :name, null: false, size: 80
12
+ t.string :description
13
+ end
14
+ add_index :bsm_oa_roles, [:name], unique: :true
15
+ end
16
+ end
@@ -0,0 +1,37 @@
1
+ module BsmOa
2
+ module ApplicationMixin
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :authorizations, class_name: BsmOa::Authorization, inverse_of: :application, dependent: :destroy
7
+ has_many :roles, inverse_of: :applications, class_name: BsmOa::Role, through: :authorizations, foreign_key: :role_id
8
+
9
+ serialize :permissions, Bsm::Model::Coders::JsonColumn.new(Array)
10
+ validate :must_have_simple_word_permissions
11
+
12
+ before_validation :normalize_permissions!
13
+
14
+ scope :ordered, -> { order(:name) }
15
+ end
16
+
17
+ def permissions_string=(permissions_string)
18
+ self.permissions = permissions_string.split(/\W+/)
19
+ end
20
+
21
+ def permissions_string
22
+ permissions.sort.join(' ')
23
+ end
24
+
25
+ protected
26
+
27
+ def must_have_simple_word_permissions
28
+ errors.add :permissions, :invalid if permissions.any? {|pm| pm =~ /[^a-z0-9]/ }
29
+ end
30
+
31
+ def normalize_permissions!
32
+ self.permissions = Array.wrap(permissions).reject(&:blank?).map(&:strip).map(&:downcase).uniq
33
+ end
34
+
35
+ end
36
+ end
37
+
@@ -0,0 +1,49 @@
1
+ module BsmOa
2
+ class Authorization < ActiveRecord::Base
3
+ self.table_name = :"#{table_name_prefix}bsm_oa_authorizations#{table_name_suffix}"
4
+
5
+ # ---> ASSOCIATIONS
6
+ belongs_to :role, inverse_of: :authorizations
7
+ belongs_to :application, inverse_of: :authorizations, class_name: Doorkeeper::Application, foreign_key: :application_id
8
+
9
+ # ---> ATTRIBUTES
10
+ serialize :permissions, Bsm::Model::Coders::JsonColumn.new(Array)
11
+ attr_readonly :application_id, :role_id, :application
12
+
13
+ # ---> VALIDATIONS
14
+ validates :application, :role, :application_id, :role_id, presence: :true
15
+ validates :application_id, uniqueness: { scope: :role_id }
16
+
17
+ # ---> CALLBACKS
18
+ before_validation :normalize_permissions!
19
+
20
+ # ---> SCOPES
21
+ scope :ordered, -> { order(id: :desc) }
22
+
23
+ # @param [String] name permission name
24
+ def toggle(name)
25
+ if permissions.include?(name)
26
+ self.permissions = permissions - [name]
27
+ else
28
+ self.permissions = permissions + [name]
29
+ end
30
+ save
31
+ end
32
+
33
+ def permissions_string=(str)
34
+ self.permissions = str.split("\s")
35
+ end
36
+
37
+ def permissions_string
38
+ permissions.sort.join(' ')
39
+ end
40
+
41
+ protected
42
+
43
+ def normalize_permissions!
44
+ self.permissions = permissions.reject(&:blank?).map(&:strip).map(&:downcase).uniq
45
+ self.permissions &= application.permissions if application
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,19 @@
1
+ module BsmOa
2
+ class Config
3
+
4
+ def user_class(value = nil)
5
+ if value.nil?
6
+ @user_class = @user_class.constantize if String === @user_class
7
+ @user_class ||= "::User".constantize
8
+ else
9
+ @user_class = value
10
+ end
11
+ end
12
+
13
+ def user_attrs(*attrs)
14
+ @user_attrs = attrs unless attrs.empty?
15
+ @user_attrs ||= [:id, :email]
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ module BsmOa
2
+ class Engine < Rails::Engine
3
+
4
+ initializer 'bsm_oa.migrations' do |app|
5
+ unless app.root.to_s.match root.to_s
6
+ config.paths["db/migrate"].expanded.each do |path|
7
+ app.config.paths["db/migrate"] << path
8
+ end
9
+ end
10
+ end
11
+
12
+ initializer "bsm_oa.routes" do
13
+ Routes.install!
14
+ end
15
+
16
+ initializer "bsm_oa.models" do
17
+ ActiveSupport.on_load(:active_record) do
18
+ require 'bsm_oa/application_mixin'
19
+ require 'bsm_oa/authorization'
20
+ require 'bsm_oa/role'
21
+
22
+ Doorkeeper::Application.send :include, ApplicationMixin
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ module BsmOa
2
+ class Role < ActiveRecord::Base
3
+ self.table_name = :"#{table_name_prefix}bsm_oa_roles#{table_name_suffix}"
4
+
5
+ # ---> ASSOCIATIONS
6
+ has_many :authorizations, inverse_of: :role, dependent: :destroy
7
+ has_many :applications, inverse_of: :roles, class_name: Doorkeeper::Application, through: :authorizations, foreign_key: :application_id
8
+
9
+ # ---> VALIDATIONS
10
+ validates :name,
11
+ presence: true,
12
+ length: { maximum: 80, allow_blank: true },
13
+ uniqueness: { case_sensitive: false, allow_blank: true }
14
+
15
+ # ---> SCOPES
16
+ scope :ordered, -> { order(id: :desc) }
17
+
18
+ # ---> CALLBACKS
19
+ before_validation :normalize_attributes!
20
+
21
+ private
22
+
23
+ def normalize_attributes!
24
+ self.name = name.try(:strip)
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ module BsmOa
2
+ module Routes
3
+
4
+ def self.install!
5
+ ActionDispatch::Routing::Mapper.send :include, Helper
6
+ end
7
+
8
+ module Helper
9
+
10
+ def mount_bsm_oa
11
+ get 'me(.:format)', to: 'bsm_oa/accounts#show', as: :bsm_oa_me
12
+ resources :roles, controller: 'bsm_oa/roles', as: :bsm_oa_roles do
13
+ resources :authorizations, controller: 'bsm_oa/authorizations', as: :bsm_oa_authorizations, shallow: true do
14
+ put :toggle, on: :member, path: "toggle/:permission"
15
+ end
16
+ end
17
+ use_doorkeeper do
18
+ controllers applications: 'bsm_oa/applications'
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module BsmOa
2
+ VERSION = "0.3.1"
3
+ end
data/lib/bsm_oa.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'doorkeeper'
2
+ require 'bsm-models'
3
+ require 'responders'
4
+ require 'jbuilder'
5
+ require 'has_scope'
6
+ require 'simple_form'
7
+
8
+ require 'bsm_oa/version'
9
+ require 'bsm_oa/engine'
10
+ require 'bsm_oa/routes'
11
+ require 'bsm_oa/config'
12
+
13
+ module BsmOa
14
+ class << self
15
+
16
+ def configure(&block)
17
+ config.send :instance_eval, &block
18
+ end
19
+
20
+ def config
21
+ @config ||= Config.new
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe BsmOa::AccountsController, type: :controller do
4
+
5
+ let(:authorization) { create :authorization }
6
+ let(:application) { authorization.application }
7
+ let(:user) { create :user, roles: [authorization.role] }
8
+ let(:access_token) { Doorkeeper::AccessToken.create! resource_owner_id: user.id, application_id: application.id }
9
+
10
+ describe 'routing' do
11
+ it { is_expected.to route(:get, "/me").to(action: :show) }
12
+ end
13
+
14
+ describe 'GET show.json (successful)' do
15
+ before do
16
+ request.headers["Authorization"] = "Bearer #{access_token.token}"
17
+ get :show, client_id: application.uid, client_secret: application.secret, format: 'json'
18
+ end
19
+
20
+ it { expect(response.body).to have_json_size(3) }
21
+ it { expect(response.body).to have_json_size(1).at_path('authorizations') }
22
+ it { expect(response.body).to have_json_size(2).at_path('authorizations/0') }
23
+ it { is_expected.to respond_with(:success) }
24
+ end
25
+
26
+ describe 'GET show.json (bad auth token)' do
27
+ before do
28
+ request.headers["Authorization"] = "Bearer abcdef"
29
+ get :show, format: 'json'
30
+ end
31
+
32
+ it { is_expected.to respond_with(:unauthorized) }
33
+ end
34
+
35
+ end
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+
3
+ describe BsmOa::ApplicationsController, type: :controller do
4
+
5
+ let(:user) { create :user }
6
+ let(:application) { create :application }
7
+
8
+ describe 'routing' do
9
+ it { is_expected.to route(:get, "/oauth/applications").to(action: :index) }
10
+ it { is_expected.to route(:post, "/oauth/applications").to(action: :create) }
11
+ it { is_expected.to route(:get, "/oauth/applications/new").to(action: :new) }
12
+ it { is_expected.to route(:get, "/oauth/applications/1").to(action: :show, id: 1) }
13
+ it { is_expected.to route(:put, "/oauth/applications/1").to(action: :update, id: 1) }
14
+ it { is_expected.to route(:delete, "/oauth/applications/1").to(action: :destroy, id: 1) }
15
+ end
16
+
17
+ describe 'GET index.json' do
18
+ before do
19
+ application
20
+ get :index, format: 'json'
21
+ end
22
+
23
+ it { expect(response.body).to have_json_size(1) }
24
+ it { is_expected.to respond_with(:success) }
25
+ end
26
+
27
+ describe 'GET show.json' do
28
+ before do
29
+ get :show, format: 'json', id: application.to_param
30
+ end
31
+
32
+ it { expect(response.body).to have_json_size(6) }
33
+ it { is_expected.to respond_with(:success) }
34
+ end
35
+
36
+ describe 'GET show.html' do
37
+ before do
38
+ get :show, format: 'html', id: application.to_param
39
+ end
40
+
41
+ it { is_expected.to redirect_to("http://test.host/oauth/applications") }
42
+ end
43
+
44
+ describe 'POST create.json (successful)' do
45
+ before do
46
+ post :create, format: 'json', doorkeeper_application: attributes_for(:application)
47
+ end
48
+
49
+ it { expect(response.body).to have_json_size(6) }
50
+ it { is_expected.to respond_with(:success) }
51
+ end
52
+
53
+ describe 'POST create.json (unsuccessful)' do
54
+ before do
55
+ post :create, format: 'json', doorkeeper_application: attributes_for(:application, name: nil)
56
+ end
57
+
58
+ it { expect(response.body).to have_json_path('errors') }
59
+ it { is_expected.to respond_with(:unprocessable_entity) }
60
+ end
61
+
62
+ describe 'POST create.html' do
63
+ before do
64
+ post :create, doorkeeper_application: attributes_for(:application)
65
+ end
66
+
67
+ it { is_expected.to respond_with(:redirect) }
68
+ it { is_expected.to redirect_to("http://test.host/oauth/applications/#{Doorkeeper::Application.last.to_param}") }
69
+ end
70
+
71
+ describe 'PUT update.json (successful)' do
72
+ before do
73
+ put :update, format: 'json', id: application.to_param, doorkeeper_application: application.attributes
74
+ end
75
+
76
+ it { expect(response.body).to have_json_size(6) }
77
+ it { is_expected.to respond_with(:success) }
78
+ end
79
+
80
+ describe 'PUT update.json (unsuccessful)' do
81
+ before do
82
+ put :update, format: 'json', id: application.to_param, doorkeeper_application: application.attributes.merge(name: nil)
83
+ end
84
+
85
+ it { expect(response.body).to have_json_path('errors') }
86
+ it { is_expected.to respond_with(:unprocessable_entity) }
87
+ end
88
+
89
+ describe 'PUT update.html' do
90
+ before do
91
+ put :update, id: application.to_param, doorkeeper_application: application.attributes
92
+ end
93
+
94
+ it { is_expected.to respond_with(:redirect) }
95
+ it { is_expected.to redirect_to("http://test.host/oauth/applications/#{application.to_param}") }
96
+ end
97
+
98
+ describe 'DELETE destroy.json' do
99
+ before do
100
+ delete :destroy, format: 'json', id: application.to_param
101
+ end
102
+
103
+ it { is_expected.to respond_with(:no_content) }
104
+ end
105
+
106
+ describe 'DELETE destroy.html' do
107
+ before do
108
+ delete :destroy, id: application.to_param
109
+ end
110
+
111
+ it { is_expected.to respond_with(:redirect) }
112
+ it { is_expected.to redirect_to("http://test.host/oauth/applications") }
113
+ end
114
+ end