From e32189716d901036ea56ba506a78c16858f583a1 Mon Sep 17 00:00:00 2001 From: Brandon Robins Date: Thu, 28 Dec 2017 17:18:39 -0600 Subject: [PATCH] Rubocop WebDavRequestsController Abstracts select methods from WebDavRequestsController to WebDavMethods and WebDavPreconditions modules. --- lib/calligraphy.rb | 2 + lib/calligraphy/rails/web_dav_methods.rb | 67 +++++ .../rails/web_dav_preconditions.rb | 114 ++++++++ .../rails/web_dav_requests_controller.rb | 274 ++++++------------ 4 files changed, 264 insertions(+), 193 deletions(-) create mode 100644 lib/calligraphy/rails/web_dav_methods.rb create mode 100644 lib/calligraphy/rails/web_dav_preconditions.rb diff --git a/lib/calligraphy.rb b/lib/calligraphy.rb index 1658590..a93aabf 100644 --- a/lib/calligraphy.rb +++ b/lib/calligraphy.rb @@ -1,4 +1,6 @@ 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/xml/builder' diff --git a/lib/calligraphy/rails/web_dav_methods.rb b/lib/calligraphy/rails/web_dav_methods.rb new file mode 100644 index 0000000..c122714 --- /dev/null +++ b/lib/calligraphy/rails/web_dav_methods.rb @@ -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 diff --git a/lib/calligraphy/rails/web_dav_preconditions.rb b/lib/calligraphy/rails/web_dav_preconditions.rb new file mode 100644 index 0000000..6f67ccc --- /dev/null +++ b/lib/calligraphy/rails/web_dav_preconditions.rb @@ -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 diff --git a/lib/calligraphy/rails/web_dav_requests_controller.rb b/lib/calligraphy/rails/web_dav_requests_controller.rb index 3084deb..21c2422 100644 --- a/lib/calligraphy/rails/web_dav_requests_controller.rb +++ b/lib/calligraphy/rails/web_dav_requests_controller.rb @@ -1,217 +1,105 @@ -module Calligraphy::Rails - class WebDavRequestsController < ActionController::Base - before_action :verify_resource_scope - before_action :authenticate_with_digest_authentiation - before_action :set_resource +# frozen_string_literal: true - # Entry-point for all WebDAV requests. Handles checking and validating - # preconditions, directing of requests to the proper WebDAV action - # method, and composing responses to send back to the client. - def invoke_method - method = request.request_method.downcase +module Calligraphy + module Rails + # Controller for all WebDAV requests. + class WebDavRequestsController < ActionController::Base + include Calligraphy::Rails::WebDavMethods + include Calligraphy::Rails::WebDavPreconditions - if check_preconditions + before_action :verify_resource_scope + before_action :authenticate_with_digest_authentiation + before_action :set_resource + + # Entry-point for all WebDAV requests. Handles checking and validating + # preconditions, directing of requests to the proper WebDAV action + # method, and composing responses to send back to the client. + def invoke_method + unless check_preconditions + return send_response(status: :precondition_failed) + end + + method = request.request_method.downcase + status, body = make_request method + + send_response status: status, body: body + end + + private + + def verify_resource_scope + # Prevent any request with `.` or `..` as part of the resource. + head :forbidden if %w[. ..].any? do |seg| + params[:resource].include? seg + end + end + + def authenticate_with_digest_authentiation + return unless digest_enabled? + + realm = Calligraphy.http_authentication_realm + + authenticate_or_request_with_http_digest(realm) do |username| + Calligraphy.digest_password_procedure.call(username) + end + end + + def digest_enabled? + Calligraphy.enable_digest_authentication + end + + def set_resource + @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 '.' + else + params[:resource] + end + end + + def make_request(method) if method == 'head' status = get head: true elsif Calligraphy.allowed_http_methods.include? method - set_resource_client_nonce(method) if Calligraphy.enable_digest_authentication + resource_client_nonce(method) if digest_enabled? status, body = send method else status = :method_not_allowed end - send_response status: status, body: body - else - send_response status: :precondition_failed - end - end - - private - - # Prevent any request with `.` or `..` as part of the resource ID. - def verify_resource_scope - head :forbidden if %w(. ..).any? { |seg| params[:resource].include? seg } - end - - def authenticate_with_digest_authentiation - return unless Calligraphy.enable_digest_authentication - - realm = Calligraphy.http_authentication_realm - - authenticate_or_request_with_http_digest(realm) do |username| - Calligraphy.digest_password_procedure.call(username) - end - end - - def set_resource - resource_id = if params[:format] - [params[:resource], params[:format]].join '.' - else - params[:resource] + [status, body] 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 - - def check_preconditions - return true unless request.headers['If'].present? - - evaluate_if_header - 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 + def resource_client_nonce(_method) + @resource.client_nonce = client_nonce end - conditions_met - end + def client_nonce + auth_header = request.headers['HTTP_AUTHORIZATION'] + digest = ::ActionController::HttpAuthentication::Digest - def get_if_conditions - lists = if request.headers['If'][0] == '<' - request.headers['If'].split Calligraphy::TAGGED_LIST_REGEX - else - request.headers['If'].split Calligraphy::UNTAGGAGED_LIST_REGEX + auth = digest.decode_credentials auth_header + auth[:cnonce] end - lists - end - - def parse_preconditions(list) - conditions = { dav_no_lock: nil, etag: nil, lock_token: nil, resource: nil } - - conditions[:dav_no_lock] = if list =~ Calligraphy::DAV_NO_LOCK_REGEX - list =~ Calligraphy::DAV_NOT_NO_LOCK_REGEX ? nil : true - end - - if list =~ Calligraphy::RESOURCE_REGEX - 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] + def send_response(status:, body: nil) + if body.nil? + head status else - conditions_met = false if target.locked_to_user? request.headers + render body: body, status: status 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 = ::ActionController::HttpAuthentication::Digest.decode_credentials auth_header - auth[:cnonce] - 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) - if body.nil? - head status - else - render body: body, status: status - end end end end