Rubocop WebDavRequestsController
Abstracts select methods from WebDavRequestsController to WebDavMethods and WebDavPreconditions modules.
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
require 'calligraphy/rails/mapper'
|
require 'calligraphy/rails/mapper'
|
||||||
|
require 'calligraphy/rails/web_dav_methods'
|
||||||
|
require 'calligraphy/rails/web_dav_preconditions'
|
||||||
require 'calligraphy/rails/web_dav_requests_controller'
|
require 'calligraphy/rails/web_dav_requests_controller'
|
||||||
|
|
||||||
require 'calligraphy/xml/builder'
|
require 'calligraphy/xml/builder'
|
||||||
|
|||||||
67
lib/calligraphy/rails/web_dav_methods.rb
Normal file
67
lib/calligraphy/rails/web_dav_methods.rb
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Calligraphy
|
||||||
|
module Rails
|
||||||
|
# Provides methods to direct the execution of WebDAV actions.
|
||||||
|
module WebDavMethods
|
||||||
|
private
|
||||||
|
|
||||||
|
def web_dav_request
|
||||||
|
{
|
||||||
|
headers: request.headers,
|
||||||
|
request: request,
|
||||||
|
resource: @resource,
|
||||||
|
response: response
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def options
|
||||||
|
response.headers['DAV'] = @resource.dav_compliance
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(head: false)
|
||||||
|
fresh_when(@resource, etag: @resource.etag) if @resource.readable?
|
||||||
|
|
||||||
|
Calligraphy::Get.new(web_dav_request).execute(head: head)
|
||||||
|
end
|
||||||
|
|
||||||
|
def put
|
||||||
|
Calligraphy::Put.new(web_dav_request).execute
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete
|
||||||
|
Calligraphy::Delete.new(web_dav_request).execute
|
||||||
|
end
|
||||||
|
|
||||||
|
def copy
|
||||||
|
Calligraphy::Copy.new(web_dav_request).execute
|
||||||
|
end
|
||||||
|
|
||||||
|
def move
|
||||||
|
Calligraphy::Move.new(web_dav_request).execute
|
||||||
|
end
|
||||||
|
|
||||||
|
def mkcol
|
||||||
|
Calligraphy::Mkcol.new(web_dav_request).execute
|
||||||
|
end
|
||||||
|
|
||||||
|
def propfind
|
||||||
|
Calligraphy::Propfind.new(web_dav_request).execute
|
||||||
|
end
|
||||||
|
|
||||||
|
def proppatch
|
||||||
|
Calligraphy::Proppatch.new(web_dav_request).execute
|
||||||
|
end
|
||||||
|
|
||||||
|
def lock
|
||||||
|
Calligraphy::Lock.new(web_dav_request).execute
|
||||||
|
end
|
||||||
|
|
||||||
|
def unlock
|
||||||
|
Calligraphy::Unlock.new(web_dav_request).execute
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
114
lib/calligraphy/rails/web_dav_preconditions.rb
Normal file
114
lib/calligraphy/rails/web_dav_preconditions.rb
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Calligraphy
|
||||||
|
module Rails
|
||||||
|
# Provides methods to handle checking and validating WebDAV request
|
||||||
|
# preconditions.
|
||||||
|
module WebDavPreconditions
|
||||||
|
private
|
||||||
|
|
||||||
|
def check_preconditions
|
||||||
|
return true unless request.headers['If'].present?
|
||||||
|
|
||||||
|
evaluate_if_header
|
||||||
|
end
|
||||||
|
|
||||||
|
def evaluate_if_header
|
||||||
|
conditions_met = false
|
||||||
|
condition_lists = if_conditions
|
||||||
|
|
||||||
|
condition_lists.each do |list|
|
||||||
|
conditions = parse_preconditions list
|
||||||
|
|
||||||
|
conditions_met = evaluate_preconditions conditions
|
||||||
|
break if conditions_met
|
||||||
|
end
|
||||||
|
|
||||||
|
conditions_met
|
||||||
|
end
|
||||||
|
|
||||||
|
def if_conditions
|
||||||
|
if request.headers['If'][0] == '<'
|
||||||
|
request.headers['If'].split Calligraphy::TAGGED_LIST_REGEX
|
||||||
|
else
|
||||||
|
request.headers['If'].split Calligraphy::UNTAGGAGED_LIST_REGEX
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_preconditions(list)
|
||||||
|
conditions = conditions_hash
|
||||||
|
conditions[:dav_no_lock] = match_dav_no_lock list
|
||||||
|
conditions[:resource] = scan_for_resource list
|
||||||
|
conditions[:lock_token] = scan_for_lock_token list
|
||||||
|
conditions[:etag] = scan_for_etag list
|
||||||
|
conditions
|
||||||
|
end
|
||||||
|
|
||||||
|
def conditions_hash
|
||||||
|
{
|
||||||
|
dav_no_lock: nil,
|
||||||
|
etag: nil,
|
||||||
|
lock_token: nil,
|
||||||
|
resource: nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def match_dav_no_lock(list)
|
||||||
|
return nil unless list =~ Calligraphy::DAV_NO_LOCK_REGEX
|
||||||
|
|
||||||
|
list =~ Calligraphy::DAV_NOT_NO_LOCK_REGEX ? nil : true
|
||||||
|
end
|
||||||
|
|
||||||
|
def scan_for_resource(list)
|
||||||
|
return nil unless list =~ Calligraphy::RESOURCE_REGEX
|
||||||
|
|
||||||
|
list.scan(Calligraphy::RESOURCE_REGEX).flatten[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
def scan_for_lock_token(list)
|
||||||
|
return nil unless list =~ Calligraphy::LOCK_TOKEN_REGEX
|
||||||
|
|
||||||
|
list.scan(Calligraphy::LOCK_TOKEN_REGEX).flatten[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
def scan_for_etag(list)
|
||||||
|
return nil unless list =~ Calligraphy::ETAG_IF_REGEX
|
||||||
|
|
||||||
|
list.scan(Calligraphy::ETAG_IF_REGEX).flatten[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
def evaluate_preconditions(conditions)
|
||||||
|
conditions_met = true
|
||||||
|
|
||||||
|
if conditions[:etag]
|
||||||
|
conditions_met = false unless evaluate_etag_condition conditions
|
||||||
|
end
|
||||||
|
|
||||||
|
conditions_met = false if conditions[:dav_no_lock]
|
||||||
|
conditions_met
|
||||||
|
end
|
||||||
|
|
||||||
|
def target_resource(conditions)
|
||||||
|
if conditions[:resource]
|
||||||
|
@resource_class.new(
|
||||||
|
resource: conditions[:resource],
|
||||||
|
mount: @resource.mount_point
|
||||||
|
)
|
||||||
|
else
|
||||||
|
@resource
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def evaluate_etag_condition(conditions)
|
||||||
|
validators = [@resource.etag, '']
|
||||||
|
validate_etag validators, conditions[:etag]
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_etag(etag_validators, validate_against)
|
||||||
|
cache_key = ActiveSupport::Cache.expand_cache_key etag_validators
|
||||||
|
|
||||||
|
validate_against == "W/\"#{Digest::MD5.hexdigest(cache_key)}\""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
module Calligraphy::Rails
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Calligraphy
|
||||||
|
module Rails
|
||||||
|
# Controller for all WebDAV requests.
|
||||||
class WebDavRequestsController < ActionController::Base
|
class WebDavRequestsController < ActionController::Base
|
||||||
|
include Calligraphy::Rails::WebDavMethods
|
||||||
|
include Calligraphy::Rails::WebDavPreconditions
|
||||||
|
|
||||||
before_action :verify_resource_scope
|
before_action :verify_resource_scope
|
||||||
before_action :authenticate_with_digest_authentiation
|
before_action :authenticate_with_digest_authentiation
|
||||||
before_action :set_resource
|
before_action :set_resource
|
||||||
@@ -8,34 +15,27 @@ module Calligraphy::Rails
|
|||||||
# preconditions, directing of requests to the proper WebDAV action
|
# preconditions, directing of requests to the proper WebDAV action
|
||||||
# method, and composing responses to send back to the client.
|
# method, and composing responses to send back to the client.
|
||||||
def invoke_method
|
def invoke_method
|
||||||
method = request.request_method.downcase
|
unless check_preconditions
|
||||||
|
return send_response(status: :precondition_failed)
|
||||||
if check_preconditions
|
|
||||||
if method == 'head'
|
|
||||||
status = get head: true
|
|
||||||
elsif Calligraphy.allowed_http_methods.include? method
|
|
||||||
set_resource_client_nonce(method) if Calligraphy.enable_digest_authentication
|
|
||||||
|
|
||||||
status, body = send method
|
|
||||||
else
|
|
||||||
status = :method_not_allowed
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
method = request.request_method.downcase
|
||||||
|
status, body = make_request method
|
||||||
|
|
||||||
send_response status: status, body: body
|
send_response status: status, body: body
|
||||||
else
|
|
||||||
send_response status: :precondition_failed
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Prevent any request with `.` or `..` as part of the resource ID.
|
|
||||||
def verify_resource_scope
|
def verify_resource_scope
|
||||||
head :forbidden if %w(. ..).any? { |seg| params[:resource].include? seg }
|
# Prevent any request with `.` or `..` as part of the resource.
|
||||||
|
head :forbidden if %w[. ..].any? do |seg|
|
||||||
|
params[:resource].include? seg
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def authenticate_with_digest_authentiation
|
def authenticate_with_digest_authentiation
|
||||||
return unless Calligraphy.enable_digest_authentication
|
return unless digest_enabled?
|
||||||
|
|
||||||
realm = Calligraphy.http_authentication_realm
|
realm = Calligraphy.http_authentication_realm
|
||||||
|
|
||||||
@@ -44,168 +44,55 @@ module Calligraphy::Rails
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def digest_enabled?
|
||||||
|
Calligraphy.enable_digest_authentication
|
||||||
|
end
|
||||||
|
|
||||||
def set_resource
|
def set_resource
|
||||||
resource_id = if params[:format]
|
@resource_class = params[:resource_class] || Calligraphy::Resource
|
||||||
|
@resource_root_path = params[:resource_root_path]
|
||||||
|
|
||||||
|
@resource = @resource_class.new(
|
||||||
|
resource: resource_id,
|
||||||
|
req: request,
|
||||||
|
root_dir: @resource_root_path
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def resource_id
|
||||||
|
if params[:format]
|
||||||
[params[:resource], params[:format]].join '.'
|
[params[:resource], params[:format]].join '.'
|
||||||
else
|
else
|
||||||
params[:resource]
|
params[:resource]
|
||||||
end
|
end
|
||||||
|
|
||||||
@resource_class = params[:resource_class] || Calligraphy::Resource
|
|
||||||
@resource_root_path = params[:resource_root_path]
|
|
||||||
|
|
||||||
@resource = @resource_class.new resource: resource_id, req: request, root_dir: @resource_root_path
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_preconditions
|
def make_request(method)
|
||||||
return true unless request.headers['If'].present?
|
if method == 'head'
|
||||||
|
status = get head: true
|
||||||
|
elsif Calligraphy.allowed_http_methods.include? method
|
||||||
|
resource_client_nonce(method) if digest_enabled?
|
||||||
|
|
||||||
evaluate_if_header
|
status, body = send method
|
||||||
end
|
|
||||||
|
|
||||||
def evaluate_if_header
|
|
||||||
conditions_met = false
|
|
||||||
condition_lists = get_if_conditions
|
|
||||||
|
|
||||||
condition_lists.each do |list|
|
|
||||||
conditions = parse_preconditions list
|
|
||||||
|
|
||||||
conditions_met = evaluate_preconditions conditions
|
|
||||||
break if conditions_met
|
|
||||||
end
|
|
||||||
|
|
||||||
conditions_met
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_if_conditions
|
|
||||||
lists = if request.headers['If'][0] == '<'
|
|
||||||
request.headers['If'].split Calligraphy::TAGGED_LIST_REGEX
|
|
||||||
else
|
else
|
||||||
request.headers['If'].split Calligraphy::UNTAGGAGED_LIST_REGEX
|
status = :method_not_allowed
|
||||||
end
|
end
|
||||||
|
|
||||||
lists
|
[status, body]
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_preconditions(list)
|
def resource_client_nonce(_method)
|
||||||
conditions = { dav_no_lock: nil, etag: nil, lock_token: nil, resource: nil }
|
@resource.client_nonce = client_nonce
|
||||||
|
|
||||||
conditions[:dav_no_lock] = if list =~ Calligraphy::DAV_NO_LOCK_REGEX
|
|
||||||
list =~ Calligraphy::DAV_NOT_NO_LOCK_REGEX ? nil : true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if list =~ Calligraphy::RESOURCE_REGEX
|
def client_nonce
|
||||||
conditions[:resource] = list.scan(Calligraphy::RESOURCE_REGEX).flatten[0]
|
|
||||||
end
|
|
||||||
|
|
||||||
if list =~ Calligraphy::LOCK_TOKEN_REGEX
|
|
||||||
conditions[:lock_token] = list.scan(Calligraphy::LOCK_TOKEN_REGEX).flatten[0]
|
|
||||||
end
|
|
||||||
|
|
||||||
if list =~ Calligraphy::ETAG_IF_REGEX
|
|
||||||
conditions[:etag] = list.scan(Calligraphy::ETAG_IF_REGEX).flatten[0]
|
|
||||||
end
|
|
||||||
|
|
||||||
conditions
|
|
||||||
end
|
|
||||||
|
|
||||||
def evaluate_preconditions(conditions)
|
|
||||||
conditions_met = true
|
|
||||||
target = if conditions[:resource]
|
|
||||||
@resource_class.new(
|
|
||||||
resource: conditions[:resource],
|
|
||||||
mount: @resource.mount_point
|
|
||||||
)
|
|
||||||
else
|
|
||||||
@resource
|
|
||||||
end
|
|
||||||
|
|
||||||
if conditions[:lock_token]
|
|
||||||
if target.locked?
|
|
||||||
conditions_met = false unless target.lock_tokens&.include? conditions[:lock_token]
|
|
||||||
else
|
|
||||||
conditions_met = false if target.locked_to_user? request.headers
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if conditions[:etag]
|
|
||||||
validators = [@resource.etag, '']
|
|
||||||
conditions_met = false unless validate_etag validators, conditions[:etag]
|
|
||||||
end
|
|
||||||
|
|
||||||
conditions_met = false if conditions[:dav_no_lock]
|
|
||||||
conditions_met
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_etag(etag_validators, validate_against)
|
|
||||||
cache_key = ActiveSupport::Cache.expand_cache_key etag_validators
|
|
||||||
|
|
||||||
"W/\"#{Digest::MD5.hexdigest(cache_key)}\"" == validate_against
|
|
||||||
end
|
|
||||||
|
|
||||||
def web_dav_request
|
|
||||||
{ headers: request.headers, request: request, resource: @resource, response: response }
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_resource_client_nonce(method)
|
|
||||||
@resource.client_nonce = get_client_nonce
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_client_nonce
|
|
||||||
auth_header = request.headers['HTTP_AUTHORIZATION']
|
auth_header = request.headers['HTTP_AUTHORIZATION']
|
||||||
|
digest = ::ActionController::HttpAuthentication::Digest
|
||||||
|
|
||||||
auth = ::ActionController::HttpAuthentication::Digest.decode_credentials auth_header
|
auth = digest.decode_credentials auth_header
|
||||||
auth[:cnonce]
|
auth[:cnonce]
|
||||||
end
|
end
|
||||||
|
|
||||||
def options
|
|
||||||
response.headers['DAV'] = @resource.dav_compliance
|
|
||||||
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
def get(head: false)
|
|
||||||
fresh_when(@resource, etag: @resource.etag) if @resource.readable?
|
|
||||||
|
|
||||||
Calligraphy::Get.new(web_dav_request).request(head: head)
|
|
||||||
end
|
|
||||||
|
|
||||||
def put
|
|
||||||
Calligraphy::Put.new(web_dav_request).request
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete
|
|
||||||
Calligraphy::Delete.new(web_dav_request).request
|
|
||||||
end
|
|
||||||
|
|
||||||
def copy
|
|
||||||
Calligraphy::Copy.new(web_dav_request).request
|
|
||||||
end
|
|
||||||
|
|
||||||
def move
|
|
||||||
Calligraphy::Move.new(web_dav_request).request
|
|
||||||
end
|
|
||||||
|
|
||||||
def mkcol
|
|
||||||
Calligraphy::Mkcol.new(web_dav_request).request
|
|
||||||
end
|
|
||||||
|
|
||||||
def propfind
|
|
||||||
Calligraphy::Propfind.new(web_dav_request).request
|
|
||||||
end
|
|
||||||
|
|
||||||
def proppatch
|
|
||||||
Calligraphy::Proppatch.new(web_dav_request).request
|
|
||||||
end
|
|
||||||
|
|
||||||
def lock
|
|
||||||
Calligraphy::Lock.new(web_dav_request).request
|
|
||||||
end
|
|
||||||
|
|
||||||
def unlock
|
|
||||||
Calligraphy::Unlock.new(web_dav_request).request
|
|
||||||
end
|
|
||||||
|
|
||||||
def send_response(status:, body: nil)
|
def send_response(status:, body: nil)
|
||||||
if body.nil?
|
if body.nil?
|
||||||
head status
|
head status
|
||||||
@@ -215,3 +102,4 @@ module Calligraphy::Rails
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user