diff --git a/lib/calligraphy.rb b/lib/calligraphy.rb index 0851ccb..ffb40ef 100644 --- a/lib/calligraphy.rb +++ b/lib/calligraphy.rb @@ -24,6 +24,7 @@ require 'calligraphy/web_dav_request/propfind' require 'calligraphy/web_dav_request/proppatch' require 'calligraphy/web_dav_request/put' require 'calligraphy/web_dav_request/unlock' +require 'calligraphy/web_dav_request/acl' #:nodoc: module Calligraphy @@ -43,7 +44,7 @@ module Calligraphy mattr_accessor :allowed_http_methods @@allowed_http_methods = %w[ options get put delete copy move - mkcol propfind proppatch lock unlock + mkcol propfind proppatch lock unlock acl ] # Proc responsible for returning the user's password, API key, @@ -70,7 +71,7 @@ module Calligraphy mattr_accessor :web_dav_actions @@web_dav_actions = %i[ options get put delete copy move - mkcol propfind proppatch lock unlock + mkcol propfind proppatch lock unlock acl ] # Default way to set up Calligraphy. diff --git a/lib/calligraphy/acl_utils.rb b/lib/calligraphy/acl_utils.rb new file mode 100644 index 0000000..e16db7f --- /dev/null +++ b/lib/calligraphy/acl_utils.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Calligraphy + module AclUtils + def parse_acl(xml) + [].tap do |ace| + xml.each do |node| + next unless node.is_a? Nokogiri::XML::Element + + ace << node + end + end + end + end +end diff --git a/lib/calligraphy/rails/mapper.rb b/lib/calligraphy/rails/mapper.rb index e2460a5..b40e743 100644 --- a/lib/calligraphy/rails/mapper.rb +++ b/lib/calligraphy/rails/mapper.rb @@ -5,6 +5,13 @@ module ActionDispatch class Mapper #:nodoc: module HttpHelpers + # Define a Calligraphy route that only recognizes HTTP ACL. + # acl 'bacon', to: 'food#bacon' + def acl(*args, &block) + args = web_dav_args args + map_method :acl, args, &block + end + # Define a Calligraphy route that only recognizes HTTP COPY. # copy 'bacon', to: 'food#bacon' def copy(*args, &block) @@ -137,6 +144,7 @@ module ActionDispatch # PROPPATCH /photos/*resource # LOCK /photos/*resource # UNLOCK /photos/*resource + # ACL /photos/*resource def calligraphy_resource(*resources, &block) options = resources.extract_options!.dup diff --git a/lib/calligraphy/rails/web_dav_methods.rb b/lib/calligraphy/rails/web_dav_methods.rb index f9de239..64c4fb1 100644 --- a/lib/calligraphy/rails/web_dav_methods.rb +++ b/lib/calligraphy/rails/web_dav_methods.rb @@ -67,6 +67,10 @@ module Calligraphy def unlock Calligraphy::Unlock.new(web_dav_request).execute end + + def acl + Calligraphy::Acl.new(web_dav_request).execute + end end end end diff --git a/lib/calligraphy/resource/file_resource.rb b/lib/calligraphy/resource/file_resource.rb index 99ce347..d54ca01 100644 --- a/lib/calligraphy/resource/file_resource.rb +++ b/lib/calligraphy/resource/file_resource.rb @@ -596,11 +596,6 @@ module Calligraphy prop end - # def include(prop) - # # TODO: Implement - # prop - # end - def lockdiscovery(prop) prop.content = fetch_lock_info prop diff --git a/lib/calligraphy/resource/resource.rb b/lib/calligraphy/resource/resource.rb index de435d9..89af6f2 100644 --- a/lib/calligraphy/resource/resource.rb +++ b/lib/calligraphy/resource/resource.rb @@ -71,6 +71,7 @@ module Calligraphy # Used in OPTIONS requests. def dav_compliance compliance_classes = %w[1 2 3] + compliance_classes.push 'access-control' if enable_access_control? compliance_classes.push 'extended-mkcol' if enable_extended_mkcol? compliance_classes.join ', ' @@ -84,6 +85,12 @@ module Calligraphy raise NotImplementedError end + # Responsible for returning a boolean indicating whether the resource + # supports Access Control Protocol (see RFC3744). + def enable_access_control? + false + end + # Responsible for returning a boolean indicating whether the resource # supports Extended MKCOL (see RFC5689). def enable_extended_mkcol? diff --git a/lib/calligraphy/web_dav_request/acl.rb b/lib/calligraphy/web_dav_request/acl.rb new file mode 100644 index 0000000..7c15ec8 --- /dev/null +++ b/lib/calligraphy/web_dav_request/acl.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +module Calligraphy + # TODO: CHANGE DESCRIPTIONS + # Responsible for processing instructions specified in the request body + # to set and/or remove properties defined on the resource. + class Acl < WebDavRequest + include Calligraphy::XML::Utils + # include Calligraphy::AclUtils + + # Responsible for evaluating preconditions for the WebDAV request. + def preconditions + # conflict_preconditions + # forbidden_preconditions + end + + # Executes the WebDAV request for a particular resource. + def execute + return :locked if @resource.locked_to_user? @headers + + # The `acl` tag contains the request to modify the access control list + # of a resource. + xml = xml_for body: body, node: 'acl' + return :bad_request if xml == :bad_request + + ace = search_xml_for body: body, search: 'ace' + + binding.pry + @resource.acl ace + + :ok + end + + private + + # Array with compact and first? + def conflict_preconditions + [ + no_ace_conflict, + no_protected_ace_conflict, + no_inherited_ace_conflict + ].compact.first + end + + def forbidden_preconditions + [ + limited_number_of_aces, + deny_before_grant, + grant_only, + no_invert, + no_abstract, + not_supported_priviledge, + missing_required_principal, + recognized_principal, + allowed_principal + ].compact.first + end + + def build_error(response) + { error: response } + end + + def no_ace_conflict + build_error 'no_ace_conflict' + end + + def no_protected_ace_conflict + build_error 'no-protected-ace-conflict' + end + + def no_inherited_ace_conflict + build_error 'no-inherited-ace-conflict' + end + + def limited_number_of_aces + build_error 'limited-number-of-aces' + end + + def deny_before_grant + build_error 'deny-before-grant' + end + + def grant_only + build_error 'grant-only' + end + + def no_invert + build_error 'no-invert' + end + + def no_abstract + build_error 'no-abstract' + end + + def not_supported_privilege + build_error 'not-supported-priviledge' + end + + def missing_required_principal + build_error 'missing-required-principal' + end + + def recognized_principal + build_error 'recognized-principal' + end + + def allowed_principal + build_error 'allowed-principal' + end + end +end diff --git a/lib/generators/templates/calligraphy.rb b/lib/generators/templates/calligraphy.rb index b359652..047c3ee 100644 --- a/lib/generators/templates/calligraphy.rb +++ b/lib/generators/templates/calligraphy.rb @@ -5,7 +5,7 @@ Calligraphy.configure do |config| # HTTP verbs and URLs and WebDAV controller actions. # config.web_dav_actions = [ # :options, :get, :put, :delete, :copy, :move, - # :mkcol, :propfind, :proppatch, :lock, :unlock + # :mkcol, :propfind, :proppatch, :lock, :unlock, :acl # ] # HTTP methods allowed by the WebDavRequests controller. @@ -15,7 +15,7 @@ Calligraphy.configure do |config| # HTTP 405 (Method Not Allowed) response. # config.allowed_http_methods = %w( # options get put delete copy move - # mkcol propfind proppatch lock unlock + # mkcol propfind proppatch lock unlock acl # ) # If Digest Authentication is enabled by default. False by default. diff --git a/spec/dummy/config/initializers/calligraphy.rb b/spec/dummy/config/initializers/calligraphy.rb index 790bb62..825b907 100644 --- a/spec/dummy/config/initializers/calligraphy.rb +++ b/spec/dummy/config/initializers/calligraphy.rb @@ -3,7 +3,7 @@ Calligraphy.configure do |config| # HTTP verbs and URLs and WebDAV controller actions. # config.web_dav_actions = [ # :options, :get, :put, :delete, :copy, :move, - # :mkcol, :propfind, :proppatch, :lock, :unlock + # :mkcol, :propfind, :proppatch, :lock, :unlock, :acl # ] # HTTP methods allowed by the WebDavRequests controller. @@ -13,7 +13,7 @@ Calligraphy.configure do |config| # HTTP 405 (Method Not Allowed) response. # config.allowed_http_methods = %w( # options get put delete copy move - # mkcol propfind proppatch lock unlock + # mkcol propfind proppatch lock unlock acl # ) # If Digest Authentication is enabled by default. False by default. diff --git a/spec/requests/acl_spec.rb b/spec/requests/acl_spec.rb new file mode 100644 index 0000000..37ff2c5 --- /dev/null +++ b/spec/requests/acl_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'support/request_helpers' +require 'support/examples/acl' + +RSpec.describe 'acl', type: :request do + before(:context) do + Calligraphy::FileResource.setup + end + + before(:each) do + skip_authentication + end + + after(:context) do + Calligraphy::FileResource.cleanup + end + + context "for #{Calligraphy::FileResource}" do + describe 'acl' do + before(:each) do + Calligraphy::FileResource.create resource: 'top' + end + + it 'grants the proper privileges' do + acl '/webdav/top', headers: { + RAW_POST_DATA: Support::Examples::Acl.rfc3744_8_1_2 + } + end + end + end +end diff --git a/spec/requests/options_spec.rb b/spec/requests/options_spec.rb index 2d44cd8..43cc2d7 100644 --- a/spec/requests/options_spec.rb +++ b/spec/requests/options_spec.rb @@ -56,5 +56,45 @@ RSpec.describe 'OPTIONS', type: :request do expect(response.headers['DAV']).to include('extended-mkcol') end end + + context 'when not using access control support' do + before(:each) do + allow_any_instance_of(Calligraphy::FileResource).to receive( + :enable_access_control? + ).and_return(false) + end + + it 'advertises support for all 3 WebDAV classes' do + options '/webdav/special' + + %w[1 2 3].each { |c| expect(response.headers['DAV']).to include(c) } + end + + it 'does not advertise support for access control' do + options '/webdav/special' + + expect(response.headers['DAV']).to_not include('access-control') + end + end + + context 'when using access control support' do + before(:each) do + allow_any_instance_of(Calligraphy::FileResource).to receive( + :enable_access_control? + ).and_return(true) + end + + it 'advertises support for all 3 WebDAV classes' do + options '/webdav/special' + + %w[1 2 3].each { |c| expect(response.headers['DAV']).to include(c) } + end + + it 'advertises support for access control' do + options '/webdav/special' + + expect(response.headers['DAV']).to include('access-control') + end + end end end diff --git a/spec/resource/resource_spec.rb b/spec/resource/resource_spec.rb index 843c9b9..c4c9696 100644 --- a/spec/resource/resource_spec.rb +++ b/spec/resource/resource_spec.rb @@ -47,6 +47,13 @@ RSpec.describe 'Resource' do end end + describe '#enable_access_control?' do + it 'is not enabled by default' do + resource = Calligraphy::Resource.new + expect(resource.enable_access_control?).to eq(false) + end + end + describe '#enable_extended_mkcol?' do it 'is not enabled by default' do resource = Calligraphy::Resource.new diff --git a/spec/routing/calligraphy_resource_routes_spec.rb b/spec/routing/calligraphy_resource_routes_spec.rb index b7f2cc4..dade1d5 100644 --- a/spec/routing/calligraphy_resource_routes_spec.rb +++ b/spec/routing/calligraphy_resource_routes_spec.rb @@ -123,5 +123,15 @@ RSpec.describe 'calligraphy_resource', type: :routing do ) end end + + context 'for ACL requests' do + it do + expect(acl: '/test/thirteen').to route_to( + controller: 'calligraphy/rails/web_dav_requests', + action: 'invoke_method', + resource: 'thirteen' + ) + end + end end end diff --git a/spec/support/examples/acl.rb b/spec/support/examples/acl.rb new file mode 100644 index 0000000..7839975 --- /dev/null +++ b/spec/support/examples/acl.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: false + +module Support + module Examples + module Acl + # RFC3744: 8.1.2 The ACL method + def self.rfc3744_8_1_2 + <<~XML + + + + + http://www.example.com/users/esedlar + + + + + + + + + + + + + + + + + + + + + + +XML + end + end + end +end diff --git a/spec/support/request_helpers.rb b/spec/support/request_helpers.rb index 770ff97..8b9d51d 100644 --- a/spec/support/request_helpers.rb +++ b/spec/support/request_helpers.rb @@ -4,7 +4,7 @@ module ActionDispatch module Integration module RequestHelpers request_methods = %w[ - copy move mkcol options propfind proppatch lock unlock + copy move mkcol options propfind proppatch lock unlock acl ] request_methods.each do |method| diff --git a/web_dav_inputs_outputs.txt b/web_dav_inputs_outputs.txt new file mode 100644 index 0000000..2e06a21 --- /dev/null +++ b/web_dav_inputs_outputs.txt @@ -0,0 +1,37 @@ +Description +activelock +collection +depth +error +exclusive +href +location +lockentry +lockinfo +lockroot +lockscope +locktoken +locktype +multistatus +owner +prop +propstat +response +responsedescription +shared +status +timeout +write + + +Input +allprop +# include +prop +propertyupdate +propfind +propname +remove +set + +