# frozen_string_literal: true# :markup: markdownrequire"active_support/core_ext/object/deep_dup"moduleActionDispatch# :nodoc:# # Action Dispatch PermissionsPolicy## Configures the HTTP# [Feature-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy)# response header to specify which browser features the current# document and its iframes can use.## Example global policy:## Rails.application.config.permissions_policy do |policy|# policy.camera :none# policy.gyroscope :none# policy.microphone :none# policy.usb :none# policy.fullscreen :self# policy.payment :self, "https://secure.example.com"# end## The Feature-Policy header has been renamed to Permissions-Policy. The# Permissions-Policy requires a different implementation and isn't yet supported# by all browsers. To avoid having to rename this middleware in the future we# use the new name for the middleware but keep the old header name and# implementation for now.classPermissionsPolicyclassMiddlewaredefinitialize(app)@app=appenddefcall(env)_,headers,_=response=@app.call(env)returnresponseifpolicy_present?(headers)request=ActionDispatch::Request.new(env)ifpolicy=request.permissions_policyheaders[ActionDispatch::Constants::FEATURE_POLICY]=policy.build(request.controller_instance)endifpolicy_empty?(policy)headers.delete(ActionDispatch::Constants::FEATURE_POLICY)endresponseendprivatedefpolicy_present?(headers)headers[ActionDispatch::Constants::FEATURE_POLICY]enddefpolicy_empty?(policy)policy&.directives&.empty?endendmoduleRequestPOLICY="action_dispatch.permissions_policy"defpermissions_policyget_header(POLICY)enddefpermissions_policy=(policy)set_header(POLICY,policy)endendMAPPINGS={self: "'self'",none: "'none'",}.freeze# List of available permissions can be found at# https://github.com/w3c/webappsec-permissions-policy/blob/main/features.md#policy-controlled-featuresDIRECTIVES={accelerometer: "accelerometer",ambient_light_sensor: "ambient-light-sensor",autoplay: "autoplay",camera: "camera",display_capture: "display-capture",encrypted_media: "encrypted-media",fullscreen: "fullscreen",geolocation: "geolocation",gyroscope: "gyroscope",hid: "hid",idle_detection: "idle-detection",keyboard_map: "keyboard-map",magnetometer: "magnetometer",microphone: "microphone",midi: "midi",payment: "payment",picture_in_picture: "picture-in-picture",screen_wake_lock: "screen-wake-lock",serial: "serial",sync_xhr: "sync-xhr",usb: "usb",web_share: "web-share",}.freezeprivate_constant:MAPPINGS,:DIRECTIVESattr_reader:directivesdefinitialize@directives={}yieldselfifblock_given?enddefinitialize_copy(other)@directives=other.directives.deep_dupendDIRECTIVES.eachdo|name,directive|define_method(name)do|*sources|ifsources.first@directives[directive]=apply_mappings(sources)else@directives.delete(directive)endendenddefbuild(context=nil)build_directives(context).compact.join("; ")endprivatedefapply_mappings(sources)sources.mapdo|source|casesourcewhenSymbolapply_mapping(source)whenString,ProcsourceelseraiseArgumentError,"Invalid HTTP permissions policy source: #{source.inspect}"endendenddefapply_mapping(source)MAPPINGS.fetch(source)doraiseArgumentError,"Unknown HTTP permissions policy source mapping: #{source.inspect}"endenddefbuild_directives(context)@directives.mapdo|directive,sources|ifsources.is_a?(Array)"#{directive}#{build_directive(sources,context).join(' ')}"elsifsourcesdirectiveelsenilendendenddefbuild_directive(sources,context)sources.map{|source|resolve_source(source,context)}enddefresolve_source(source,context)casesourcewhenStringsourcewhenSymbolsource.to_swhenProcifcontext.nil?raiseRuntimeError,"Missing context for the dynamic permissions policy source: #{source.inspect}"elsecontext.instance_exec(&source)endelseraiseRuntimeError,"Unexpected permissions policy source: #{source.inspect}"endendendend