diff --git a/app/controllers/v0/devices_controller.rb b/app/controllers/v0/devices_controller.rb index ff896ee6..4712ec16 100644 --- a/app/controllers/v0/devices_controller.rb +++ b/app/controllers/v0/devices_controller.rb @@ -14,11 +14,13 @@ def show def index if params[:with_tags] - @q = Device.with_user_tags(params[:with_tags]) + @q = policy_scope(Device) + .with_user_tags(params[:with_tags]) .includes(:owner,:tags, kit: [:sensors, :components]) .ransack(params[:q]) else - @q = Device.includes(:owner, :tags, kit: [:components, :sensors]) + @q = policy_scope(Device) + .includes(:owner, :tags, kit: [:components, :sensors]) .ransack(params[:q]) end @@ -82,6 +84,7 @@ def fresh_world_map longitude: device.longitude, city: device.city, country_code: device.country_code, + is_private: device.is_private, kit_id: device.kit_id, state: device.state, system_tags: device.system_tags, @@ -124,7 +127,7 @@ def world_map private def device_params - params.permit( + params_to_permit = [ :name, :description, :mac_address, @@ -138,6 +141,15 @@ def device_params :meta, :kit_id, :user_tags + ] + + # Researchers + Admins can update is_private + if current_user.role_mask >= 2 + params_to_permit.push(:is_private) + end + + params.permit( + params_to_permit ) end diff --git a/app/controllers/v0/static_controller.rb b/app/controllers/v0/static_controller.rb index 586f24fd..52004655 100644 --- a/app/controllers/v0/static_controller.rb +++ b/app/controllers/v0/static_controller.rb @@ -29,6 +29,7 @@ def metrics render json: { devices: { total: Device.count, + private: Device.where(is_private: true).count, online: { now: Device.where('last_recorded_at > ?', 10.minutes.ago).count, last_hour: Device.where('last_recorded_at > ?', 1.hour.ago).count, diff --git a/app/models/user.rb b/app/models/user.rb index 4bde34de..b7c88740 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -98,6 +98,7 @@ def is_admin? end def role + return 'researcher' if role_mask == 2 role_mask < 5 ? 'citizen' : 'admin' end diff --git a/app/policies/device_policy.rb b/app/policies/device_policy.rb index 642affc4..291f2d7d 100644 --- a/app/policies/device_policy.rb +++ b/app/policies/device_policy.rb @@ -1,7 +1,34 @@ class DevicePolicy < ApplicationPolicy + class Scope + attr_reader :user, :scope + + def initialize(user, scope) + @user = user + @scope = scope + end + + def resolve + if user + if user.is_admin? + # Admins get everything + scope + else + # Non admins should get all non_private + the Devices they own + scope.where(is_private: false).or(scope.where(owner_id: user.id)) + end + else + # not logged in + scope.where(is_private: false) + end + end + end def show? - true + if record.is_private? + update? + else + true + end end def update? diff --git a/app/views/v0/devices/_device.jbuilder b/app/views/v0/devices/_device.jbuilder index 6a653c6a..d381127d 100644 --- a/app/views/v0/devices/_device.jbuilder +++ b/app/views/v0/devices/_device.jbuilder @@ -7,6 +7,7 @@ json.(device, :hardware_info, :system_tags, :user_tags, + :is_private, :notify_low_battery, :notify_stopped_publishing, :last_reading_at, diff --git a/db/migrate/20190819084816_add_is_private_to_devices.rb b/db/migrate/20190819084816_add_is_private_to_devices.rb new file mode 100644 index 00000000..09663943 --- /dev/null +++ b/db/migrate/20190819084816_add_is_private_to_devices.rb @@ -0,0 +1,5 @@ +class AddIsPrivateToDevices < ActiveRecord::Migration[5.2] + def change + add_column :devices, :is_private, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 61b95735..2bcad7bb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_02_22_130041) do +ActiveRecord::Schema.define(version: 2019_08_19_084816) do # These are extensions that must be enabled in order to support this database enable_extension "adminpack" @@ -93,6 +93,7 @@ t.datetime "notify_low_battery_timestamp", default: "2019-01-16 16:19:35" t.boolean "notify_low_battery", default: false t.boolean "notify_stopped_publishing", default: false + t.boolean "is_private", default: false t.index ["device_token"], name: "index_devices_on_device_token", unique: true t.index ["geohash"], name: "index_devices_on_geohash" t.index ["kit_id"], name: "index_devices_on_kit_id" diff --git a/db/seeds.rb b/db/seeds.rb index 9d81a29f..02b16daa 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -26,7 +26,7 @@ 3.times do Kit.create( name: "Kit #{Faker::Educator.campus}", - description: Faker::Lorem.sentence(5), + description: Faker::Lorem.sentence, slug: 'sck:1,1', sensor_map: {"temp": 12, "hum": 13, "light": 14} ) @@ -89,10 +89,11 @@ #device has many sensors through components #has_many :components, as: :board -5.times do + +10.times do Device.create( { - owner: User.first, + owner: User.all.sample, name: Faker::Address.city, city: Faker::Address.city, country_code: Faker::Address.country_code, @@ -102,6 +103,7 @@ latitude: 42.385, longitude: 2.173, device_token: Faker::Crypto.sha1[0,6], + is_private: [true, false].sample, notify_low_battery: [true, false].sample, notify_low_battery_timestamp: Time.now, notify_stopped_publishing: [true, false].sample, @@ -128,6 +130,7 @@ # belongs_to :sensor # Kit and Device have many Components, as: :board + Component.create( board: Kit.first, sensor: Sensor.find(12) ) @@ -181,7 +184,8 @@ report: {"random_property":"random_result"}, ) -Device.find(1).update_attributes( +d = Device.first +d.update!( hardware_info: { "id": 1, "uuid": "7d45fead-defd-4482-bc6a-a1b711879e2d", diff --git a/spec/requests/v0/devices_spec.rb b/spec/requests/v0/devices_spec.rb index 99165f23..d3fc6971 100644 --- a/spec/requests/v0/devices_spec.rb +++ b/spec/requests/v0/devices_spec.rb @@ -4,6 +4,7 @@ let(:application) { create :application } let(:user) { create :user } + let(:user2) { create :user } let(:token) { create :access_token, application: application, resource_owner_id: user.id } let(:device) { create(:device) } let(:admin) { create :admin } @@ -24,7 +25,49 @@ # expect(json[0]['name']).to eq(first.name) # expect(json[1]['name']).to eq(second.name) expect(json[0].keys).to eq(%w(id uuid name description state - hardware_info system_tags user_tags notify_low_battery notify_stopped_publishing last_reading_at added_at updated_at mac_address owner data kit)) + hardware_info system_tags user_tags is_private notify_low_battery notify_stopped_publishing last_reading_at added_at updated_at mac_address owner data kit)) + end + + describe "when not logged in" do + it 'does not show private devices' do + device = create(:device, owner: user, is_private: false) + device1 = create(:device, owner: user, is_private: true) + device2 = create(:device, owner: user, is_private: true) + + expect(Device.count).to eq(3) + j = api_get "devices/" + expect(j.count).to eq(1) + expect(response.status).to eq(200) + expect(j[0]['id']).to eq(device.id) + end + end + + describe "when logged in as a normal user" do + it 'shows the user his devices, even though they are private' do + device1 = create(:device, owner: user, is_private: false) + device2 = create(:device, owner: user, is_private: true) + device3 = create(:device, owner: user2, is_private: true) + + expect(Device.count).to eq(3) + j = api_get "devices/", { access_token: token.token } + expect(j[0]['id']).to eq(device1.id) + expect(response.status).to eq(200) + expect(j.count).to eq(2) + end + end + + describe "when logged in as an admin" do + it 'allows admin to see ALL devices' do + device1 = create(:device, owner: user, is_private: false) + device2 = create(:device, owner: user, is_private: true) + device3 = create(:device, owner: user2, is_private: true) + + expect(Device.count).to eq(3) + j = api_get "devices/", {access_token: admin_token.token} + expect(response.status).to eq(200) + expect(j[0]['id']).to eq(device1.id) + expect(j.count).to eq(3) + end end describe "world map" do @@ -87,6 +130,21 @@ expect(response.status).to eq(404) end + it 'does not show a private device' do + device = create(:device, owner: user, is_private: true) + j = api_get "devices/#{device.id}" + expect(j['id']).to eq("forbidden") + expect(response.status).to eq(403) + end + + it 'shows a non_private device' do + device = create(:device, owner: user, is_private: false) + j = api_get "devices/#{device.id}" + expect(j['id']).to eq(device.id) + expect(response.status).to eq(200) + end + + describe "mac_address" do it "filters mac address from guests" do @@ -118,6 +176,19 @@ let!(:device) { create :device, owner: user } + it "cannot update a device is_private attribute" do + api_put "devices/#{device.id}", { is_private: true, access_token: token.token } + expect(response.status).to eq(200) + expect(Device.find(device.id).is_private).to eq(false) + end + + it "can update a device is_private attribute when user has role" do + user.update role_mask: 3 + api_put "devices/#{device.id}", { is_private: true, access_token: token.token } + expect(response.status).to eq(200) + expect(Device.find(device.id).is_private).to eq(true) + end + it "updates a device" do api_put "devices/#{device.id}", { name: 'new name', access_token: token.token } expect(response.status).to eq(200) @@ -141,7 +212,6 @@ end describe "POST /devices" do - it "creates a device" do api_post 'devices', { access_token: token.token,