From a5765c32390db341c4aca59f65ebf6111f1abdf4 Mon Sep 17 00:00:00 2001 From: Rob Dingwell Date: Wed, 27 Aug 2014 16:37:42 -0400 Subject: [PATCH 1/3] Adding 'rolify' role based access library. This is being done to begin to support user/provider level authorization to results. --- Gemfile | 2 ++ Gemfile.lock | 2 ++ app/controllers/admin_controller.rb | 4 ++-- app/controllers/api/patients_controller.rb | 2 +- app/controllers/logs_controller.rb | 2 +- app/models/ability.rb | 18 +++++++++++++----- app/models/role.rb | 16 ++++++++++++++++ app/models/user.rb | 17 +++++++++++++---- app/views/admin/users.html.erb | 4 ++-- app/views/home/index.html.erb | 6 +++++- app/views/shared/_header.html.erb | 4 ++-- app/views/shared/_top_nav.html.erb | 6 +++--- .../initializers/active_model_serializers.rb | 1 + config/initializers/mongo.rb | 1 + config/initializers/rolify.rb | 8 ++++++++ lib/hds/provider.rb | 3 ++- lib/tasks/popHealth_users.rake | 10 ++++++++++ test/fixtures/roles.yml | 11 +++++++++++ test/fixtures/roles/admin_role.json | 10 ++++++++++ test/fixtures/roles/staff_role.json | 12 ++++++++++++ test/fixtures/users/admin_user.json | 8 ++++++-- test/fixtures/users/admin_user2.json | 7 +++++-- test/fixtures/users/generic_user.json | 6 ++++-- test/fixtures/users/generic_user2.json | 6 ++++-- test/fixtures/users/no_staff_role_user.json | 3 +-- test/fixtures/users/unapproved_user.json | 3 +-- test/functional/admin_controller_test.rb | 2 +- test/functional/api/queries_controller_test.rb | 1 + test/functional/logs_controller_test.rb | 1 + test/models/role_test.rb | 7 +++++++ test/unit/admin_rake_test.rb | 2 +- test/unit/models/user_test.rb | 1 + test/unit/user_rake_test.rb | 1 + 33 files changed, 151 insertions(+), 36 deletions(-) create mode 100644 app/models/role.rb create mode 100644 config/initializers/rolify.rb create mode 100644 test/fixtures/roles.yml create mode 100644 test/fixtures/roles/admin_role.json create mode 100644 test/fixtures/roles/staff_role.json create mode 100644 test/models/role_test.rb diff --git a/Gemfile b/Gemfile index a7951bf55..518c6173b 100644 --- a/Gemfile +++ b/Gemfile @@ -44,6 +44,8 @@ gem 'jquery-rails' # necessary for jquery_ujs w/data-method="delete" etc gem 'uglifier' gem 'non-stupid-digest-assets' # support vendored non-digest assets +gem 'rolify' + group :test, :develop, :ci do gem 'pry' gem 'jasmine', '2.0.1' diff --git a/Gemfile.lock b/Gemfile.lock index d3cac71e7..f68b09b37 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -209,6 +209,7 @@ GEM rest-client (1.6.8) mime-types (~> 1.16) rdoc (>= 2.4.2) + rolify (3.4.0) rubyzip (0.9.9) sass (3.2.19) sass-rails (4.0.3) @@ -299,6 +300,7 @@ DEPENDENCIES pry-byebug quality-measure-engine! rails (~> 4.1.2) + rolify rubyzip sass-rails (~> 4.0.2) simplecov diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index c314f5dbf..d148801ee 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -112,10 +112,10 @@ def toggle_privilidges(username, role, direction) if user if direction == :promote - user.update_attribute(role, true) + user.add_role(role) render :text => "Yes - revoke" else - user.update_attribute(role, false) + user.remove_role(role) render :text => "No - grant" end else diff --git a/app/controllers/api/patients_controller.rb b/app/controllers/api/patients_controller.rb index 714f6f71a..653677c42 100644 --- a/app/controllers/api/patients_controller.rb +++ b/app/controllers/api/patients_controller.rb @@ -124,7 +124,7 @@ def set_filter_params @quality_report = QME::QualityReport.find(params[:quality_report_id]) authorize! :read, @quality_report @query["provider.npi"] = {"$in" => @quality_report.filters["providers"]} - elsif current_user.admin? + elsif current_user.has_role?( :admin ) else @query["provider.npi"] = current_user.npi end diff --git a/app/controllers/logs_controller.rb b/app/controllers/logs_controller.rb index 8864310d8..0a11fe4e0 100644 --- a/app/controllers/logs_controller.rb +++ b/app/controllers/logs_controller.rb @@ -20,7 +20,7 @@ def index end where = {} - where[:username] = current_user.username unless current_user.admin? + where[:username] = current_user.username unless current_user.has_role?( :admin ) start_date = date_param_to_date(params[:log_start_date]) if start_date diff --git a/app/models/ability.rb b/app/models/ability.rb index d36098bfd..57d7719b1 100755 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -20,13 +20,13 @@ def initialize(user) user ||= User.new - if user.admin? + if user.has_role?(:admin) can :manage, :all # can [:create,:delete], Record # can :manage, Provider # can [:read, :recalculate,:create, :deleted], QME::QualityReport # can [:create,:delete], HealthDataStandards::CQM::Measure - elsif user.staff_role? + elsif user.has_role?(:staff) can :read, HealthDataStandards::CQM::Measure can :read, Record can :manage, Provider @@ -39,12 +39,20 @@ def initialize(user) patient.providers.map(&:npi).include?(user.npi) end can [:read,:delete, :recalculate, :create], QME::QualityReport do |qr| - provider = Provider.by_npi(user.npi).first - provider ? (qr.filters || {})["providers"].include?(provider.id) : false + providers = Provider.with_role(:staff, user) + provider_ids = [] + providers.each do |p| + provider_ids.concat p.descendants_and_self.collect(&:id) + end + !(provider_ids & (qr.filters || {})["providers"]).empty? end can :read, HealthDataStandards::CQM::Measure can :read, Provider do |pv| - user.npi && (pv.npi == user.npi) + ret = false + pv.ancestors_and_self.each do |p| + ret = true if user.has_role?(:staff, p) + end + ret end can :manage, User, id: user.id cannot :manage, User unless APP_CONFIG['allow_user_update'] diff --git a/app/models/role.rb b/app/models/role.rb new file mode 100644 index 000000000..8e5d8de94 --- /dev/null +++ b/app/models/role.rb @@ -0,0 +1,16 @@ +class Role + include Mongoid::Document + has_and_belongs_to_many :users + belongs_to :resource, :polymorphic => true + + field :name, :type => String + + index({ + :name => 1, + :resource_type => 1, + :resource_id => 1 + }, + { :unique => true}) + + scopify +end diff --git a/app/models/user.rb b/app/models/user.rb index 19e9c7725..73929e2c2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -5,7 +5,7 @@ class User include ActiveModel::MassAssignmentSecurity include Mongoid::Document - + rolify after_initialize :build_preferences, unless: Proc.new { |user| user.preferences.present? } before_save :denullify_arrays before_create :set_defaults @@ -80,7 +80,7 @@ class User validates :username, :presence => true, length: {minimum: 3, maximum: 254} def set_defaults - self.staff_role ||= APP_CONFIG["default_user_staff_role"] + self.add_role :staff if APP_CONFIG["default_user_staff_role"] self.approved ||= APP_CONFIG["default_user_approved"] true end @@ -128,7 +128,7 @@ def self.by_email(email) # ============= def grant_admin - update_attribute(:admin, true) + self.add_role :admin update_attribute(:approved, true) end @@ -137,7 +137,16 @@ def approve end def revoke_admin - update_attribute(:admin, false) + self.remove_role :admin + self[:admin] + end + + def admin? + self.has_role?( :admin ) + end + + def staff_role? + self.has_role?( :staff ) end end diff --git a/app/views/admin/users.html.erb b/app/views/admin/users.html.erb index c7b8d8bda..8aaf78b14 100644 --- a/app/views/admin/users.html.erb +++ b/app/views/admin/users.html.erb @@ -108,14 +108,14 @@ - <% if user.admin? -%> + <% if user.has_role?(:admin) -%> Yes - revoke <% else -%> No - grant <% end -%> - <% if user.staff_role? -%> + <% if user.has_role?(:staff) -%> Yes - revoke <% else -%> No - grant diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index 413d09e33..fa40220cf 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -3,7 +3,11 @@ PopHealth.categories = <%= @categories.to_json.html_safe %>; PopHealth.patientCount = <%= @patient_count %>; PopHealth.currentUser = new Thorax.Models.User(<%= current_user.to_json({:include => :preferences }).html_safe %>); - PopHealth.rootProvider = new Thorax.Models.Provider(<%= Provider.root.to_json({:include => :children }).html_safe %>, {parse:true}); + <% + providerRoot = Provider.root if current_user.admin? || current_user.staff_role? + providerRoot ||= Provider.with_role(:staff, current_user).first + %> + PopHealth.rootProvider = new Thorax.Models.Provider(<%= providerRoot.to_json({:include => :children }).html_safe %>, {parse:true}); PopHealth.router = new PopHealth.Router(); Backbone.history.start(); diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb index 4ca67b53e..2bdeb0afd 100644 --- a/app/views/shared/_header.html.erb +++ b/app/views/shared/_header.html.erb @@ -22,11 +22,11 @@ <% if APP_CONFIG['logout_enabled']%>
  • <%= link_to raw(' Logout'), destroy_user_session_path, method: 'delete' %>
  • <% end %> - <% if current_user.staff_role? || current_user.admin? %> + <% if current_user.has_role? (:staff )|| current_user.has_role?( :admin )%>
  • <%= link_to raw(' Providers'), '/#providers'%>
  • - <% if current_user.admin? %> + <% if current_user.has_role?( :admin )%> <% if (APP_CONFIG['patient_management_enabled']) %>
  • <%= link_to raw(' Patients'), admin_patients_path%>
  • <% end %> diff --git a/app/views/shared/_top_nav.html.erb b/app/views/shared/_top_nav.html.erb index abb2014eb..e984d5714 100644 --- a/app/views/shared/_top_nav.html.erb +++ b/app/views/shared/_top_nav.html.erb @@ -2,16 +2,16 @@ Welcome, <%= current_user.first_name.to_s %> | help <% if APP_CONFIG['edit_account_enabled']%>| <%= link_to 'account', edit_user_registration_path(current_user) %> <% end %> - <% if current_user.staff_role? || current_user.admin? %> | manage <% end %> + <% if current_user.has_role?( :staff) || current_user.has_role?(:admin) %> | manage <% end %> <% if APP_CONFIG['logout_enabled']%>| <%= link_to 'logout', destroy_user_session_path, 'class' => 'log'%> <% end %>