Dav4rack library upgraded to the latest stable version

This commit is contained in:
Karel Pičman 2014-06-18 12:46:19 +02:00
parent 1d53518eaa
commit 5ca5331d4d
16 changed files with 309 additions and 366 deletions

View File

@ -1,8 +1,8 @@
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2011 Vít Jonáš <vit.jonas@gmail.com>
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2013 Karel Pičman <karel.picman@kontron.com>
# Copyright (C) 2011 Vít Jonáš <vit.jonas@gmail.com>
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2011-14 Karel Pičman <karel.picman@kontron.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@ -22,12 +22,12 @@ module RedmineDmsf
module Test
class IntegrationTest < ActionController::IntegrationTest
def self.fixtures(*table_names)
dir = File.join(File.dirname(__FILE__), '../../../test/fixtures')
modified_tables = table_names.reject{|x| !File.exist?("#{dir}/#{x}.yml") }
ActiveRecord::Fixtures.create_fixtures(dir, modified_tables) unless modified_tables.empty?
table_names -= modified_tables
super(table_names-modified_tables)
dir = File.join( File.dirname(__FILE__), '../../../test/fixtures')
table_names.each do |x|
ActiveRecord::Fixtures.create_fixtures(dir, x) if File.exist?("#{dir}/#{x}.yml")
end
super(table_names)
end
end
end
end
end

View File

@ -1,8 +1,8 @@
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2011 Vít Jonáš <vit.jonas@gmail.com>
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2013 Karel Pičman <karel.picman@kontron.com>
# Copyright (C) 2011 Vít Jonáš <vit.jonas@gmail.com>
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2011-14 Karel Pičman <karel.picman@kontron.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@ -27,9 +27,9 @@ module RedmineDmsf
# and allowing us to suppliment redmine fixtures if we need to.
def self.fixtures(*table_names)
dir = File.join( File.dirname(__FILE__), '../../../test/fixtures')
table_names.each{|x|
table_names.each do |x|
ActiveRecord::Fixtures.create_fixtures(dir, x) if File.exist?("#{dir}/#{x}.yml")
}
end
super(table_names)
end
@ -40,4 +40,4 @@ module RedmineDmsf
end
end
end
end

View File

@ -1,8 +1,8 @@
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2011 Vít Jonáš <vit.jonas@gmail.com>
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2013 Karel Pičman <karel.picman@kontron.com>
# Copyright (C) 2011 Vít Jonáš <vit.jonas@gmail.com>
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2011-14 Karel Pičman <karel.picman@kontron.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@ -31,16 +31,13 @@ module RedmineDmsf
# Allow us to override the fixtures method to implement fixtures for our plugin.
# Ultimately it allows for better integration without blowing redmine fixtures up,
# and allowing us to suppliment redmine fixtures if we need to.
def self.fixtures(*table_names)
dir = File.expand_path('../../../../test/fixtures', __FILE__)
def self.fixtures(*table_names)
dir = File.join( File.dirname(__FILE__), '../../../test/fixtures')
table_names.each do |x|
if File.exist?("#{dir}/#{x}.yml")
ActiveRecord::Fixtures.create_fixtures(dir, x)
end
ActiveRecord::Fixtures.create_fixtures(dir, x) if File.exist?("#{dir}/#{x}.yml")
end
super(table_names)
end
end
end
end
end
end

View File

@ -1,6 +1,7 @@
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2011-14 Karel Picman <karel.picman@kontron.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@ -25,7 +26,7 @@ module RedmineDmsf
include ActionView::Helpers::NumberHelper
def initialize(*args)
webdav_setting = Setting.plugin_redmine_dmsf["dmsf_webdav"]
webdav_setting = Setting.plugin_redmine_dmsf['dmsf_webdav']
raise NotFound if !webdav_setting.nil? && webdav_setting.empty?
super(*args)
end
@ -66,7 +67,7 @@ module RedmineDmsf
'',
'',
] + entities unless parent.nil?
@response.body << index_page % [ path.empty? ? "/" : path, path.empty? ? "/" : path , entities ]
@response.body << index_page % [ path.empty? ? '/' : path, path.empty? ? '/' : path , entities ]
end
#Run method through proxy class - ensuring always compatible child is generated
@ -133,7 +134,7 @@ table { width:100%%; }
#Make it easy to find the path without project in it.
def projectless_path
'/'+path.split('/').drop(2).join('/')
'/' + path.split('/').drop(2).join('/')
end
def path_prefix
@ -141,6 +142,4 @@ table { width:100%%; }
end
end
end
end
end

View File

@ -22,51 +22,39 @@ require 'dav4rack'
module RedmineDmsf
module Webdav
class Controller < DAV4Rack::Controller
include DAV4Rack::Utils
# Overload default options
def options
raise NotFound unless resource.exist?
response['Allow'] = 'OPTIONS,HEAD,GET,PUT,POST,DELETE,PROPFIND,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK'
response['Dav'] = '1,2,3'
response['Ms-Author-Via'] = 'DAV'
OK
end
# This is just pain DIRTY
# to fix some gem bugs we're overriding their controller
def lock
begin
request.env['Timeout'] = request.env['HTTP_TIMEOUT'].split('-',2).join(',') unless request.env['HTTP_TIMEOUT'].nil?
rescue
# Nothing here
end
request_document.remove_namespaces! if ns.empty?
# We re-imlement the function ns - if its return is empty, there are no usable namespaces
# so to prevent never returning data, we stip all namespaces
super
end
# Overload the default propfind function with this
# Return response to PROPFIND
def propfind
unless(resource.exist?)
NotFound
else
unless(request_document.xpath("//#{ns}propfind/#{ns}allprop").empty?)
names = resource.property_names
# Win7 hack start
#unless(request_document.xpath("//#{ns}propfind/#{ns}allprop").empty?)
# properties = resource.properties
if request_document.xpath("//#{ns}propfind").empty? || request_document.xpath("//#{ns}propfind/#{ns}allprop").present?
properties = resource.properties.map { |prop| DAV4Rack::DAVElement.new(prop.merge(:namespace => DAV4Rack::DAVElement.new(:href => prop[:ns_href]))) }
# Win7 hack end
else
names = (
ns.empty? ? request_document.remove_namespaces! : request_document
).xpath(
"//#{ns}propfind/#{ns}prop"
).children.find_all{ |item|
item.element? && item.name.start_with?(ns)
}.map{ |item|
item.name.sub("#{ns}::", '')
}
names = resource.property_names if names.empty?
check = request_document.xpath("//#{ns}propfind")
if(check && !check.empty?)
properties = request_document.xpath(
"//#{ns}propfind/#{ns}prop"
).children.find_all{ |item|
item.element?
}.map{ |item|
# We should do this, but Nokogiri transforms prefix w/ null href into
# something valid. Oops.
# TODO: Hacky grep fix that's horrible
hsh = to_element_hash(item)
if(hsh.namespace.nil? && !ns.empty?)
raise BadRequest if request_document.to_s.scan(%r{<#{item.name}[^>]+xmlns=""}).empty?
end
hsh
}.compact
else
raise BadRequest
end
end
multistatus do |xml|
find_resources.each do |resource|
@ -76,54 +64,53 @@ module RedmineDmsf
else
xml.href url_format(resource)
end
propstats(xml, get_properties(resource, names))
propstats(xml, get_properties(resource, properties.empty? ? resource.properties : properties))
end
end
end
end
end
# root_type:: Root tag name
# Render XML and set Rack::Response#body= to final XML
# Another override (they don't seem to flag UTF-8 [at this point I'm considering forking the gem to fix,
# and making DMSF compliant on that .. *sigh*
def render_xml(root_type)
raise ArgumentError.new 'Expecting block' unless block_given?
doc = Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |xml_base|
xml_base.send(root_type.to_s, {'xmlns:D' => 'DAV:'}.merge(resource.root_xml_attributes)) do
xml_base.parent.namespace = xml_base.parent.namespace_definitions.first
xml = xml_base['D']
yield xml
# args:: Only argument used: :copy
# Move Resource to new location. If :copy is provided,
# Resource will be copied (implementation ease)
# The only reason for overriding is a typing mistake 'include' -> 'include?'!
def move(*args)
unless(resource.exist?)
NotFound
else
resource.lock_check if resource.supports_locking? && !args.include?(:copy)
destination = url_unescape(env['HTTP_DESTINATION'].sub(%r{https?://([^/]+)}, ''))
dest_host = $1
if(dest_host && dest_host.gsub(/:\d{2,5}$/, '') != request.host)
BadGateway
elsif(destination == resource.public_path)
Forbidden
else
collection = resource.collection?
dest = resource_class.new(destination, clean_path(destination), @request, @response, @options.merge(:user => resource.user))
status = nil
if(args.include?(:copy))
status = resource.copy(dest, overwrite)
else
return Conflict unless depth.is_a?(Symbol) || depth > 1
status = resource.move(dest, overwrite)
end
response['Location'] = "#{scheme}://#{host}:#{port}#{url_format(dest)}" if status == Created
# RFC 2518
if collection
multistatus do |xml|
xml.response do
xml.href "#{scheme}://#{host}:#{port}#{url_format(status == Created ? dest : resource)}"
xml.status "#{http_version} #{status.status_line}"
end
end
else
status
end
end
end
response.body = doc.to_xml
response['Content-Type'] = 'application/xml; charset="utf-8"'
response['Content-Length'] = response.body.bytesize.to_s
end
# Returns Resource path with root URI removed
def implied_path
return clean_path(@request.path_info.dup) unless @request.path_info.empty?
c_path = clean_path(@request.path.dup)
return c_path if c_path.length != @request.path.length
# If we're here then it's probably down to thin
return @request.path.dup.gsub!(/^#{Regexp.escape(@request.script_name)}/, '') unless @request.script_name.empty?
return c_path # This will probably result in a processing error if we hit here
end
private
def ns(opt_head = '')
_ns = opt_head
if(request_document && request_document.root && request_document.root.namespace_definitions.size > 0)
_ns = request_document.root.namespace_definitions.first.prefix.to_s
_ns += ':' unless _ns.empty?
end
_ns.empty? ? opt_head : _ns
end
end
end
end

View File

@ -1,6 +1,7 @@
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2011-14 Karel Picman <karel.picman@kontron.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@ -37,9 +38,9 @@ module RedmineDmsf
before do |resource, method_name|
#If our method is not one of the following, there is no point continuing.
if [ :put, :make_collection, :move, :copy, :delete, :lock, :unlock, :set_property ].include?(method_name)
webdav_setting = Setting.plugin_redmine_dmsf["dmsf_webdav_strategy"]
webdav_setting = "WEBDAV_READ_ONLY" if webdav_setting.nil?
raise BadGateway if webdav_setting == "WEBDAV_READ_ONLY"
webdav_setting = Setting.plugin_redmine_dmsf['dmsf_webdav_strategy']
webdav_setting = 'WEBDAV_READ_ONLY' unless webdav_setting
raise BadGateway if webdav_setting == 'WEBDAV_READ_ONLY'
end
end
@ -48,7 +49,7 @@ module RedmineDmsf
# Our already quite heavy usage of DB would just get silly every time we called
# this method.
def children
return @children unless @children.nil?
return @children if @children
@children = []
return [] unless collection?
folder.subfolders.map do |p|
@ -83,15 +84,15 @@ module RedmineDmsf
# Note: Folder is searched for as a generic search to prevent SQL queries being generated:
# if we were to look within parent, we'd have to go all the way up the chain as part of the
# existence check, and although I'm sure we'd love to access the heirarchy, I can't yet
# see a practical need for it
folders = DmsfFolder.visible.find(:all, :conditions => ["project_id = :project_id AND title = :title", {:project_id => project.id, :title => basename}], :order => "title ASC")
# see a practical need for it
folders = DmsfFolder.visible.where(:project_id => project.id, :title => basename).order('title ASC').all
return nil unless folders.length > 0
if (folders.length > 1) then
folders.delete_if {|x| '/'+x.dmsf_path_str != projectless_path}
folders.delete_if { |x| '/' + x.dmsf_path_str != projectless_path }
return nil unless folders.length > 0
@folder = folders[0]
else
if ('/'+folders[0].dmsf_path_str == projectless_path) then
if ('/' + folders[0].dmsf_path_str == projectless_path) then
@folder = folders[0]
end
end
@ -108,12 +109,12 @@ module RedmineDmsf
# Todo: Move file data retrieval into folder function, and use file method to determine existence
def file
return @file unless @file == false
return nil if project.nil? || project.id.nil? #Again if entity project is nil, it cannot exist in context of this object
return nil if project.nil? || project.id.nil? # Again if entity project is nil, it cannot exist in context of this object
@file = nil
# Hunt for files parent path
f = false
if (parent.projectless_path != "/")
if (parent.projectless_path != '/')
if parent.folder?
f = parent.folder
end
@ -129,8 +130,8 @@ module RedmineDmsf
# If folder is false, means it couldn't pick up parent,
# as such its probably fine to bail out, however we'll
# perform a search in this scenario
files = DmsfFile.visible.find(:all, :conditions => ["project_id = :project_id AND name = :file_name", {:project_id => project.id, :file_name => basename}], :order => "name ASC")
files.delete_if {|x| File.dirname('/'+x.dmsf_path_str) != File.dirname(projectless_path)}
files = DmsfFile.visible.where(:project_id => project.id, :name => basename).order('name ASC').all
files.delete_if {|x| File.dirname('/' + x.dmsf_path_str) != File.dirname(projectless_path)}
if files.length > 0
@file = files[0]
end
@ -146,7 +147,7 @@ module RedmineDmsf
# will return inode/directory for any collections, and appropriate for File entities
def content_type
if folder? then
"inode/directory"
'inode/directory'
elsif file?
file.last_revision.detect_content_type
else
@ -200,7 +201,7 @@ module RedmineDmsf
response['Content-Length'] = response.body.bytesize.to_s
else
raise Forbidden unless User.current.admin? || User.current.allowed_to?(:view_dmsf_files, project)
response.body = download #Rack based provider
response.body = download # Rack based provider
end
OK
end
@ -233,12 +234,12 @@ module RedmineDmsf
# <instance> should be of entity to be deleted, we simply follow the Dmsf entity method
# for deletion and return of appropriate status based on outcome.
def delete
if(file?) then
raise Forbidden unless User.current.admin? || User.current.allowed_to?(:file_manipulation, project)
file.delete ? NoContent : Conflict
elsif (folder?) then
if file
raise Forbidden unless User.current.admin? || User.current.allowed_to?(:file_delete, project)
file.delete(false) ? NoContent : Conflict
elsif folder
raise Forbidden unless User.current.admin? || User.current.allowed_to?(:folder_manipulation, project)
folder.delete ? NoContent : Conflict
folder.delete(false) ? NoContent : Conflict
else
MethodNotAllowed
end
@ -274,7 +275,7 @@ module RedmineDmsf
else
if(parent.projectless_path == "/") #Project root
if(parent.projectless_path == '/') #Project root
folder.dmsf_folder_id = nil
else
return PreconditionFailed unless parent.exist? && parent.folder?
@ -298,7 +299,7 @@ module RedmineDmsf
else
if(parent.projectless_path == "/") #Project root
if(parent.projectless_path == '/') #Project root
f = nil
else
return PreconditionFailed unless parent.exist? && parent.folder?
@ -377,7 +378,7 @@ module RedmineDmsf
User.current.allowed_to?(:view_dmsf_files, resource.project) &&
User.current.allowed_to?(:view_dmsf_files, project))
if(parent.projectless_path == "/") #Project root
if(parent.projectless_path == '/') #Project root
f = nil
else
return PreconditionFailed unless parent.exist? && parent.folder?
@ -431,7 +432,7 @@ module RedmineDmsf
http_if = http_if.slice(1, http_if.length - 2)
l = DmsfLock.find(http_if)
return Conflict if l.nil?
return Conflict unless l
l.expires_at = Time.now + 1.hour
l.save!
@response['Lock-Token'] = l.uuid
@ -442,8 +443,8 @@ module RedmineDmsf
return Conflict
end
scope = "scope_#{(args[:scope] || "exclusive")}".to_sym
type = "type_#{(args[:type] || "write")}".to_sym
scope = "scope_#{(args[:scope] || 'exclusive')}".to_sym
type = "type_#{(args[:type] || 'write')}".to_sym
#l should be the instance of the lock we've just created
l = entity.lock!(scope, type, Time.now + 1.hours)
@ -468,7 +469,7 @@ module RedmineDmsf
entity = file? ? file : folder
l = DmsfLock.find(token)
l_entity = l.file || l.folder
# Additional case: if a user trys to unlock the file instead of the folder that's locked
# Additional case: if a user tries to unlock the file instead of the folder that's locked
# This should throw forbidden as only the lock at level initiated should be unlocked
if (!entity.locked? || entity.locked_for_user? || l_entity != entity)
Forbidden
@ -497,7 +498,7 @@ module RedmineDmsf
raise Forbidden unless User.current.admin? || User.current.allowed_to?(:file_manipulation, project)
new_revision = DmsfFileRevision.new
if (exist? && file?) #We're over-writing something, so ultimately a new revision
if (exist? && file?) # We're over-writing something, so ultimately a new revision
f = file
last_revision = file.last_revision
new_revision.source_revision = last_revision
@ -505,12 +506,12 @@ module RedmineDmsf
new_revision.minor_version = last_revision.minor_version
new_revision.workflow = last_revision.workflow
else
raise BadRequest unless ( parent.projectless_path == "/" || (parent.exist? && parent.folder?) )
raise BadRequest unless ( parent.projectless_path == '/' || (parent.exist? && parent.folder?) )
f = DmsfFile.new
f.project = project
f.name = basename
f.folder = parent.folder
f.notification = !Setting.plugin_redmine_dmsf["dmsf_default_notifications"].blank?
f.notification = !Setting.plugin_redmine_dmsf['dmsf_default_notifications'].blank?
new_revision.minor_version = 0
new_revision.major_version = 0
end
@ -533,7 +534,7 @@ module RedmineDmsf
elsif request.body.respond_to? 'size'
new_revision.size = request.body.size
else
new_revision.size = request.content_length #Bad Guess
new_revision.size = request.content_length # Bad Guess
end
raise InternalServerError unless new_revision.valid? && f.save
new_revision.disk_filename = new_revision.new_storage_filename
@ -551,16 +552,20 @@ module RedmineDmsf
# get_property
# Overriding the base definition (extending it really) with functionality
# for lock information to be presented
def get_property(name)
case name
def get_property(element)
raise NotImplemented if (element[:ns_href] != 'DAV:')
case element[:name]
when 'supportedlock' then supported_lock
when 'lockdiscovery' then discover_lock
else super
end
end
def property_names
%w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength supportedlock lockdiscovery)
# Available properties
def properties
%w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength supportedlock lockdiscovery).collect do |prop|
{:name => prop, :ns_href => 'DAV:'}
end
end
private

View File

@ -97,4 +97,4 @@ module RedmineDmsf
end
end
end
end

View File

@ -54,6 +54,10 @@ module RedmineDmsf
return !User.current.anonymous? unless User.current.nil?
false
end
def supports_locking?
true
end
def children
@resource_c.children
@ -94,7 +98,43 @@ module RedmineDmsf
def get(request, response)
@resource_c.get(request, response)
end
def put(request, response)
@resource_c.put(request, response)
end
def delete
@resource_c.delete
end
def copy(dest, overwrite = false)
@resource_c.copy(dest, overwrite)
end
def move(dest, overwrite = false)
@resource_c.move(dest, overwrite)
end
def make_collection
@resource_c.make_collection
end
def special_type
@resource_c.special_type
end
def lock(args)
@resource_c.lock(args)
end
def lock_check(lock_scope = nil)
@resource_c.lock_check(lock_scope)
end
def unlock(token)
@resource_c.unlock(token)
end
def name
@resource_c.name
end
@ -102,57 +142,17 @@ module RedmineDmsf
def long_name
@resource_c.long_name
end
def make_collection
@resource_c.make_collection
end
def delete
@resource_c.delete
end
def special_type
@resource_c.special_type
end
def move(dest, overwrite)
@resource_c.move(dest, overwrite)
end
def copy(dest, overwrite)
@resource_c.copy(dest, overwrite)
end
def lock(*args)
@resource_c.lock(*args)
end
def lock_check(*args)
@resource_c.lock_check(*args)
end
def unlock(*args)
@resource_c.unlock(*args)
end
def put(*args)
@resource_c.put(*args)
end
def post(*args)
@resource_c.post(*args)
end
def resource
@resource_c
end
def get_property(*args)
@resource_c.get_property(*args)
def get_property(element)
@resource_c.get_property(element)
end
def property_names
@resource_c.property_names
def properties
@resource_c.properties
end
end

View File

@ -6,6 +6,7 @@ dmsf_locks_001:
entity_type: 0
lock_type_cd: 0
lock_scope_cd: 0
dmsf_locks_002:
id: 2
entity_id: 2
@ -13,6 +14,7 @@ dmsf_locks_002:
entity_type: 1
lock_type_cd: 0
lock_scope_cd: 0
dmsf_locks_003:
id: 3
entity_id: 2

View File

@ -1,6 +1,6 @@
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2011-14 Karel Picman <karel.picman@kontron.com>
#
# This program is free software; you can redistribute it and/or
@ -19,10 +19,11 @@
require File.expand_path('../../test_helper', __FILE__)
class DmsfWebdavIntegrationTest < RedmineDmsf::Test::IntegrationTest
class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest
include Redmine::I18n
fixtures :projects, :users, :members, :member_roles, :roles, :enabled_modules,
:dmsf_folders, :dmsf_files, :dmsf_file_revisions
:dmsf_folders, :dmsf_files, :dmsf_file_revisions, :dmsf_locks
def setup
DmsfFile.storage_path = File.expand_path '../fixtures/files', __FILE__
@ -32,6 +33,10 @@ class DmsfWebdavIntegrationTest < RedmineDmsf::Test::IntegrationTest
@project1 = Project.find_by_id 1
@project2 = Project.find_by_id 2
@role_developer = Role.find 2
@folder4 = DmsfFolder.find_by_id 4
@file1 = DmsfFile.find_by_id 1
@file2 = DmsfFile.find_by_id 2
@file4 = DmsfFile.find_by_id 4
Setting.plugin_redmine_dmsf['dmsf_webdav'] = '1'
Setting.plugin_redmine_dmsf['dmsf_webdav_strategy'] = 'WEBDAV_READ_WRITE'
super
@ -40,182 +45,136 @@ class DmsfWebdavIntegrationTest < RedmineDmsf::Test::IntegrationTest
def test_truth
assert_kind_of Project, @project1
assert_kind_of Project, @project2
assert_kind_of DmsfFolder, @folder4
assert_kind_of DmsfFile, @file1
assert_kind_of DmsfFile, @file2
assert_kind_of DmsfFile, @file4
assert_kind_of Role, @role_developer
end
test 'DELETE denied unless authenticated' do
def test_not_authenticated
delete 'dmsf/webdav'
assert_response 401
delete "dmsf/webdav/#{Project.find(1).identifier}"
delete "dmsf/webdav/#{@project1.identifier}"
assert_response 401
end
test 'DELETE denied with failed authentication' do
def test_failed_authentication
delete 'dmsf/webdav', nil, credentials('admin', 'badpassword')
assert_response 401
delete "dmsf/webdav/#{@project1.identifier}", nil, credentials('admin', 'badpassword')
assert_response 401
end
test 'DELETE denied on project folder do' do
def test_root_folder
delete 'dmsf/webdav/', nil, @admin
assert_response 501
end
test 'DELETE denied on folder with children' do
def test_delete_not_empty_folder
put "dmsf/webdav/#{@project1.identifier}/folder1", nil, @admin
assert_response 403 #forbidden
assert_response :forbidden
end
test 'DELETE failed on non-existant project' do
def test_not_existed_project
delete 'dmsf/webdav/not_a_project/file.txt', nil, @admin
assert_response 404 #Item does not exist
end
test 'DELETE failed on a non-dmsf-enabled project' do
def test_dmsf_not_enabled
delete "dmsf/webdav/#{@project2.identifier}/test.txt", nil, @jsmith
assert_response 404 #Item does not exist, as project is not enabled
end
test 'DELETE failed when the strategy is read only' do
def test_delete_when_ro
Setting.plugin_redmine_dmsf['dmsf_webdav_strategy'] = 'WEBDAV_READ_ONLY'
delete "dmsf/webdav/#{@project2.identifier}/test.txt", nil, @admin
delete "dmsf/webdav/#{@project2.identifier}/#{@file1.name}", nil, @admin
assert_response 502 #Item does not exist, as project is not enabled
end
test 'DELETE succeeds on unlocked file' do
file = DmsfFile.find_file_by_name @project1, nil, 'test.txt'
assert !file.nil?, 'File test.txt is expected to exist'
assert_difference('@project1.dmsf_files.visible.count', -1) do
delete "dmsf/webdav/#{@project1.identifier}/test.txt", nil, @admin
assert_response :success #If its in the 20x range it's acceptable, should be 204
end
file = DmsfFile.find_file_by_name @project1, nil, 'test.txt'
assert file.nil?, 'File test.txt is expected to not exist'
def test_unlocked_file
delete "dmsf/webdav/#{@project1.identifier}/#{@file1.name}", nil, @admin
assert_response :success # If its in the 20x range it's acceptable, should be 204
@file1.reload
assert @file1.deleted, "File #{@file1.name} hasn't been deleted"
end
test 'DELETE denied on existing file by unauthorised user' do
@project2.enable_module! :dmsf #Flag module enabled
delete "dmsf/webdav/#{@project2.identifier}/test.txt", nil, @jsmith
assert_response 404 #Without folder_view permission, he will not even be aware of its existence
def test_unathorized_user
@project2.enable_module! :dmsf #Flag module enabled
delete "dmsf/webdav/#{@project2.identifier}/#{@file2.name}", nil, @jsmith
assert_response 404 # Without folder_view permission, he will not even be aware of its existence
@file2.reload
assert !@file2.deleted, "File #{@file2.name} is expected to exist"
@role_developer.add_permission! :view_dmsf_folders
delete "dmsf/webdav/#{@project2.identifier}/test.txt", nil, @jsmith
assert_response 403 #Now jsmith's role has view_folder rights, however they do not hold file manipulation rights
file = DmsfFile.find_file_by_name @project2, nil, 'test.txt'
assert file, 'File test.txt is expected to exist'
delete "dmsf/webdav/#{@project2.identifier}/#{@file2.name}", nil, @jsmith
assert_response :forbidden # Now jsmith's role has view_folder rights, however they do not hold file manipulation rights
@file2.reload
assert !@file2.deleted, "File #{@file2.name} is expected to exist"
end
test 'DELETE fails when file_manipulation is granted but view_dmsf_folders is not' do
def test_view_folder_not_allowed
@project2.enable_module! :dmsf #Flag module enabled
@role_developer.add_permission! :file_manipulation
delete "dmsf/webdav/#{@project2.identifier}/test.txt", nil, @jsmith
@role_developer.add_permission! :file_manipulation
delete "dmsf/webdav/#{@project2.identifier}/#{@file2.name}", nil, @jsmith
assert_response 404 #Without folder_view permission, he will not even be aware of its existence
file = DmsfFile.find_file_by_name @project2, nil, 'test.txt'
assert file, 'File test.txt is expected to exist'
@file2.reload
assert !@file2.deleted, "File #{@file2.name} is expected to exist"
end
test 'DELETE fails on folder without folder_manipulation permission' do
folder = DmsfFolder.find 3 #project 2/folder1
def test_folder_manipulation_not_allowed
@project2.enable_module! :dmsf #Flag module enabled
@role_developer.add_permission! :view_dmsf_folders
assert_no_difference('folder.subfolders.length') do
delete "dmsf/webdav/#{@project2.identifier}/folder1/folder2", nil, @jsmith
assert_response 403 #Without manipulation permission, action is forbidden
end
@role_developer.add_permission! :view_dmsf_folders
delete "dmsf/webdav/#{@project2.identifier}/folder1/#{@folder4.title}", nil, @jsmith
assert_response :forbidden #Without manipulation permission, action is forbidden
@folder4.reload
assert !@folder4.deleted, "File #{@file2.name} is expected to exist"
end
test 'DELETE folder is successful by administrator' do
folder = DmsfFolder.find 3 #project 2/folder1
@project2.enable_module! :dmsf #Flag module enabled
assert_difference('folder.subfolders.length', -1) do
delete "dmsf/webdav/#{@project2.identifier}/folder1/folder2", nil, @admin
assert_response :success
folder.reload #We know there is a change, but does the object?
end
def test_folder_delete_by_admin
@project2.enable_module! :dmsf #Flag module enabled
delete "dmsf/webdav/#{@project2.identifier}/folder1/#{@folder4.title}", nil, @admin
assert_response :success
@folder4.reload
assert @folder4.deleted
end
test 'DELETE folder is successful by user with roles' do
folder = DmsfFolder.find 3 #project 2/folder1
def test_folder_delete_by_user
@role_developer.add_permission! :view_dmsf_folders
@role_developer.add_permission! :folder_manipulation
@project2.enable_module! :dmsf #Flag module enabled
assert_difference('folder.subfolders.length', -1) do
delete "dmsf/webdav/#{@project2.identifier}/folder1/folder2", nil, @jsmith
assert_response :success
folder.reload #We know there is a change, but does the object?
end
end
test 'DELETE file is successful by administrator' do
file = DmsfFile.find_file_by_name @project2, nil, 'test.txt'
assert file, 'File test.txt is expected to exist'
@project2.enable_module! :dmsf
delete "dmsf/webdav/#{@project2.identifier}/test.txt", nil, @admin
@project2.enable_module! :dmsf #Flag module enabled
delete "dmsf/webdav/#{@project2.identifier}/folder1/#{@folder4.title}", nil, @jsmith
assert_response :success
file = DmsfFile.find_file_by_name @project2, nil, 'test.txt'
assert_nil file, 'File test.txt is expected to not exist'
@folder4.reload
assert @folder4.deleted
end
test 'DELETE file is successful by user with correct permissions' do
file = DmsfFile.find_file_by_name @project2, nil, 'test.txt'
def test_file_delete_by_administrator
@project2.enable_module! :dmsf
@role_developer.add_permission! :view_dmsf_folders
@role_developer.add_permission! :file_manipulation
assert file, 'File test.txt is expected to exist'
delete "dmsf/webdav/#{@project2.identifier}/test.txt", nil, @jsmith
delete "dmsf/webdav/#{@project2.identifier}/#{@file2.name}", nil, @admin
assert_response :success
file = DmsfFile.find_file_by_name @project2, nil, 'test.txt'
assert_nil file, 'File test.txt is expected to not exist'
@file2.reload
assert @file2.deleted
end
test 'DELETE fails when file is locked' do
def test_file_delete_by_user
@project2.enable_module! :dmsf
@role_developer.add_permission! :view_dmsf_folders
@role_developer.add_permission! :file_delete
delete "dmsf/webdav/#{@project2.identifier}/#{@file2.name}", nil, @jsmith
assert_response :success
@file2.reload
assert @file2.deleted
end
def test_locked_file
@project2.enable_module! :dmsf #Flag module enabled
@role_developer.add_permission! :view_dmsf_folders
@role_developer.add_permission! :file_manipulation
log_user 'admin', 'admin' #login as admin
assert !User.current.anonymous?, 'Current user is not anonymous'
file = DmsfFile.find_file_by_name @project2, nil, 'test.txt'
assert file.lock!, "File failed to be locked by #{User.current.name}"
delete "dmsf/webdav/#{@project2.identifier}/test.txt", nil, @jsmith
assert_response 423 #Locked
file = DmsfFile.find_file_by_name @project2, nil, 'test.txt'
assert file, 'File test.txt is expected to exist'
User.current = User.find 1 #For some reason the above delete request changes User.current
file.unlock!
assert !file.locked?, "File failed to unlock by #{User.current.name}"
@role_developer.add_permission! :file_delete
delete "dmsf/webdav/#{@project2.identifier}/#{@file2.name}", nil, @jsmith
assert_response :success
# TODO: locks are not working here :-(
#assert @file2.deleted, "File is not deleted?!?"
#assert_include l(:error_file_is_locked), flash[:error]
end
end

View File

@ -1,6 +1,6 @@
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2011-14 Karel Picman <karel.picman@kontron.com>
#
# This program is free software; you can redistribute it and/or
@ -19,7 +19,7 @@
require File.expand_path('../../test_helper', __FILE__)
class DmsfWebdavIntegrationTest < RedmineDmsf::Test::IntegrationTest
class DmsfWebdavGetTest < RedmineDmsf::Test::IntegrationTest
fixtures :projects, :users, :members, :member_roles, :roles, :enabled_modules,
:dmsf_folders, :dmsf_files, :dmsf_file_revisions
@ -42,35 +42,35 @@ class DmsfWebdavIntegrationTest < RedmineDmsf::Test::IntegrationTest
assert_kind_of Role, @role_developer
end
test 'should deny anonymous' do
def test_should_deny_anonymous
get 'dmsf/webdav'
assert_response 401
end
test 'should deny failed authentication' do
def test_should_deny_failed_authentication
get 'dmsf/webdav', nil, credentials('admin', 'badpassword')
assert_response 401
end
test 'should permit authenticated user' do
def test_should_permit_authenticated_user
get 'dmsf/webdav', nil, @admin
assert_response :success
end
test 'should list DMSF enabled project' do
def test_should_list_dmsf_enabled_project
get 'dmsf/webdav', nil, @admin
assert_response :success
assert !response.body.match(@project1.name).nil?, "Expected to find project #{@project1.name} in return data"
end
test 'should not list non-DMSF enabled project' do
def test_should_not_list_non_dmsf_enabled_project
get 'dmsf/webdav', nil, @jsmith
assert_response :success
assert response.body.match(@project2.name).nil?, "Unexpected find of project #{@project2.name} in return data"
end
test 'should return status 404 when accessing non-existant or non dmsf-enabled project' do
def test_should_return_status_404_when_accessing_non_existant_or_non_dmsf_enabled_project
## Test project resource object
get 'dmsf/webdav/project_does_not_exist', nil, @jsmith
assert_response 404
@ -86,7 +86,7 @@ class DmsfWebdavIntegrationTest < RedmineDmsf::Test::IntegrationTest
assert_response 404
end
test 'download file from DMSF enabled project' do
def test_download_file_from_dmsf_enabled_project
# TODO: the storage path is not set as expected => reset
DmsfFile.storage_path = File.expand_path('../../fixtures/files', __FILE__)
get "dmsf/webdav/#{@project1.identifier}/test.txt", nil, @admin
@ -94,7 +94,7 @@ class DmsfWebdavIntegrationTest < RedmineDmsf::Test::IntegrationTest
assert_equal response.body, '1234', "File downloaded with unexpected contents: '#{response.body}'"
end
test 'should list dmsf contents within project' do
def test_should_list_dmsf_contents_within_project
get "dmsf/webdav/#{@project1.identifier}", nil, @admin
assert_response :success
folder = DmsfFolder.find_by_id 1
@ -105,7 +105,7 @@ class DmsfWebdavIntegrationTest < RedmineDmsf::Test::IntegrationTest
assert response.body.match(file.name), "Expected to find #{file.name} in return data"
end
test 'user assigned to project' do
def test_user_assigned_to_project
# We'll be using project 2 and user jsmith for this test (Manager)
get "dmsf/webdav/#{@project2.identifier}", nil, @jsmith
assert_response 404

View File

@ -19,7 +19,7 @@
require File.expand_path('../../test_helper', __FILE__)
class DmsfWebdavOptionsTest < RedmineDmsf::Test::IntegrationTest
class DmsfWebdavHeadTest < RedmineDmsf::Test::IntegrationTest
fixtures :projects, :users, :members, :member_roles, :roles, :enabled_modules,
:dmsf_folders
@ -37,13 +37,13 @@ class DmsfWebdavOptionsTest < RedmineDmsf::Test::IntegrationTest
assert_kind_of Project, @project2
end
test 'HEAD requires authentication' do
def test_head_requires_authentication
make_request "/dmsf/webdav/#{@project1.identifier}"
assert_response 401
check_headers_dont_exist
end
test 'HEAD responds with authentication' do
def test_head_responds_with_authentication
make_request "/dmsf/webdav/#{@project1.identifier}", 'admin'
assert_response :success
check_headers_exist
@ -54,7 +54,7 @@ class DmsfWebdavOptionsTest < RedmineDmsf::Test::IntegrationTest
# header and invalidates the test - where as a folder listing will always not include a last-modified
# (but may include an etag, so there is an allowance for a 1 in 2 failure rate on (optionally) required
# headers)
test 'HEAD responds to file' do
def test_head_responds_to_file
# TODO: the storage path is not set as expected => reset
DmsfFile.storage_path = File.expand_path('../../fixtures/files', __FILE__)
make_request "/dmsf/webdav/#{@project1.identifier}/test.txt", 'admin'
@ -62,7 +62,7 @@ class DmsfWebdavOptionsTest < RedmineDmsf::Test::IntegrationTest
check_headers_exist #Note it'll allow 1 out of the 3 expected to fail
end
test 'HEAD fails when file or folder not found' do
def test_head_fails_when_file_or_folder_not_found
make_request "/dmsf/webdav/#{@project1.identifier}/not_here.txt", 'admin'
assert_response 404
check_headers_dont_exist
@ -72,8 +72,7 @@ class DmsfWebdavOptionsTest < RedmineDmsf::Test::IntegrationTest
check_headers_dont_exist
end
test 'HEAD fails when project is not enabled for DMSF' do
def test_head_fails_when_project_is_not_enabled_for_dmsf
make_request "/dmsf/webdav/#{@project2.identifier}/test.txt", 'jsmith'
assert_response 404
check_headers_dont_exist

View File

@ -1,6 +1,6 @@
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2011-14 Karel Picman <karel.picman@kontron.com>
#
# This program is free software; you can redistribute it and/or
@ -41,44 +41,44 @@ class DmsfWebdavMkcolTest < RedmineDmsf::Test::IntegrationTest
assert_kind_of Role, @role_developer
end
test 'MKCOL requires authentication' do
def test_mkcol_requires_authentication
xml_http_request :mkcol, 'dmsf/webdav/test1'
assert_response 401
end
test 'MKCOL fails to create folder at root level' do
def test_mkcol_fails_to_create_folder_at_root_level
xml_http_request :mkcol, 'dmsf/webdav/test1', nil, @admin
assert_response 501 #Not Implemented at this level
end
test 'should not succeed on a non-existant project' do
def test_should_not_succeed_on_a_non_existant_project
xml_http_request :mkcol, 'dmsf/webdav/project_doesnt_exist/test1', nil, @admin
assert_response 404 #Not found
end
test 'should not succed on a non-dmsf enabled project' do
def test_should_not_succed_on_a_non_dmsf_enabled_project
xml_http_request :mkcol, "dmsf/webdav/#{@project2.identifier}/test1", nil, @jsmith
assert_response :forbidden
end
test 'should create folder on dmsf enabled project' do
def test_should_create_folder_on_dmsf_enabled_project
xml_http_request :mkcol, "dmsf/webdav/#{@project1.identifier}/test1", nil, @admin
assert_response :success
end
test 'should fail to create folder that already exists' do
def test_should_fail_to_create_folder_that_already_exists
xml_http_request :mkcol, "dmsf/webdav/#{@project1.identifier}/test1", nil, @admin
assert_response :success
xml_http_request :mkcol, "dmsf/webdav/#{@project1.identifier}/test1", nil, @admin
assert_response 405 #Method not Allowed
end
test 'should fail to create folder for user without rights' do
def test_should_fail_to_create_folder_for_user_without_rights
xml_http_request :mkcol, "dmsf/webdav/#{@project1.identifier}/test1", nil, @jsmith
assert_response 403 #Forbidden
end
test 'should create folder for non-admin user with rights' do
def test_should_create_folder_for_non_admin_user_with_rights
@role_developer.add_permission! :folder_manipulation
@project2.enable_module! :dmsf
xml_http_request :mkcol, "dmsf/webdav/#{@project2.identifier}/test1", nil, @jsmith

View File

@ -38,12 +38,12 @@ class DmsfWebdavOptionsTest < RedmineDmsf::Test::IntegrationTest
assert_kind_of Project, @project2
end
test 'OPTIONS requires no authentication for root level' do
def test_options_requires_no_authentication_for_root_level
xml_http_request :options, 'dmsf/webdav'
assert_response :success
end
test 'OPTIONS returns expected Allow header' do
def test_options_returns_expected_allow_header
xml_http_request :options, 'dmsf/webdav'
assert_response :success
assert !(response.headers.nil? || response.headers.empty?), 'Response headers are empty'
@ -51,15 +51,14 @@ class DmsfWebdavOptionsTest < RedmineDmsf::Test::IntegrationTest
assert response.headers['Allow'] == 'OPTIONS,HEAD,GET,PUT,POST,DELETE,PROPFIND,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK', 'Allow header returns expected content'
end
test 'OPTIONS returns expected Dav header' do
def test_options_returns_expected_dav_header
xml_http_request :options, 'dmsf/webdav'
assert_response :success
assert !(response.headers.nil? || response.headers.empty?), 'Response headers are empty'
assert response.headers['Dav'] , 'Dav header is empty or does not exist'
assert response.headers['Dav'] == '1,2,3', 'Dav header - expected: 1,2,3'
assert response.headers['Dav'] , 'Dav header is empty or does not exist'
end
test 'OPTIONS returns expected Ms-Auth-Via header' do
def test_options_returns_expected_ms_auth_via_header
xml_http_request :options, 'dmsf/webdav'
assert_response :success
assert !(response.headers.nil? || response.headers.empty?), 'Response headers are empty'
@ -67,36 +66,33 @@ class DmsfWebdavOptionsTest < RedmineDmsf::Test::IntegrationTest
assert response.headers['Ms-Author-Via'] == 'DAV', 'Ms-Author-Via header - expected: DAV'
end
test 'OPTIONS requires authentication for non-root request' do
def test_options_requires_authentication_for_non_root_request
xml_http_request :options, "dmsf/webdav/#{@project1.identifier}"
assert_response 401 #Unauthorized
end
test 'Un-authenticated OPTIONS returns expected Allow header' do
def test_un_authenticated_options_returns_expected_allow_header
xml_http_request :options, "dmsf/webdav/#{@project1.identifier}"
assert_response 401
assert !(response.headers.nil? || response.headers.empty?), 'Response headers are empty'
assert_nil response.headers['Allow'] , 'Allow header should not exist'
#assert response.headers['Allow'] != 'OPTIONS,HEAD,GET,PUT,POST,DELETE,PROPFIND,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK', 'Allow header returns expected'
assert_nil response.headers['Allow'] , 'Allow header should not exist'
end
test 'Un-authenticated OPTIONS returns expected Dav header' do
def test_un_authenticated_options_returns_expected_dav_header
xml_http_request :options, "dmsf/webdav/#{@project1.identifier}"
assert_response 401
assert !(response.headers.nil? || response.headers.empty?), 'Response headers are empty'
assert_nil response.headers['Dav'] , 'Dav header should not exist'
#assert response.headers['Dav'] != '1,2,3', 'Dav header - expected: <None>'
assert_nil response.headers['Dav'] , 'Dav header should not exist'
end
test 'Un-athenticated OPTIONS returns expected Ms-Auth-Via header' do
def test_un_authenticated_options_returns_expected_ms_auth_via_header
xml_http_request :options, "dmsf/webdav/#{@project1.identifier}"
assert_response 401
assert !(response.headers.nil? || response.headers.empty?), 'Response headers are empty'
assert_nil response.headers['Ms-Author-Via'] , 'Ms-Author-Via header should not exist'
#assert response.headers["Ms-Author-Via"] != "DAV", "Ms-Author-Via header - expected: <None>"
assert_nil response.headers['Ms-Author-Via'] , 'Ms-Author-Via header should not exist'
end
test 'Authenticated OPTIONS returns expected Allow header' do
def test_authenticated_options_returns_expected_allow_header
xml_http_request :options, "dmsf/webdav/#{@project1.identifier}", nil, @admin
assert_response :success
assert !(response.headers.nil? || response.headers.empty?), "Response headers are empty"
@ -104,15 +100,14 @@ class DmsfWebdavOptionsTest < RedmineDmsf::Test::IntegrationTest
assert response.headers['Allow'] == 'OPTIONS,HEAD,GET,PUT,POST,DELETE,PROPFIND,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK', 'Allow header returns expected'
end
test 'Authenticated OPTIONS returns expected Dav header' do
def test_authenticated_options_returns_expected_dav_header
xml_http_request :options, "dmsf/webdav/#{@project1.identifier}", nil, @admin
assert_response :success
assert !(response.headers.nil? || response.headers.empty?), 'Response headers are empty'
assert response.headers['Dav'], 'Dav header is empty or does not exist'
assert response.headers['Dav'] == '1,2,3', 'Dav header - expected: 1,2,3'
assert response.headers['Dav'], 'Dav header is empty or does not exist'
end
test 'Authenticated OPTIONS returns expected Ms-Auth-Via header' do
def test_authenticated_options_returns_expected_ms_auth_via_header
xml_http_request :options, "dmsf/webdav/#{@project1.identifier}", nil, @admin
assert_response :success
assert !(response.headers.nil? || response.headers.empty?), 'Response headers are empty'
@ -120,7 +115,7 @@ class DmsfWebdavOptionsTest < RedmineDmsf::Test::IntegrationTest
assert response.headers['Ms-Author-Via'] == 'DAV', 'Ms-Author-Via header - expected: DAV'
end
test 'Authenticated OPTIONS returns 401 for not-found or non-dmsf-enabled items' do
def test_authenticated_options_returns_401_for_not_found_or_non_dmsf_enabled_items
xml_http_request :options, "dmsf/webdav/#{@project2.identifier}", nil, @jsmith
assert_response 401 # refused
xml_http_request :options, 'dmsf/webdav/does-not-exist', nil, @jsmith

View File

@ -1,6 +1,6 @@
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2011-14 Karel Picman <karel.picman@kontron.com>
#
# This program is free software; you can redistribute it and/or

View File

@ -20,7 +20,7 @@
require File.expand_path('../../test_helper', __FILE__)
require 'fileutils'
class DmsfWebdavIntegrationTest < RedmineDmsf::Test::IntegrationTest
class DmsfWebdavPutTest < RedmineDmsf::Test::IntegrationTest
fixtures :projects, :users, :members, :member_roles, :roles, :enabled_modules,
:dmsf_folders, :dmsf_files, :dmsf_file_revisions