bsm_oa 0.3.1

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 (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