Add support for digest authentication

This commit is contained in:
Brandon Robins
2017-12-14 23:16:01 -06:00
parent 50becf8ae3
commit dde85d453c
8 changed files with 66 additions and 8 deletions

View File

@@ -1,8 +1,8 @@
PATH PATH
remote: . remote: .
specs: specs:
calligraphy (0.1.0) calligraphy (0.2.0)
rails (>= 5.0) rails (~> 5.0)
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
@@ -48,6 +48,7 @@ GEM
builder (3.2.3) builder (3.2.3)
concurrent-ruby (1.0.5) concurrent-ruby (1.0.5)
crass (1.0.3) crass (1.0.3)
diff-lcs (1.3)
erubi (1.7.0) erubi (1.7.0)
globalid (0.4.1) globalid (0.4.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
@@ -66,7 +67,7 @@ GEM
nokogiri (1.8.1) nokogiri (1.8.1)
mini_portile2 (~> 2.3.0) mini_portile2 (~> 2.3.0)
rack (2.0.3) rack (2.0.3)
rack-test (0.8.0) rack-test (0.8.2)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rails (5.1.4) rails (5.1.4)
actioncable (= 5.1.4) actioncable (= 5.1.4)
@@ -92,6 +93,19 @@ GEM
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rake (12.3.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) sprockets (3.7.1)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
@@ -112,6 +126,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
calligraphy! calligraphy!
rspec
BUNDLED WITH BUNDLED WITH
1.16.0 1.16.0

View File

@@ -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`) * Provides a framework for handling WebDAV requests (e.g. `PROPFIND`, `PROPPATCH`)
* Allows you to extend WedDAV functionality to any type of resource * 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 ## Getting Started
Add the following line to your Gemfile: Add the following line to your Gemfile:
```ruby ```ruby
gem 'calligraphy' gem 'calligraphy', :git => 'https://github.com/eanlain/calligraphy'
``` ```
Then run `bundle install` Then run `bundle install`

View File

@@ -17,4 +17,6 @@ Gem::Specification.new do |s|
s.test_files = Dir['spec/**/*'] s.test_files = Dir['spec/**/*']
s.add_dependency 'rails', '~> 5.0' s.add_dependency 'rails', '~> 5.0'
s.add_development_dependency 'rspec'
end end

View File

@@ -38,6 +38,12 @@ module Calligraphy
options head get put delete copy move mkcol propfind proppatch lock unlock 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 mattr_accessor :lock_timeout_period
@@lock_timeout_period = 24 * 60 * 60 @@lock_timeout_period = 24 * 60 * 60
@@ -45,4 +51,8 @@ module Calligraphy
@@web_dav_actions = %i( @@web_dav_actions = %i(
options get put delete copy move mkcol propfind proppatch lock unlock options get put delete copy move mkcol propfind proppatch lock unlock
) )
def self.configure
yield self
end
end end

View File

@@ -309,6 +309,7 @@ module Calligraphy
def create_lock(properties, depth) def create_lock(properties, depth)
@store.transaction do @store.transaction do
@store[:lockcreator] = client_nonce
@store[:lockdiscovery] = [] unless @store[:lockdiscovery].is_a? Array @store[:lockdiscovery] = [] unless @store[:lockdiscovery].is_a? Array
@store[:lockdepth] = depth @store[:lockdepth] = depth
@@ -366,6 +367,7 @@ module Calligraphy
def locking_ancestor?(ancestor_path, ancestors, headers=nil) def locking_ancestor?(ancestor_path, ancestors, headers=nil)
ancestor_store_path = "#{ancestor_path}/#{ancestors[-1]}.pstore" ancestor_store_path = "#{ancestor_path}/#{ancestors[-1]}.pstore"
check_lock_creator = Calligraphy.enable_digest_authentication
blocking_lock = false blocking_lock = false
unlockable = true unlockable = true
@@ -381,6 +383,10 @@ module Calligraphy
ancestor_store[:lockdiscovery] ancestor_store[:lockdiscovery]
end 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: [] blocking_lock = obj_exists_and_is_not_type? obj: ancestor_lock, type: []
if blocking_lock if blocking_lock
@@ -392,7 +398,8 @@ module Calligraphy
.each { |x| x } .each { |x| x }
.map { |k, v| k[:locktoken].children[0].text } .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
end end
@@ -463,6 +470,8 @@ module Calligraphy
def remove_lock(token) def remove_lock(token)
@store.transaction do @store.transaction do
@store.delete :lockcreator
if @store[:lockdiscovery].length == 1 if @store[:lockdiscovery].length == 1
@store.delete :lockdiscovery @store.delete :lockdiscovery
else else

View File

@@ -1,6 +1,7 @@
module Calligraphy::Rails module Calligraphy::Rails
class WebDavRequestsController < ActionController::Base class WebDavRequestsController < ActionController::Base
before_action :verify_resource_scope before_action :verify_resource_scope
before_action :authenticate_with_digest_authentiation
before_action :set_resource before_action :set_resource
def invoke_method def invoke_method
@@ -10,6 +11,8 @@ module Calligraphy::Rails
if method == 'head' if method == 'head'
status = get head: true status = get head: true
elsif Calligraphy.allowed_methods.include? method elsif Calligraphy.allowed_methods.include? method
set_resource_client_nonce(method) if Calligraphy.enable_digest_authentication
status, body = send method status, body = send method
else else
status = :method_not_allowed status = :method_not_allowed
@@ -27,6 +30,14 @@ module Calligraphy::Rails
head :forbidden if params[:resource].include? '..' head :forbidden if params[:resource].include? '..'
end 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 def set_resource
resource_id = if params[:format] resource_id = if params[:format]
[params[:resource], params[:format]].join '.' [params[:resource], params[:format]].join '.'
@@ -182,5 +193,16 @@ module Calligraphy::Rails
render body: body, status: status render body: body, status: status
end end
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
end end

View File

@@ -1,6 +1,6 @@
module Calligraphy module Calligraphy
class Resource 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 attr_reader :full_request_path, :mount_point, :request_body, :request_path, :root_dir
def initialize(resource: nil, req: nil, mount: nil, root_dir: nil) def initialize(resource: nil, req: nil, mount: nil, root_dir: nil)

View File

@@ -1,3 +1,3 @@
module Calligraphy module Calligraphy
VERSION = '0.1.0' VERSION = '0.2.0'
end end