#1133 Litmus test suite
This commit is contained in:
parent
3f22ce6f05
commit
6faed31397
@ -273,6 +273,15 @@ class DmsfFile < ActiveRecord::Base
|
||||
file = DmsfFile.new
|
||||
file.dmsf_folder_id = folder.id if folder
|
||||
file.project_id = project.id
|
||||
if DmsfFile.where(project_id: file.project_id, dmsf_folder_id: file.dmsf_folder_id, name: filename).exists?
|
||||
1.step do |i|
|
||||
gen_filename = " #{filename} #{l(:dmsf_copy, n: i)}"
|
||||
unless DmsfFile.where(project_id: file.project_id, dmsf_folder_id: file.dmsf_folder_id, name: gen_filename).exists?
|
||||
filename = gen_filename
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
file.name = filename
|
||||
file.notification = Setting.plugin_redmine_dmsf['dmsf_default_notifications'].present?
|
||||
if file.save && last_revision
|
||||
|
||||
@ -416,6 +416,8 @@ cs:
|
||||
label_scroll_down: Posunout se dolů
|
||||
note_webdav_disabled: WebDAV je zablokovaný. Kontaktujte administrátora.
|
||||
|
||||
dmsf_copy: "Kopie (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: My locked documents
|
||||
|
||||
@ -415,6 +415,8 @@ de:
|
||||
label_scroll_down: Runterscrollen
|
||||
note_webdav_disabled: WebDAV is disabled. Contact the administrator.
|
||||
|
||||
dmsf_copy: "Kopie (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: Von mir gesperrte Dokumente
|
||||
|
||||
@ -416,6 +416,8 @@ en:
|
||||
label_scroll_down: Scroll down
|
||||
note_webdav_disabled: WebDAV is disabled. Contact the administrator.
|
||||
|
||||
dmsf_copy: "Copy (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: My locked documents
|
||||
|
||||
@ -416,6 +416,8 @@ es:
|
||||
label_scroll_down: Scroll down
|
||||
note_webdav_disabled: WebDAV is disabled. Contact the administrator.
|
||||
|
||||
dmsf_copy: "Copy (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: My locked documents
|
||||
|
||||
@ -416,6 +416,8 @@ fr:
|
||||
label_scroll_down: Scroll down
|
||||
note_webdav_disabled: WebDAV is disabled. Contact the administrator.
|
||||
|
||||
dmsf_copy: "Copy (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: My locked documents
|
||||
|
||||
@ -415,6 +415,8 @@ hu:
|
||||
label_scroll_down: Scroll down
|
||||
note_webdav_disabled: WebDAV is disabled. Contact the administrator.
|
||||
|
||||
dmsf_copy: "Copy (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: My locked documents
|
||||
|
||||
@ -416,6 +416,8 @@ it: # Italian strings thx 2 Matteo Arceci!
|
||||
label_scroll_down: Scroll down
|
||||
note_webdav_disabled: WebDAV is disabled. Contact the administrator.
|
||||
|
||||
dmsf_copy: "Copy (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: My locked documents
|
||||
|
||||
@ -416,6 +416,8 @@ ja:
|
||||
label_scroll_down: Scroll down
|
||||
note_webdav_disabled: WebDAV is disabled. Contact the administrator.
|
||||
|
||||
dmsf_copy: "Copy (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: 自分がロック中の文書
|
||||
|
||||
@ -415,6 +415,8 @@ ko:
|
||||
label_scroll_down: Scroll down
|
||||
note_webdav_disabled: WebDAV is disabled. Contact the administrator.
|
||||
|
||||
dmsf_copy: "Copy (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: 내 잠긴 파일
|
||||
|
||||
@ -416,6 +416,8 @@ nl:
|
||||
label_scroll_down: Scroll down
|
||||
note_webdav_disabled: WebDAV is disabled. Contact the administrator.
|
||||
|
||||
dmsf_copy: "Copy (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: My locked documents
|
||||
|
||||
@ -416,6 +416,8 @@ pl:
|
||||
label_scroll_down: Scroll down
|
||||
note_webdav_disabled: WebDAV is disabled. Contact the administrator.
|
||||
|
||||
dmsf_copy: "Copy (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: My locked documents
|
||||
|
||||
@ -416,6 +416,8 @@ pt-BR:
|
||||
label_scroll_down: Scroll down
|
||||
note_webdav_disabled: WebDAV is disabled. Contact the administrator.
|
||||
|
||||
dmsf_copy: "Copy (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: My locked documents
|
||||
|
||||
@ -416,6 +416,8 @@ ru:
|
||||
label_scroll_down: Scroll down
|
||||
note_webdav_disabled: WebDAV is disabled. Contact the administrator.
|
||||
|
||||
dmsf_copy: "Copy (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: Мои заблокированные документы
|
||||
|
||||
@ -416,6 +416,8 @@ sl:
|
||||
label_scroll_down: Scroll down
|
||||
note_webdav_disabled: WebDAV is disabled. Contact the administrator.
|
||||
|
||||
dmsf_copy: "Copy (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: My locked documents
|
||||
|
||||
@ -415,6 +415,8 @@ zh-TW:
|
||||
label_scroll_down: Scroll down
|
||||
note_webdav_disabled: WebDAV is disabled. Contact the administrator.
|
||||
|
||||
dmsf_copy: "Copy (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: My locked documents
|
||||
|
||||
@ -416,6 +416,8 @@ zh:
|
||||
label_scroll_down: Scroll down
|
||||
note_webdav_disabled: WebDAV is disabled. Contact the administrator.
|
||||
|
||||
dmsf_copy: "Copy (%{n})"
|
||||
|
||||
easy_pages:
|
||||
modules:
|
||||
dmsf_locked_documents: My locked documents
|
||||
|
||||
28
db/migrate/20210115120901_add_owner_to_dmsf_lock.rb
Normal file
28
db/migrate/20210115120901_add_owner_to_dmsf_lock.rb
Normal file
@ -0,0 +1,28 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine plugin for Document Management System "Features"
|
||||
#
|
||||
# Copyright © 2011-20 Karel Pičman <karel.picman@kontron.com>
|
||||
# Copyright © 2016-17 carlolars
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class AddOwnerToDmsfLock < ActiveRecord::Migration[5.2]
|
||||
|
||||
def change
|
||||
add_column :dmsf_locks, :owner, :string, null: true
|
||||
end
|
||||
|
||||
end
|
||||
@ -320,12 +320,14 @@ module DAV4Rack
|
||||
asked[:timeout] = timeout.split(',').map{|x|x.strip}
|
||||
end
|
||||
|
||||
Rails.logger.info ">>> #{request.document}"
|
||||
|
||||
ns = request.ns
|
||||
if doc = request.document and lockinfo = doc.xpath("//#{ns}lockinfo")
|
||||
|
||||
asked[:scope] = lockinfo.xpath("//#{ns}lockscope").children.find_all{|n|n.element?}.map{|n|n.name}.first
|
||||
asked[:type] = lockinfo.xpath("#{ns}locktype").children.find_all{|n|n.element?}.map{|n|n.name}.first
|
||||
asked[:owner] = lockinfo.xpath("//#{ns}owner/#{ns}href").children.map{|n|n.text}.first
|
||||
asked[:owner] = lockinfo.xpath("//#{ns}owner").children.map{|n|n.text}.first
|
||||
end
|
||||
|
||||
r = XmlResponse.new(response, resource.namespaces)
|
||||
|
||||
@ -76,10 +76,12 @@ module DAV4Rack
|
||||
|
||||
Ox::Element.new(D_ACTIVELOCK).tap do |activelock|
|
||||
if scope
|
||||
activelock << ox_element(D_LOCKSCOPE, scope)
|
||||
scope = Ox::Element.new("#{DAV_NAMESPACE_NAME}:#{scope}")
|
||||
activelock << ox_element(D_LOCKSCOPE, scope)
|
||||
end
|
||||
if type
|
||||
activelock << ox_element(D_LOCKTYPE, type)
|
||||
type = Ox::Element.new("#{DAV_NAMESPACE_NAME}:#{type}")
|
||||
activelock << ox_element(D_LOCKTYPE)
|
||||
end
|
||||
activelock << ox_element(D_DEPTH, depth)
|
||||
activelock << ox_element(D_TIMEOUT,
|
||||
|
||||
@ -44,7 +44,7 @@ module RedmineDmsf
|
||||
ret
|
||||
end
|
||||
|
||||
def lock!(scope = :scope_exclusive, type = :type_write, expire = nil)
|
||||
def lock!(scope = :scope_exclusive, type = :type_write, expire = nil, owner = nil)
|
||||
# Raise a lock error if entity is locked, but its not at resource level
|
||||
existing = lock(false)
|
||||
raise DmsfLockError.new(l(:error_resource_or_parent_locked)) if self.locked? && existing.empty?
|
||||
@ -56,7 +56,10 @@ module RedmineDmsf
|
||||
raise DmsfLockError.new(l(:error_parent_locked))
|
||||
else
|
||||
existing.each do |l|
|
||||
raise DmsfLockError.new(l(:error_resource_locked)) if l.user.id == User.current.id
|
||||
#if l.user.id == User.current.id
|
||||
if (l.user.id == User.current.id) && (owner.nil? || (owner == l.owner))
|
||||
raise DmsfLockError.new(l(:error_resource_locked))
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
@ -68,13 +71,17 @@ module RedmineDmsf
|
||||
l.entity_type = self.is_a?(DmsfFile) ? 0 : 1
|
||||
l.lock_type = type
|
||||
l.lock_scope = scope
|
||||
###
|
||||
#Rails.logger.info ">>> #{scope}"
|
||||
###
|
||||
l.user = User.current
|
||||
l.expires_at = expire
|
||||
l.dmsf_file_last_revision_id = self.last_revision.id if self.is_a?(DmsfFile)
|
||||
l.owner = owner
|
||||
l.save!
|
||||
reload
|
||||
locks.reload
|
||||
l.reload
|
||||
# reload
|
||||
# locks.reload
|
||||
# l.reload
|
||||
l
|
||||
end
|
||||
|
||||
@ -82,12 +89,12 @@ module RedmineDmsf
|
||||
return false unless self.locked?
|
||||
existing = self.lock(true)
|
||||
# If its empty its a folder that's locked (not root)
|
||||
(existing.empty? || (!self.dmsf_folder.nil? && self.dmsf_folder.locked?)) ? false : true
|
||||
(existing.empty? || (self.dmsf_folder&.locked?)) ? false : true
|
||||
end
|
||||
|
||||
#
|
||||
# By using the path upwards, surely this would be quicker?
|
||||
def locked_for_user?
|
||||
def locked_for_user?(args = nil)
|
||||
return false unless locked?
|
||||
b_shared = nil
|
||||
self.dmsf_path.each do |entity|
|
||||
@ -95,19 +102,34 @@ module RedmineDmsf
|
||||
next if locks.empty?
|
||||
locks.each do |lock|
|
||||
next if lock.expired? # In case we're in between updates
|
||||
if lock.lock_scope == :scope_exclusive && b_shared.nil?
|
||||
return true if (!lock.user) || (lock.user.id != User.current.id)
|
||||
|
||||
# if lock.owner.present? && (lock.user.to_s != lock.owner)
|
||||
# Rails.logger.info ">>> #{lock.user} X #{User.current} X #{lock.owner}"
|
||||
# end
|
||||
|
||||
if lock.lock_scope == :scope_exclusive #&& b_shared.nil?
|
||||
#return true if (lock.user&.id != User.current.id) || (lock.owner != (args ? args[:owner] : nil))
|
||||
#if args && (args[:method] == 'put') && args[:owner].blank?
|
||||
# return true if (lock.user&.id != User.current.id) #|| ((lock.owner != (args ? args[:owner] : nil)))
|
||||
#else
|
||||
return true if (lock.user&.id != User.current.id) || ((lock.owner != (args ? args[:owner] : nil)))
|
||||
#end
|
||||
else
|
||||
b_shared = true if b_shared.nil?
|
||||
b_shared = false if lock.user.id == User.current.id
|
||||
if b_shared && (lock.user&.id == User.current.id) && (lock.owner == (args ? args[:owner] : nil)) ||
|
||||
(args && (args[:scope] == 'shared'))
|
||||
b_shared = false
|
||||
end
|
||||
end
|
||||
end
|
||||
Rails.logger.info ">>> #{b_shared}"
|
||||
return true if b_shared
|
||||
end
|
||||
Rails.logger.info ">>> false"
|
||||
false
|
||||
end
|
||||
|
||||
def unlock!(force_file_unlock_allowed = false)
|
||||
def unlock!(force_file_unlock_allowed = false, owner = nil)
|
||||
raise DmsfLockError.new(l(:warning_file_not_locked)) unless self.locked?
|
||||
existing = self.lock(true)
|
||||
if existing.empty? || (!self.dmsf_folder.nil? && self.dmsf_folder.locked?) # If its empty its a folder that's locked (not root)
|
||||
@ -124,7 +146,7 @@ module RedmineDmsf
|
||||
else
|
||||
b_destroyed = false
|
||||
existing.each do |lock|
|
||||
if (lock.user && (lock.user.id == User.current.id)) || User.current.admin?
|
||||
if ((lock.user&.id == User.current.id) && (lock.owner == owner)) || User.current.admin?
|
||||
lock.destroy
|
||||
b_destroyed = true
|
||||
break
|
||||
|
||||
@ -118,7 +118,7 @@ module RedmineDmsf
|
||||
new_path = @path
|
||||
new_path = new_path + '/' unless new_path[-1,1] == '/'
|
||||
new_path = '/' + new_path unless new_path[0,1] == '/'
|
||||
@__proxy.class.new "#{new_path}#{name}", request, response, @options.merge(user: @user)
|
||||
ResourceProxy.new "#{new_path}#{name}", request, response, @options.merge(user: @user)
|
||||
end
|
||||
|
||||
def child_project(p)
|
||||
@ -127,7 +127,7 @@ module RedmineDmsf
|
||||
new_path = new_path + '/' unless new_path[-1,1] == '/'
|
||||
new_path = '/' + new_path unless new_path[0,1] == '/'
|
||||
new_path += project_display_name
|
||||
@__proxy.class.new new_path, request, response, @options.merge(user: @user, project: true)
|
||||
ResourceProxy.new new_path, request, response, @options.merge(user: @user, project: true)
|
||||
end
|
||||
|
||||
def parent
|
||||
@ -240,6 +240,9 @@ module RedmineDmsf
|
||||
else
|
||||
@file = DmsfFile.find_file_by_name(@project, @folder, pinfo.first)
|
||||
@folder = nil
|
||||
unless (pinfo.length < 2 || @subproject || @folder || @file)
|
||||
raise Conflict
|
||||
end
|
||||
break # We're at the end
|
||||
end
|
||||
end
|
||||
|
||||
@ -38,8 +38,20 @@ module RedmineDmsf
|
||||
allow_unauthenticated_options_on_root: true,
|
||||
namespaces: {
|
||||
'http://apache.org/dav/props/' => 'd',
|
||||
'http://ucb.openoffice.org/dav/props/' => 'd',
|
||||
'SAR:' => 'd'
|
||||
'http://ucb.openoffice.org/dav/props/' => 'd', # LibreOffice
|
||||
'SAR:' => 'd', # Cyberduck
|
||||
'http://webdav.org/neon/litmus/' => 'd', # Litmus
|
||||
'http://example.com/neon/litmus/' => 'ns1',
|
||||
'http://example.com/alpha' => 'ns2',
|
||||
'http://example.com/beta' => 'ns3',
|
||||
'http://example.com/gamma' => 'ns4',
|
||||
'http://example.com/delta' => 'ns5',
|
||||
'http://example.com/epsilon' => 'ns6',
|
||||
'http://example.com/zeta' => 'ns7',
|
||||
'http://example.com/eta' => 'ns8',
|
||||
'http://example.com/theta' => 'ns9',
|
||||
'http://example.com/iota' => 'ns10',
|
||||
'http://example.com/kappa' => 'ns11'
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
@ -28,6 +28,38 @@ module RedmineDmsf
|
||||
class DmsfResource < BaseResource
|
||||
include Redmine::I18n
|
||||
|
||||
def initialize(path, request, response, options)
|
||||
super path, request, response, options
|
||||
end
|
||||
|
||||
# name:: String - Property name
|
||||
# Returns the value of the given property
|
||||
def get_property(element)
|
||||
if element[:ns_href] == DAV_NAMESPACE
|
||||
super
|
||||
else
|
||||
get_custom_property element
|
||||
end
|
||||
end
|
||||
|
||||
# name:: String - Property name
|
||||
# value:: New value
|
||||
# Set the property to the given value
|
||||
def set_property(element, value)
|
||||
# let Resource handle DAV properties
|
||||
if element[:ns_href] == DAV_NAMESPACE
|
||||
super
|
||||
else
|
||||
set_custom_property element, value
|
||||
end
|
||||
end
|
||||
|
||||
# name:: Property name
|
||||
# Remove the property from the resource
|
||||
def remove_property(element)
|
||||
Redmine::Search.cache_store.delete "#{get_property_key}-#{element[:name]}"
|
||||
end
|
||||
|
||||
# Gather collection of objects that denote current entities child entities
|
||||
# Used for listing directories etc, implemented basic caching because otherwise
|
||||
# Our already quite heavy usage of DB would just get silly every time we called
|
||||
@ -112,7 +144,6 @@ module RedmineDmsf
|
||||
end
|
||||
|
||||
# Process incoming GET request
|
||||
#
|
||||
# If instance is a collection, calls html_display (defined in base_resource.rb) which cycles through children for display
|
||||
# File will only be presented for download if user has permission to view files
|
||||
def get(request, response)
|
||||
@ -129,15 +160,13 @@ module RedmineDmsf
|
||||
end
|
||||
|
||||
# Process incoming MKCOL request
|
||||
#
|
||||
# Create a DmsfFolder at location requested, only if parent is a folder (or root)
|
||||
# - 2012-06-18: Ensure item is only functional if project is enabled for dmsf
|
||||
# Ensure item is only functional if project is enabled for dmsf
|
||||
def make_collection
|
||||
if request.body.read.to_s.empty?
|
||||
raise NotFound unless project && project.module_enabled?('dmsf')
|
||||
raise Forbidden unless User.current.admin? || User.current.allowed_to?(:folder_manipulation, project)
|
||||
raise Forbidden unless (!parent.exist? || !parent.folder || DmsfFolder.permissions?(parent.folder, false))
|
||||
return MethodNotAllowed if exist? # If we already exist, why waste the time trying to save?
|
||||
f = DmsfFolder.new
|
||||
f.title = basename
|
||||
f.dmsf_folder_id = parent.folder&.id
|
||||
@ -150,13 +179,13 @@ module RedmineDmsf
|
||||
end
|
||||
|
||||
# Process incoming DELETE request
|
||||
#
|
||||
# <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
|
||||
raise Forbidden unless User.current.admin? || User.current.allowed_to?(:file_delete, project)
|
||||
raise Forbidden unless (!parent.exist? || !parent.folder || DmsfFolder.permissions?(parent.folder, false))
|
||||
raise Locked if file.locked?
|
||||
pattern = Setting.plugin_redmine_dmsf['dmsf_webdav_disable_versioning']
|
||||
if pattern.present? && basename.match(pattern)
|
||||
# Files that are not versioned should be destroyed
|
||||
@ -174,6 +203,7 @@ module RedmineDmsf
|
||||
Conflict
|
||||
end
|
||||
elsif folder
|
||||
raise Locked if folder.locked?
|
||||
# To fullfil Litmus requirements to not delete folder if fragments are in the URL
|
||||
uri = URI(uri_encode(request.get_header('REQUEST_URI')))
|
||||
raise BadRequest if uri.fragment.present?
|
||||
@ -186,124 +216,116 @@ module RedmineDmsf
|
||||
end
|
||||
|
||||
# Process incoming MOVE request
|
||||
#
|
||||
# Behavioural differences between collection and single entity
|
||||
# TODO: Support overwrite between both types of entity, and implement better checking
|
||||
def move(dest, overwrite)
|
||||
dest = @__proxy.class.new(dest, @request, @response, @options.merge(user: @user))
|
||||
# All of this should carry across the ResourceProxy frontend, we ensure this to
|
||||
# prevent unexpected errors
|
||||
resource = dest.is_a?(ResourceProxy) ? dest.resource : dest
|
||||
return PreconditionFailed if !resource.is_a?(DmsfResource) || resource.project.nil?
|
||||
parent = resource.parent
|
||||
raise Forbidden unless resource.project.module_enabled?(:dmsf)
|
||||
def move(dest_path, overwrite)
|
||||
dest = ResourceProxy.new(dest_path, @request, @response, @options.merge(user: @user))
|
||||
return PreconditionFailed if !dest.resource.is_a?(DmsfResource) || dest.resource.project.nil?
|
||||
parent = dest.resource.parent
|
||||
raise Forbidden unless dest.resource.project.module_enabled?(:dmsf)
|
||||
if !parent.exist? || (!User.current.admin? && (!DmsfFolder.permissions?(folder, false) ||
|
||||
!DmsfFolder.permissions?(parent.folder, false)))
|
||||
raise Forbidden
|
||||
end
|
||||
if collection?
|
||||
if dest.exist?
|
||||
return overwrite ? NotImplemented : PreconditionFailed
|
||||
if overwrite
|
||||
if folder
|
||||
(dest.collection?)&.delete true
|
||||
end
|
||||
else
|
||||
return PreconditionFailed
|
||||
end
|
||||
end
|
||||
if !User.current.admin? && (!User.current.allowed_to?(:folder_manipulation, project) ||
|
||||
!User.current.allowed_to?(:folder_manipulation, resource.project))
|
||||
!User.current.allowed_to?(:folder_manipulation, dest.resource.project))
|
||||
raise Forbidden
|
||||
end
|
||||
unless folder
|
||||
return MethodNotAllowed # Moving sub-project not enabled
|
||||
end
|
||||
raise Locked if folder.locked_for_user?
|
||||
# Change the title
|
||||
return MethodNotAllowed unless folder # Moving sub-project not enabled
|
||||
folder.title = resource.basename
|
||||
folder.title = dest.resource.basename
|
||||
return PreconditionFailed unless folder.save
|
||||
# Move to a new destination
|
||||
folder.move_to(resource.project, parent.folder) ? Created : PreconditionFailed
|
||||
folder.move_to(dest.resource.project, parent.folder) ? Created : PreconditionFailed
|
||||
else
|
||||
if !User.current.admin? && (!User.current.allowed_to?(:file_manipulation, project) ||
|
||||
!User.current.allowed_to?(:file_manipulation, resource.project))
|
||||
!User.current.allowed_to?(:file_manipulation, dest.resource.project))
|
||||
raise Forbidden
|
||||
end
|
||||
if dest.exist?
|
||||
raise Locked if file.locked_for_user?
|
||||
if dest.exist? && (!dest.collection?)
|
||||
return PreconditionFailed unless overwrite
|
||||
if (project == resource.project) && file.name.match(/.\.tmp$/i)
|
||||
# Renaming a *.tmp file to an existing file in the same project, probably Office that is saving a file.
|
||||
Rails.logger.info "WebDAV MOVE: #{file.name} -> #{resource.basename} (exists), possible MSOffice rename from .tmp when saving"
|
||||
if resource.file.last_revision.size == 0 || reuse_version_for_locked_file(resource.file)
|
||||
# Last revision in the destination has zero size so reuse that revision
|
||||
new_revision = resource.file.last_revision
|
||||
else
|
||||
# Create a new revison by cloning the last revision in the destination
|
||||
new_revision = resource.file.last_revision.clone
|
||||
new_revision.increase_version 1
|
||||
end
|
||||
# The file on disk must be renamed from .tmp to the correct filetype or else Xapian won't know how to index.
|
||||
# Copy file.last_revision.disk_file to new_revision.disk_file
|
||||
new_revision.size = file.last_revision.size
|
||||
new_revision.disk_filename = new_revision.new_storage_filename
|
||||
Rails.logger.info "WebDAV MOVE: Copy file #{file.last_revision.disk_filename} -> #{new_revision.disk_filename}"
|
||||
File.open(file.last_revision.disk_file, 'rb') do |f|
|
||||
new_revision.copy_file_content f
|
||||
end
|
||||
# Save
|
||||
new_revision.save && resource.file.save
|
||||
# Delete (and destroy) the file that should have been renamed and return what should have been returned in case of a copy
|
||||
file.delete(true) ? Created : PreconditionFailed
|
||||
if dest.resource.file.last_revision.size == 0 || reuse_version_for_locked_file(dest.resource.file)
|
||||
# Last revision in the destination has zero size so reuse that revision
|
||||
new_revision = dest.resource.file.last_revision
|
||||
else
|
||||
# Files cannot be merged at this point, until a decision is made on how to merge them
|
||||
# ideally, we would merge revision history for both, ensuring the origin file wins with latest revision.
|
||||
NotImplemented
|
||||
# Create a new revison by cloning the last revision in the destination
|
||||
new_revision = dest.resource.file.last_revision.clone
|
||||
new_revision.increase_version 1
|
||||
end
|
||||
# The file on disk must be renamed from .tmp to the correct filetype or else Xapian won't know how to index.
|
||||
# Copy file.last_revision.disk_file to new_revision.disk_file
|
||||
new_revision.size = file.last_revision.size
|
||||
new_revision.disk_filename = new_revision.new_storage_filename
|
||||
File.open(file.last_revision.disk_file, 'rb') do |f|
|
||||
new_revision.copy_file_content f
|
||||
end
|
||||
# Save
|
||||
new_revision.save && dest.resource.file.save
|
||||
# Delete (and destroy) the file that should have been renamed and return what should have been returned in case of a copy
|
||||
file.delete(true) ? Created : PreconditionFailed
|
||||
else
|
||||
return PreconditionFailed unless exist? && file
|
||||
if (project == resource.project) && resource.basename.match(/.\.tmp$/i)
|
||||
Rails.logger.info "WebDAV MOVE: #{file.name} -> #{resource.basename}, possible MSOffice rename to .tmp when saving."
|
||||
if (project == dest.resource.project) && dest.resource.basename.match(/.\.tmp$/i)
|
||||
Rails.logger.info "WebDAV MOVE: #{file.name} -> #{dest.resource.basename}, possible MSOffice rename to .tmp when saving."
|
||||
# Renaming the file to X.tmp, might be Office that is saving a file. Keep the original file.
|
||||
file.copy_to_filename resource.project, parent&.folder, resource.basename
|
||||
file.copy_to_filename dest.resource.project, parent&.folder, dest.resource.basename
|
||||
Created
|
||||
else
|
||||
if (project == resource.project) && (file.last_revision.size == 0)
|
||||
if (project == dest.resource.project) && (file.last_revision.size == 0)
|
||||
# Moving a zero sized file within the same project, just update the dmsf_folder
|
||||
file.dmsf_folder = parent&.folder
|
||||
else
|
||||
return InternalServerError unless file.move_to(resource.project, parent&.folder)
|
||||
return InternalServerError unless file.move_to(dest.resource.project, parent&.folder)
|
||||
end
|
||||
# Update Revision and names of file [We can link to old physical resource, as it's not changed]
|
||||
if file.last_revision
|
||||
file.last_revision.name = resource.basename
|
||||
file.last_revision.title = DmsfFileRevision.filename_to_title(resource.basename)
|
||||
file.last_revision.name = dest.resource.basename
|
||||
file.last_revision.title = DmsfFileRevision.filename_to_title(dest.resource.basename)
|
||||
end
|
||||
file.name = resource.basename
|
||||
file.name = dest.resource.basename
|
||||
# Save Changes
|
||||
(file.last_revision.save && file.save) ? Created : PreconditionFailed
|
||||
if file.last_revision.save && file.save
|
||||
dest.exist? ? NoContent : Created
|
||||
else
|
||||
PreconditionFailed
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Process incoming COPY request
|
||||
#
|
||||
# Behavioural differences between collection and single entity
|
||||
# TODO: Support overwrite between both types of entity, and an integrative copy where destination exists for collections
|
||||
def copy(dest, overwrite, depth)
|
||||
dest = @__proxy.class.new(dest, @request, @response, @options.merge(user: @user))
|
||||
# All of this should carry across the ResourceProxy frontend, we ensure this to
|
||||
# prevent unexpected errors
|
||||
if dest.is_a?(ResourceProxy)
|
||||
resource = dest.resource
|
||||
else
|
||||
resource = dest
|
||||
end
|
||||
|
||||
return PreconditionFailed if !resource.is_a?(DmsfResource) || resource.project.nil?
|
||||
|
||||
parent = resource.parent
|
||||
dest = ResourceProxy.new(dest, @request, @response, @options.merge(user: @user))
|
||||
return PreconditionFailed unless dest.resource.project
|
||||
parent = dest.resource.parent
|
||||
raise Forbidden unless (!parent.exist? || !parent.folder || DmsfFolder.permissions?(parent.folder, false))
|
||||
|
||||
if dest.exist?
|
||||
return overwrite ? NotImplemented : PreconditionFailed
|
||||
end
|
||||
|
||||
return Conflict unless dest.parent.exist?
|
||||
|
||||
res = Created
|
||||
if dest.exist?
|
||||
return Locked if dest.lockdiscovery.present?
|
||||
if overwrite
|
||||
dest.delete
|
||||
res = NoContent
|
||||
else
|
||||
return PreconditionFailed
|
||||
end
|
||||
end
|
||||
return PreconditionFailed unless parent.exist? && parent.folder
|
||||
|
||||
if collection?
|
||||
# Permission check if they can manipulate folders and view folders
|
||||
# Can they:
|
||||
@ -312,14 +334,13 @@ module RedmineDmsf
|
||||
# View files on the source project :view_dmsf_files
|
||||
# View fodlers on the source project :view_dmsf_folders
|
||||
raise Forbidden unless User.current.admin? ||
|
||||
(User.current.allowed_to?(:folder_manipulation, resource.project) &&
|
||||
User.current.allowed_to?(:view_dmsf_folders, resource.project) &&
|
||||
(User.current.allowed_to?(:folder_manipulation, dest.resource.project) &&
|
||||
User.current.allowed_to?(:view_dmsf_folders, dest.resource.project) &&
|
||||
User.current.allowed_to?(:view_dmsf_files, project) &&
|
||||
User.current.allowed_to?(:view_dmsf_folders, project))
|
||||
raise Forbidden unless DmsfFolder.permissions?(folder, false)
|
||||
|
||||
folder.title = resource.basename
|
||||
new_folder = folder.copy_to(resource.project, parent.folder)
|
||||
folder.title = dest.resource.basename
|
||||
new_folder = folder.copy_to(dest.resource.project, parent.folder)
|
||||
return PreconditionFailed if new_folder.nil? || new_folder.id.nil?
|
||||
Created
|
||||
else
|
||||
@ -329,32 +350,67 @@ module RedmineDmsf
|
||||
# View files on destination project :view_dmsf_files
|
||||
# View files on the source project :view_dmsf_files
|
||||
raise Forbidden unless User.current.admin? ||
|
||||
(User.current.allowed_to?(:file_manipulation, resource.project) &&
|
||||
User.current.allowed_to?(:view_dmsf_files, resource.project) &&
|
||||
(User.current.allowed_to?(:file_manipulation, dest.resource.project) &&
|
||||
User.current.allowed_to?(:view_dmsf_files, dest.resource.project) &&
|
||||
User.current.allowed_to?(:view_dmsf_files, project))
|
||||
|
||||
return PreconditionFailed unless exist? && file
|
||||
new_file = file.copy_to(resource.project, parent&.folder)
|
||||
new_file = file.copy_to(dest.resource.project, parent&.folder)
|
||||
return InternalServerError unless (new_file && new_file.last_revision)
|
||||
|
||||
# Update Revision and names of file [We can link to old physical resource, as it's not changed]
|
||||
new_file.last_revision.name = resource.basename
|
||||
new_file.name = resource.basename
|
||||
|
||||
# Update Revision and names of file (We can link to old physical resource, as it's not changed)
|
||||
new_file.last_revision.name = dest.resource.basename
|
||||
new_file.name = dest.resource.basename
|
||||
# Save Changes
|
||||
(new_file.last_revision.save && new_file.save) ? Created : PreconditionFailed
|
||||
(new_file.last_revision.save && new_file.save) ? res : PreconditionFailed
|
||||
end
|
||||
end
|
||||
|
||||
# Lock Check
|
||||
# Check for the existence of locks
|
||||
# At present as deletions of folders are not recursive, we do not need to extend
|
||||
# this to cover every file, just queried
|
||||
def lock_check(lock_scope = nil)
|
||||
if file
|
||||
raise Locked if file.locked_for_user?
|
||||
elsif folder
|
||||
raise Locked if folder.locked_for_user?
|
||||
def lock_check(args = {})
|
||||
entity = file || folder
|
||||
if entity
|
||||
refresh = args && (!args[:scope]) && (!args[:type])
|
||||
args ||= {}
|
||||
args[:method] = @request.request_method.downcase
|
||||
http_if = request.get_header('HTTP_IF')
|
||||
if http_if.present?
|
||||
no_lock = http_if.include?('<DAV:no-lock>')
|
||||
not_no_lock = http_if.include?('Not <DAV:no-lock>')
|
||||
# Invalid lock token
|
||||
if /\(<([a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+)>/.match(http_if)
|
||||
raise PreconditionFailed unless entity.locked?
|
||||
if $1 != entity.lock.first.uuid
|
||||
raise Locked if entity.locked_for_user?(args)
|
||||
end
|
||||
else
|
||||
if ((!no_lock || not_no_lock) && entity.locked_for_user?(args))
|
||||
raise Locked
|
||||
else
|
||||
raise PreconditionFailed
|
||||
end
|
||||
end
|
||||
# Invalid etag
|
||||
if /^\(<([a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+)> \[([a-f0-9]+-[a-f0-9]+-[a-f0-9]+)\]/.match(http_if)
|
||||
if $2 != etag
|
||||
raise PreconditionFailed
|
||||
else
|
||||
return # lock & etag
|
||||
end
|
||||
end
|
||||
# no-lock
|
||||
return if no_lock
|
||||
end
|
||||
if entity.locked_for_user?(args) && !refresh
|
||||
if http_if.present?
|
||||
case args[:method]
|
||||
when 'put'
|
||||
return
|
||||
when 'proppatch'
|
||||
return
|
||||
end
|
||||
end
|
||||
raise Locked
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -366,11 +422,9 @@ module RedmineDmsf
|
||||
raise e
|
||||
end
|
||||
unless exist?
|
||||
e = DAV4Rack::LockFailure.new
|
||||
e.add_failure @path, NotFound
|
||||
raise e
|
||||
return super(args)
|
||||
end
|
||||
lock_check args[:scope]
|
||||
lock_check args
|
||||
entity = file || folder
|
||||
unless entity
|
||||
e = DAV4Rack::LockFailure.new
|
||||
@ -378,43 +432,38 @@ module RedmineDmsf
|
||||
raise e
|
||||
end
|
||||
begin
|
||||
if entity.locked? && entity.locked_for_user?
|
||||
raise DAV4Rack::LockFailure.new("Failed to lock: #{@path}")
|
||||
else
|
||||
# If scope and type are not defined, the only thing we can
|
||||
# logically assume is that the lock is being refreshed (office loves
|
||||
# to do this for example, so we do a few checks, try to find the lock
|
||||
# and ultimately extend it, otherwise we return Conflict for any failure
|
||||
if (!args[:scope]) && (!args[:type]) # Perhaps a lock refresh
|
||||
http_if = request.env['HTTP_IF']
|
||||
if http_if.blank?
|
||||
e = DAV4Rack::LockFailure.new
|
||||
e.add_failure @path, Conflict
|
||||
raise e
|
||||
end
|
||||
l = nil
|
||||
if http_if =~ /([a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12})/
|
||||
l = DmsfLock.find_by(uuid: $1)
|
||||
end
|
||||
unless l
|
||||
e = DAV4Rack::LockFailure.new
|
||||
e.add_failure @path, Conflict
|
||||
raise e
|
||||
end
|
||||
l.expires_at = Time.current + 1.week
|
||||
l.save!
|
||||
@response['Lock-Token'] = l.uuid
|
||||
return [1.weeks.to_i, l.uuid]
|
||||
# If scope and type are not defined, the only thing we can
|
||||
# logically assume is that the lock is being refreshed (office loves
|
||||
# to do this for example, so we do a few checks, try to find the lock
|
||||
# and ultimately extend it, otherwise we return Conflict for any failure
|
||||
refresh = args && (!args[:scope]) && (!args[:type]) # Perhaps a lock refresh
|
||||
if refresh
|
||||
http_if = request.get_header('HTTP_IF')
|
||||
if http_if.blank?
|
||||
e = DAV4Rack::LockFailure.new
|
||||
e.add_failure @path, Conflict
|
||||
raise e
|
||||
end
|
||||
|
||||
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.current + 1.weeks)
|
||||
l = nil
|
||||
if /([a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12})/.match(http_if)
|
||||
l = DmsfLock.find_by(uuid: $1)
|
||||
end
|
||||
unless l
|
||||
e = DAV4Rack::LockFailure.new
|
||||
e.add_failure @path, Conflict
|
||||
raise e
|
||||
end
|
||||
l.expires_at = Time.current + 1.week
|
||||
l.save!
|
||||
@response['Lock-Token'] = l.uuid
|
||||
[1.week.to_i, l.uuid]
|
||||
return [1.weeks.to_i, l.uuid]
|
||||
end
|
||||
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.current + 1.weeks, args[:owner])
|
||||
@response['Lock-Token'] = l.uuid
|
||||
[1.week.to_i, l.uuid]
|
||||
rescue DmsfLockError
|
||||
e = DAV4Rack::LockFailure.new
|
||||
e.add_failure @path, Conflict
|
||||
@ -426,7 +475,9 @@ module RedmineDmsf
|
||||
# Token based unlock (authenticated) will ensure that a correct token is sent, further ensuring
|
||||
# ownership of token before permitting unlock
|
||||
def unlock(token)
|
||||
return NotFound unless exist?
|
||||
unless exist?
|
||||
return super(token)
|
||||
end
|
||||
if token.nil? || token.empty? || (token == '<(null)>') || User.current.anonymous?
|
||||
BadRequest
|
||||
else
|
||||
@ -436,14 +487,13 @@ module RedmineDmsf
|
||||
return BadRequest
|
||||
end
|
||||
begin
|
||||
entity = file || folder
|
||||
l = DmsfLock.find(token)
|
||||
return NoContent unless l
|
||||
# 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
|
||||
entity = file || folder
|
||||
return NoContent unless entity&.locked?
|
||||
l_entity = l.file || l.folder
|
||||
if entity.locked_for_user? || (l_entity != entity)
|
||||
if l_entity != entity
|
||||
Forbidden
|
||||
else
|
||||
entity.unlock!
|
||||
@ -456,7 +506,6 @@ module RedmineDmsf
|
||||
end
|
||||
|
||||
# HTTP POST request.
|
||||
#
|
||||
# Forbidden, as method should not be utilized.
|
||||
def post(request, response)
|
||||
raise Forbidden
|
||||
@ -467,7 +516,6 @@ module RedmineDmsf
|
||||
raise BadRequest if collection?
|
||||
raise Forbidden unless User.current.admin? || User.current.allowed_to?(:file_manipulation, project)
|
||||
raise Forbidden unless (!parent.exist? || !parent.folder || DmsfFolder.permissions?(parent.folder, false))
|
||||
|
||||
# Ignore file name patterns given in the plugin settings
|
||||
pattern = Setting.plugin_redmine_dmsf['dmsf_webdav_ignore']
|
||||
pattern = /^(\._|\.DS_Store$|Thumbs.db$)/ if pattern.blank?
|
||||
@ -475,23 +523,18 @@ module RedmineDmsf
|
||||
Rails.logger.info "#{basename} ignored"
|
||||
return NoContent
|
||||
end
|
||||
|
||||
reuse_revision = false
|
||||
|
||||
if exist? # We're over-writing something, so ultimately a new revision
|
||||
f = file
|
||||
|
||||
# Disable versioning for file name patterns given in the plugin settings.
|
||||
pattern = Setting.plugin_redmine_dmsf['dmsf_webdav_disable_versioning']
|
||||
if pattern.present? && basename.match(pattern)
|
||||
Rails.logger.info "Versioning disabled for #{basename}"
|
||||
reuse_revision = true
|
||||
end
|
||||
|
||||
if reuse_version_for_locked_file(file)
|
||||
reuse_revision = true
|
||||
end
|
||||
|
||||
last_revision = file.last_revision
|
||||
if last_revision.size == 0 || reuse_revision
|
||||
new_revision = last_revision
|
||||
@ -504,10 +547,8 @@ module RedmineDmsf
|
||||
new_revision = DmsfFileRevision.new
|
||||
end
|
||||
# Custom fields
|
||||
i = 0
|
||||
last_revision.custom_field_values.each do |custom_value|
|
||||
last_revision.custom_field_values.each_with_index do |custom_value, i|
|
||||
new_revision.custom_field_values[i].value = custom_value
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
else
|
||||
@ -586,10 +627,10 @@ module RedmineDmsf
|
||||
def lockdiscovery
|
||||
entity = file || folder
|
||||
return [] unless entity&.locked?
|
||||
if entity.dmsf_folder && entity.dmsf_folder.locked?
|
||||
entity.lock.reverse[0].folder.locks(false) # longwinded way of getting base items locks
|
||||
if entity.dmsf_folder&.locked?
|
||||
entity.lock.reverse[0].folder.locks # longwinded way of getting base items locks
|
||||
else
|
||||
entity.lock(false)
|
||||
entity.lock false
|
||||
end
|
||||
end
|
||||
|
||||
@ -632,6 +673,7 @@ module RedmineDmsf
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Prepare file for download using Rack functionality:
|
||||
# Download (see RedmineDmsf::Webdav::Download) extends Rack::File to allow single-file
|
||||
# implementation of service for request, which allows for us to pipe a single file through
|
||||
@ -653,21 +695,45 @@ module RedmineDmsf
|
||||
File.new disk_file
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def reuse_version_for_locked_file(file)
|
||||
locks = file.lock
|
||||
locks.each do |lock|
|
||||
next if lock.expired?
|
||||
# lock should be exclusive but just in case make sure we find this users lock
|
||||
next if lock.user != User.current
|
||||
if lock.dmsf_file_last_revision_id < file.last_revision.id
|
||||
if lock.dmsf_file_last_revision_id.nil? || (lock.dmsf_file_last_revision_id < file.last_revision.id)
|
||||
# At least one new revision has been created since the lock was created, reuse that revision.
|
||||
return true
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
def set_custom_property(element, value)
|
||||
if value.present?
|
||||
Redmine::Search.cache_store.write "#{get_property_key}-#{element[:name]}", value
|
||||
else
|
||||
Redmine::Search.cache_store.delete "#{get_property_key}-#{element[:name]}"
|
||||
end
|
||||
OK
|
||||
end
|
||||
|
||||
def get_custom_property(element)
|
||||
val = Redmine::Search.cache_store.fetch "#{get_property_key}-#{element[:name]}"
|
||||
val.present? ? val : NotFound
|
||||
end
|
||||
|
||||
def get_property_key
|
||||
if file
|
||||
return "DmsfFile-#{file.id}"
|
||||
elsif folder
|
||||
return "DmsfFolder-#{folder.id}"
|
||||
elsif subproject
|
||||
return "Project-#{subproject.id}"
|
||||
else
|
||||
return "Project-#{project.id}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@ -162,6 +162,10 @@ module RedmineDmsf
|
||||
@resource_c.get_property element
|
||||
end
|
||||
|
||||
def remove_property(element)
|
||||
@resource_c.remove_property element
|
||||
end
|
||||
|
||||
def properties
|
||||
@resource_c.properties
|
||||
end
|
||||
@ -170,6 +174,10 @@ module RedmineDmsf
|
||||
@resource_c.propstats response, stats
|
||||
end
|
||||
|
||||
def set_property(element, value)
|
||||
@resource_c.set_property element, value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_resource_class(path)
|
||||
|
||||
@ -59,7 +59,7 @@ class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest
|
||||
|
||||
def test_not_existed_project
|
||||
delete '/dmsf/webdav/not_a_project/file.txt', params: nil, headers: @admin
|
||||
assert_response :not_found
|
||||
assert_response :conflict
|
||||
end
|
||||
|
||||
def test_dmsf_not_enabled
|
||||
@ -130,7 +130,7 @@ class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest
|
||||
def test_folder_delete_by_user_with_project_names
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] = true
|
||||
delete "/dmsf/webdav/#{@project1.identifier}/#{@folder6.title}", params: nil, headers: @jsmith
|
||||
assert_response :not_found
|
||||
assert_response :conflict
|
||||
p1name_uri = ERB::Util.url_encode(RedmineDmsf::Webdav::ProjectResource.create_project_name(@project1))
|
||||
delete "/dmsf/webdav/#{p1name_uri}/#{@folder6.title}", params: nil, headers: @jsmith
|
||||
assert_response :success
|
||||
@ -155,7 +155,7 @@ class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest
|
||||
def test_file_delete_by_user_with_project_names
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] = '1'
|
||||
delete "/dmsf/webdav/#{@project1.identifier}/#{@file1.name}", params: nil, headers: @jsmith
|
||||
assert_response :not_found
|
||||
assert_response :conflict
|
||||
p1name_uri = ERB::Util.url_encode(RedmineDmsf::Webdav::ProjectResource.create_project_name(@project1))
|
||||
delete "/dmsf/webdav/#{p1name_uri}/#{@file1.name}", params: nil, headers: @jsmith
|
||||
assert_response :success
|
||||
|
||||
@ -116,7 +116,7 @@ class DmsfWebdavGetTest < RedmineDmsf::Test::IntegrationTest
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] = true
|
||||
project1_uri = ERB::Util.url_encode(RedmineDmsf::Webdav::ProjectResource.create_project_name(@project1))
|
||||
get "/dmsf/webdav/#{@project1.identifier}/test.txt", params: nil, headers: @admin
|
||||
assert_response :not_found
|
||||
assert_response :conflict
|
||||
get "/dmsf/webdav/#{project1_uri}/test.txt", params: nil, headers: @admin
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
@ -54,7 +54,7 @@ class DmsfWebdavHeadTest < RedmineDmsf::Test::IntegrationTest
|
||||
check_headers_exist
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] = '1'
|
||||
head "/dmsf/webdav/#{@project1.identifier}/test.txt", params: nil, headers: @admin
|
||||
assert_response :not_found
|
||||
assert_response :conflict
|
||||
head "/dmsf/webdav/#{@project1_uri}/test.txt", params: nil, headers: @admin
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
@ -41,8 +41,7 @@ class DmsfWebdavLockTest < RedmineDmsf::Test::IntegrationTest
|
||||
log_user 'admin', 'admin'
|
||||
process :lock, "/dmsf/webdav/#{@file2.project.identifier}/#{@file2.name}", params: @xml,
|
||||
headers: @admin.merge!({ HTTP_DEPTH: 'infinity', HTTP_TIMEOUT: 'Infinite' })
|
||||
assert_response :multi_status
|
||||
assert_match '<d:status>HTTP/1.1 409 Conflict</d:status>', response.body
|
||||
assert_response :locked
|
||||
end
|
||||
|
||||
def test_lock_file
|
||||
@ -50,7 +49,6 @@ class DmsfWebdavLockTest < RedmineDmsf::Test::IntegrationTest
|
||||
create_time = Time.utc(2000, 1, 2, 3, 4, 5)
|
||||
refresh_time = Time.utc(2000, 1, 2, 6, 7, 8)
|
||||
lock_token = nil
|
||||
|
||||
# Time travel, will make the usec part of the time 0
|
||||
travel_to create_time do
|
||||
# Lock file
|
||||
@ -62,8 +60,7 @@ class DmsfWebdavLockTest < RedmineDmsf::Test::IntegrationTest
|
||||
# <d:prop xmlns:d=\"DAV:\">
|
||||
# <d:lockdiscovery>
|
||||
# <d:activelock>
|
||||
# <d:lockscope>exclusive</d:lockscope>
|
||||
# <d:locktype>write</d:locktype>
|
||||
# <d:lockscope><d:exclusive/></d:lockscope>
|
||||
# <d:depth>infinity</d:depth>
|
||||
# <d:timeout>Second-604800</d:timeout>
|
||||
# <d:locktoken>
|
||||
@ -72,8 +69,7 @@ class DmsfWebdavLockTest < RedmineDmsf::Test::IntegrationTest
|
||||
# </d:activelock>
|
||||
# </d:lockdiscovery>
|
||||
# </d:prop>
|
||||
assert_match '<d:lockscope>exclusive</d:lockscope>', response.body
|
||||
assert_match '<d:locktype>write</d:locktype>', response.body
|
||||
assert_match '<d:lockscope><d:exclusive/></d:lockscope>', response.body
|
||||
assert_match '<d:depth>infinity</d:depth>', response.body
|
||||
# 1.week = 7*24*3600=604800 seconds
|
||||
assert_match '<d:timeout>Second-604800</d:timeout>', response.body
|
||||
@ -88,12 +84,14 @@ class DmsfWebdavLockTest < RedmineDmsf::Test::IntegrationTest
|
||||
assert_equal create_time, l.updated_at
|
||||
assert_equal (create_time + 1.week), l.expires_at
|
||||
end
|
||||
|
||||
travel_to refresh_time do
|
||||
# Refresh lock
|
||||
process :lock, "/dmsf/webdav/#{@project1.identifier}/#{@file9.name}",
|
||||
params: nil,
|
||||
headers: @jsmith.merge!({ HTTP_DEPTH: 'infinity', HTTP_TIMEOUT: 'Infinite', HTTP_IF: lock_token })
|
||||
xml = %{<?xml version="1.0" encoding="utf-8" ?>
|
||||
<d:lockinfo xmlns:d="DAV:">
|
||||
<d:owner>jsmith</d:owner>
|
||||
</d:lockinfo>}
|
||||
process :lock, "/dmsf/webdav/#{@file9.project.identifier}/#{@file9.name}", params: xml,
|
||||
headers: @jsmith.merge!({ HTTP_DEPTH: 'infinity', HTTP_TIMEOUT: 'Infinite', HTTP_IF: "(<#{lock_token}>)" })
|
||||
assert_response :success
|
||||
# 1.week = 7*24*3600=604800 seconds
|
||||
assert_match '<d:timeout>Second-604800</d:timeout>', response.body
|
||||
|
||||
@ -38,7 +38,7 @@ class DmsfWebdavMkcolTest < RedmineDmsf::Test::IntegrationTest
|
||||
|
||||
def test_should_not_succeed_on_a_non_existant_project
|
||||
process :mkcol, '/dmsf/webdav/project_doesnt_exist/test1', params: nil, headers: @admin
|
||||
assert_response :not_found
|
||||
assert_response :conflict
|
||||
end
|
||||
|
||||
def test_should_not_succed_on_a_non_dmsf_enabled_project
|
||||
@ -65,7 +65,7 @@ class DmsfWebdavMkcolTest < RedmineDmsf::Test::IntegrationTest
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] = true
|
||||
project1_uri = ERB::Util.url_encode(RedmineDmsf::Webdav::ProjectResource.create_project_name(@project1))
|
||||
process :mkcol, "/dmsf/webdav/#{@project1.identifier}/test2", params: nil, headers: @jsmith
|
||||
assert_response :not_found
|
||||
assert_response :conflict
|
||||
process :mkcol, "/dmsf/webdav/#{project1_uri}/test3", params: nil, headers: @jsmith
|
||||
assert_response :success # Created
|
||||
end
|
||||
|
||||
@ -166,16 +166,11 @@ class DmsfWebdavMoveTest < RedmineDmsf::Test::IntegrationTest
|
||||
end
|
||||
|
||||
def test_move_to_existing_filename
|
||||
file9 = DmsfFile.find_by(id: 9)
|
||||
assert file9
|
||||
new_name = "#{file9.name}"
|
||||
assert_no_difference 'file9.dmsf_file_revisions.count' do
|
||||
assert_no_difference '@file1.dmsf_file_revisions.count' do
|
||||
process :move, "/dmsf/webdav/#{@project1.identifier}/#{@file1.name}", params: nil,
|
||||
headers: @jsmith.merge!({
|
||||
destination: "http://www.example.com/dmsf/webdav/#{@project1.identifier}/#{new_name}"})
|
||||
assert_response :not_implemented
|
||||
end
|
||||
assert_no_difference '@file9.dmsf_file_revisions.count' do
|
||||
process :move, "/dmsf/webdav/#{@project1.identifier}/#{@file1.name}", params: nil,
|
||||
headers: @jsmith.merge!({
|
||||
destination: "http://www.example.com/dmsf/webdav/#{@project1.identifier}/#{@file9.name}"})
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@ class DmsfWebdavUnlockTest < RedmineDmsf::Test::IntegrationTest
|
||||
l = @file2.locks.first
|
||||
process :unlock, "/dmsf/webdav/#{@file2.project.identifier}/#{@file2.name}", params: nil,
|
||||
headers: @jsmith.merge!({ HTTP_DEPTH: 'infinity', HTTP_TIMEOUT: 'Infinite', HTTP_LOCK_TOKEN: l.uuid })
|
||||
assert_response :not_found
|
||||
assert_response :forbidden
|
||||
end
|
||||
|
||||
def test_unlock_file_with_invalid_token
|
||||
@ -67,7 +67,7 @@ class DmsfWebdavUnlockTest < RedmineDmsf::Test::IntegrationTest
|
||||
# folder1 is missing in the path
|
||||
process :unlock, "/dmsf/webdav/#{@folder2.project.identifier}/#{@folder2.title}", params: nil,
|
||||
headers: @jsmith.merge!({ HTTP_DEPTH: 'infinity', HTTP_TIMEOUT: 'Infinite', HTTP_LOCK_TOKEN: l.uuid })
|
||||
assert_response :not_found
|
||||
assert_response :forbidden
|
||||
end
|
||||
|
||||
def test_unlock_folder
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user