From dde85d453c6ee9ec30e285aa673f952f48dc0431 Mon Sep 17 00:00:00 2001 From: Brandon Robins Date: Thu, 14 Dec 2017 23:16:01 -0600 Subject: [PATCH] Add support for digest authentication --- Gemfile.lock | 21 +++++++++++++++--- README.md | 4 ++-- calligraphy.gemspec | 2 ++ lib/calligraphy.rb | 10 +++++++++ lib/calligraphy/file_resource.rb | 11 +++++++++- .../rails/web_dav_requests_controller.rb | 22 +++++++++++++++++++ lib/calligraphy/resource.rb | 2 +- lib/calligraphy/version.rb | 2 +- 8 files changed, 66 insertions(+), 8 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 778b6f1..db2ff72 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,8 +1,8 @@ PATH remote: . specs: - calligraphy (0.1.0) - rails (>= 5.0) + calligraphy (0.2.0) + rails (~> 5.0) GEM remote: https://rubygems.org/ @@ -48,6 +48,7 @@ GEM builder (3.2.3) concurrent-ruby (1.0.5) crass (1.0.3) + diff-lcs (1.3) erubi (1.7.0) globalid (0.4.1) activesupport (>= 4.2.0) @@ -66,7 +67,7 @@ GEM nokogiri (1.8.1) mini_portile2 (~> 2.3.0) rack (2.0.3) - rack-test (0.8.0) + rack-test (0.8.2) rack (>= 1.0, < 3) rails (5.1.4) actioncable (= 5.1.4) @@ -92,6 +93,19 @@ GEM rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rake (12.3.0) + rspec (3.7.0) + rspec-core (~> 3.7.0) + rspec-expectations (~> 3.7.0) + rspec-mocks (~> 3.7.0) + rspec-core (3.7.0) + rspec-support (~> 3.7.0) + rspec-expectations (3.7.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.7.0) + rspec-mocks (3.7.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.7.0) + rspec-support (3.7.0) sprockets (3.7.1) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -112,6 +126,7 @@ PLATFORMS DEPENDENCIES calligraphy! + rspec BUNDLED WITH 1.16.0 diff --git a/README.md b/README.md index 6f7a76d..dbe3dd2 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ Calligraphy is a Web Distributed Authoring and Versioning (WebDAV) solution for * Provides a framework for handling WebDAV requests (e.g. `PROPFIND`, `PROPPATCH`) * Allows you to extend WedDAV functionality to any type of resource -* Passes 103/104 of the [Litmus](https://github.com/tolsen/litmus) tests (using `Calligraphy::FileResource`) +* Passes all of the [Litmus](https://github.com/eanlain/litmus) tests (using `Calligraphy::FileResource` and digest authentication) ## Getting Started Add the following line to your Gemfile: ```ruby -gem 'calligraphy' +gem 'calligraphy', :git => 'https://github.com/eanlain/calligraphy' ``` Then run `bundle install` diff --git a/calligraphy.gemspec b/calligraphy.gemspec index 6f93e9f..df3fd1c 100644 --- a/calligraphy.gemspec +++ b/calligraphy.gemspec @@ -17,4 +17,6 @@ Gem::Specification.new do |s| s.test_files = Dir['spec/**/*'] s.add_dependency 'rails', '~> 5.0' + + s.add_development_dependency 'rspec' end diff --git a/lib/calligraphy.rb b/lib/calligraphy.rb index e47bc77..0f591d4 100644 --- a/lib/calligraphy.rb +++ b/lib/calligraphy.rb @@ -38,6 +38,12 @@ module Calligraphy options head get put delete copy move mkcol propfind proppatch lock unlock ) + mattr_accessor :digest_password_procedure + @@digest_password_procedure = Proc.new { |x| 'changeme!' } + + mattr_accessor :enable_digest_authentication + @@enable_digest_authentication = false + mattr_accessor :lock_timeout_period @@lock_timeout_period = 24 * 60 * 60 @@ -45,4 +51,8 @@ module Calligraphy @@web_dav_actions = %i( options get put delete copy move mkcol propfind proppatch lock unlock ) + + def self.configure + yield self + end end diff --git a/lib/calligraphy/file_resource.rb b/lib/calligraphy/file_resource.rb index add5a57..bf7162b 100644 --- a/lib/calligraphy/file_resource.rb +++ b/lib/calligraphy/file_resource.rb @@ -309,6 +309,7 @@ module Calligraphy def create_lock(properties, depth) @store.transaction do + @store[:lockcreator] = client_nonce @store[:lockdiscovery] = [] unless @store[:lockdiscovery].is_a? Array @store[:lockdepth] = depth @@ -366,6 +367,7 @@ module Calligraphy def locking_ancestor?(ancestor_path, ancestors, headers=nil) ancestor_store_path = "#{ancestor_path}/#{ancestors[-1]}.pstore" + check_lock_creator = Calligraphy.enable_digest_authentication blocking_lock = false unlockable = true @@ -381,6 +383,10 @@ module Calligraphy ancestor_store[:lockdiscovery] end + ancestor_lock_creator = ancestor_store.transaction(true) do + ancestor_store[:lockcreator] + end if check_lock_creator + blocking_lock = obj_exists_and_is_not_type? obj: ancestor_lock, type: [] if blocking_lock @@ -392,7 +398,8 @@ module Calligraphy .each { |x| x } .map { |k, v| k[:locktoken].children[0].text } - unlockable = ancestor_lock_tokens.include? token + unlockable = ancestor_lock_tokens.include?(token) || + (check_lock_creator && (ancestor_lock_creator == client_nonce)) end end @@ -463,6 +470,8 @@ module Calligraphy def remove_lock(token) @store.transaction do + @store.delete :lockcreator + if @store[:lockdiscovery].length == 1 @store.delete :lockdiscovery else diff --git a/lib/calligraphy/rails/web_dav_requests_controller.rb b/lib/calligraphy/rails/web_dav_requests_controller.rb index f0cb7f8..9ce30cd 100644 --- a/lib/calligraphy/rails/web_dav_requests_controller.rb +++ b/lib/calligraphy/rails/web_dav_requests_controller.rb @@ -1,6 +1,7 @@ module Calligraphy::Rails class WebDavRequestsController < ActionController::Base before_action :verify_resource_scope + before_action :authenticate_with_digest_authentiation before_action :set_resource def invoke_method @@ -10,6 +11,8 @@ module Calligraphy::Rails if method == 'head' status = get head: true elsif Calligraphy.allowed_methods.include? method + set_resource_client_nonce(method) if Calligraphy.enable_digest_authentication + status, body = send method else status = :method_not_allowed @@ -27,6 +30,14 @@ module Calligraphy::Rails head :forbidden if params[:resource].include? '..' end + def authenticate_with_digest_authentiation + if Calligraphy.enable_digest_authentication + authenticate_or_request_with_http_digest do |username| + Calligraphy.digest_password_procedure.call(username) + end + end + end + def set_resource resource_id = if params[:format] [params[:resource], params[:format]].join '.' @@ -182,5 +193,16 @@ module Calligraphy::Rails render body: body, status: status end 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 end end diff --git a/lib/calligraphy/resource.rb b/lib/calligraphy/resource.rb index b5cce6d..b2a2dbe 100644 --- a/lib/calligraphy/resource.rb +++ b/lib/calligraphy/resource.rb @@ -1,6 +1,6 @@ module Calligraphy class Resource - attr_accessor :contents, :updated_at + attr_accessor :client_nonce, :contents, :updated_at attr_reader :full_request_path, :mount_point, :request_body, :request_path, :root_dir def initialize(resource: nil, req: nil, mount: nil, root_dir: nil) diff --git a/lib/calligraphy/version.rb b/lib/calligraphy/version.rb index 59d9fbc..f0ebee3 100644 --- a/lib/calligraphy/version.rb +++ b/lib/calligraphy/version.rb @@ -1,3 +1,3 @@ module Calligraphy - VERSION = '0.1.0' + VERSION = '0.2.0' end