2 Commits

Author SHA1 Message Date
Brandon Robins
944668ffcb WIP 2018-05-16 02:40:15 -05:00
Brandon Robins
2ce668b9ea Improve testing setup 2018-03-27 23:52:28 -05:00
21 changed files with 482 additions and 87 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -596,11 +596,6 @@ module Calligraphy
prop
end
# def include(prop)
# # TODO: Implement
# prop
# end
def lockdiscovery(prop)
prop.content = fetch_lock_info
prop

View File

@@ -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?

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -1,4 +1,6 @@
Rails.application.routes.draw do
calligraphy_resource :test
calligraphy_resource :webdav, resource_class: Calligraphy::FileResource, resource_root_path: File.expand_path('../../../../tmp/webdav', __FILE__)
calligraphy_resource :webdav,
resource_class: Calligraphy::FileResource,
resource_root_path: File.expand_path('../../../../tmp/webdav', __FILE__)
end

View File

@@ -22,7 +22,7 @@ require 'rspec/rails'
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
Dir[Rails.root.join('../support/**/*.rb')].each { |f| require f }
# Dir[Rails.root.join('../support/**/*.rb')].each { |f| require f }
# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove this line.
@@ -57,3 +57,5 @@ RSpec.configure do |config|
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
end
require 'support/resource_helpers'

33
spec/requests/acl_spec.rb Normal file
View File

@@ -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

View File

@@ -5,20 +5,19 @@ require 'support/request_helpers'
require 'support/examples/ext_mkcol'
RSpec.describe 'mkcol', type: :request do
before(:all) do
tmp_dir = Rails.root.join('../../tmp').to_path
Dir.mkdir tmp_dir unless File.exist? tmp_dir
webdav_dir = Rails.root.join('../../tmp/webdav').to_path
FileUtils.rm_r webdav_dir if File.exist? webdav_dir
Dir.mkdir webdav_dir
before(:context) do
Calligraphy::FileResource.setup
end
before(:each) do
allow(Calligraphy).to receive(:enable_digest_authentication)
.and_return(false)
skip_authentication
end
after(:context) do
Calligraphy::FileResource.cleanup
end
context "for #{Calligraphy::FileResource}" do
it 'creates a collection with additional properties' do
allow_any_instance_of(Calligraphy::FileResource).to receive(
:valid_resourcetypes
@@ -48,4 +47,5 @@ RSpec.describe 'mkcol', type: :request do
expect(response.body).to include('valid-resourcetype')
end
end
end
end

View File

@@ -4,11 +4,19 @@ require 'rails_helper'
require 'support/request_helpers'
RSpec.describe 'OPTIONS', type: :request do
before(:each) do
allow(Calligraphy).to receive(:enable_digest_authentication)
.and_return(false)
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
context 'when not using extended MKCOL support' do
before(:each) do
allow_any_instance_of(Calligraphy::FileResource).to receive(
@@ -48,4 +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

View File

@@ -6,25 +6,22 @@ require 'support/examples/propfind'
require 'support/examples/proppatch'
RSpec.describe 'PROPFIND', type: :request do
before(:all) do
tmp_dir = Rails.root.join('../../tmp').to_path
Dir.mkdir tmp_dir unless File.exist? tmp_dir
webdav_dir = Rails.root.join('../../tmp/webdav').to_path
FileUtils.rm_r webdav_dir if File.exist? webdav_dir
Dir.mkdir webdav_dir
before(:context) do
Calligraphy::FileResource.setup
end
before(:each) do
allow(Calligraphy).to receive(:enable_digest_authentication)
.and_return(false)
skip_authentication
end
after(:context) do
Calligraphy::FileResource.cleanup
end
context 'with xml defintiion' do
before(:each) do
put '/webdav/bar.html', headers: {
RAW_POST_DATA: 'hello world'
}
Calligraphy::FileResource.create resource: 'bar.html'
proppatch '/webdav/bar.html', headers: {
RAW_POST_DATA: Support::Examples::Proppatch.rfc4918_9_2_2
}

View File

@@ -46,5 +46,26 @@ RSpec.describe 'Resource' do
expect(resource.dav_compliance).to eq('1, 2, 3')
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
expect(resource.enable_extended_mkcol?).to eq(false)
end
end
describe '#valid_resourcetypes' do
it 'returns only a collection resourcetype by default' do
resource = Calligraphy::Resource.new
expect(resource.valid_resourcetypes).to match_array(['collection'])
end
end
end
end

View File

@@ -113,5 +113,25 @@ RSpec.describe 'calligraphy_resource', type: :routing do
)
end
end
context 'for HEAD requests' do
it do
expect(head: '/test/twelve').to route_to(
controller: 'calligraphy/rails/web_dav_requests',
action: 'invoke_method',
resource: 'twelve'
)
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

View File

@@ -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
<?xml version="1.0" encoding="utf-8" ?>
<D:acl xmlns:D="DAV:">
<D:ace>
<D:principal>
<D:href>http://www.example.com/users/esedlar</D:href>
</D:principal>
<D:grant>
<D:privilege><D:read/></D:privilege>
<D:privilege><D:write/></D:privilege>
</D:grant>
</D:ace>
<D:ace>
<D:principal>
<D:property><D:owner/></D:property>
</D:principal>
<D:grant>
<D:privilege><D:read-acl/></D:privilege>
<D:privilege><D:write-acl/></D:privilege>
</D:grant>
</D:ace>
<D:ace>
<D:principal><D:all/></D:principal>
<D:grant>
<D:privilege><D:read/></D:privilege>
</D:grant>
</D:ace>
</D:acl>
XML
end
end
end
end

View File

@@ -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|
@@ -15,3 +15,7 @@ module ActionDispatch
end
end
end
def skip_authentication
allow(Calligraphy).to receive(:enable_digest_authentication).and_return(false)
end

View File

@@ -0,0 +1,49 @@
# frozen_string_literal: true
RESOURCES_TO_TEST_AGAINST = [
Calligraphy::FileResource
].freeze
module RSpecMethods
def setup; end
def cleanup; end
def create(resource:, content: 'Hi hi!'); end
end
module FileResourceHelpers
def setup
tmp_dir = Rails.root.join('../../tmp').to_path
Dir.mkdir tmp_dir unless File.exist? tmp_dir
FileUtils.rm_r resource_root if File.exist? resource_root
Dir.mkdir resource_root
end
def cleanup
FileUtils.rm_r resource_root if File.exist? resource_root
end
def create(resource:, content: 'Hello world')
resource = Calligraphy::FileResource.new(
resource: resource,
root_dir: resource_root
)
resource.write content
end
def resource_root
Rails.root.join('../../tmp/webdav').to_path
end
end
RESOURCES_TO_TEST_AGAINST.each do |resource_class|
resource_class.send :extend, RSpecMethods
end
module Calligraphy
class FileResource
extend FileResourceHelpers
end
end

View File

@@ -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