diff --git a/spec/controllers/drivers_spec.cr b/spec/controllers/drivers_spec.cr index 40d6aeca..89a987e1 100644 --- a/spec/controllers/drivers_spec.cr +++ b/spec/controllers/drivers_spec.cr @@ -133,6 +133,41 @@ module PlaceOS::Api end end + describe "access control", tags: "access-control" do + it "GET /drivers returns 403 for non-admin / non-support callers" do + _, headers = Spec::Authentication.authentication(sys_admin: false, support: false) + result = client.get(Drivers.base_route, headers: headers) + result.status_code.should eq 403 + end + + it "GET /drivers/:id/readme returns 403 for non-admin / non-support callers" do + repository = Model::Generator.repository(type: Model::Repository::Type::Driver) + repository.uri = "https://github.com/PlaceOS/drivers" + repository.save! + + driver = Model::Driver.new( + name: "Restricted Readme", + role: Model::Driver::Role::Logic, + commit: "HEAD", + module_name: "AutoRelease", + file_name: "drivers/place/auto_release.cr", + ) + driver.repository = repository + driver.save! + + _, headers = Spec::Authentication.authentication(sys_admin: false, support: false) + path = File.join(Drivers.base_route, driver.id.as(String), "readme") + result = client.get(path, headers: headers) + result.status_code.should eq 403 + end + + it "GET /drivers is allowed for support-only callers" do + _, headers = Spec::Authentication.authentication(sys_admin: false, support: true) + result = client.get(Drivers.base_route, headers: headers) + result.success?.should be_true + end + end + describe "scopes" do before_each do HttpMocks.core_compiled diff --git a/spec/controllers/zones_spec.cr b/spec/controllers/zones_spec.cr index c7eeffd7..cebb3ceb 100644 --- a/spec/controllers/zones_spec.cr +++ b/spec/controllers/zones_spec.cr @@ -624,6 +624,34 @@ module PlaceOS::Api end end + describe "GET /zones/:id/triggers access control" do + it "returns 403 for non-admin / non-support callers" do + zone = Model::Generator.zone.save! + _, headers = Spec::Authentication.authentication(sys_admin: false, support: false) + + result = client.get( + path: "#{Zones.base_route}#{zone.id}/triggers", + headers: headers, + ) + result.status_code.should eq 403 + + zone.destroy + end + + it "is allowed for support-only callers" do + zone = Model::Generator.zone.save! + _, headers = Spec::Authentication.authentication(sys_admin: false, support: true) + + result = client.get( + path: "#{Zones.base_route}#{zone.id}/triggers", + headers: headers, + ) + result.success?.should be_true + + zone.destroy + end + end + describe "scopes" do Spec.test_controller_scope(Zones) Spec.test_update_write_scope(Zones) diff --git a/src/placeos-rest-api/controllers/drivers.cr b/src/placeos-rest-api/controllers/drivers.cr index dc01cbe7..ed44c4f0 100644 --- a/src/placeos-rest-api/controllers/drivers.cr +++ b/src/placeos-rest-api/controllers/drivers.cr @@ -13,6 +13,12 @@ module PlaceOS::Api before_action :check_admin, except: [:index, :show, :readme] + # Restrict driver listing and readme access to admin/support. These + # endpoints expose internal infrastructure detail (driver inventory, driver + # README files that may include configuration notes or credential examples) + # that should not be available to standard authenticated users. + before_action :check_support, only: [:index, :readme] + ############################################################################################### @[AC::Route::Filter(:before_action, except: [:index, :create])] diff --git a/src/placeos-rest-api/controllers/zones.cr b/src/placeos-rest-api/controllers/zones.cr index 398b6822..fd59a613 100644 --- a/src/placeos-rest-api/controllers/zones.cr +++ b/src/placeos-rest-api/controllers/zones.cr @@ -13,10 +13,12 @@ module PlaceOS::Api # Scopes ############################################################################################### - before_action :can_read, only: [:index, :show] + before_action :can_read, only: [:index, :show, :trigger_instances] before_action :can_write, only: [:create, :update, :destroy, :remove] - before_action :check_support, only: [:zone_execute] + # Restrict zone trigger listing to admin/support — `:trigger_instances` + # (GET /zones/:id/triggers) exposes internal automation/driver state. + before_action :check_support, only: [:zone_execute, :trigger_instances] # Response helpers ###############################################################################################