From 8f7157ca19faa9640b2b04860e69823c57cb11cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Thu, 16 May 2024 14:54:11 +0200 Subject: [PATCH] #1464 Digest authentication --- app/controllers/dmsf_controller.rb | 29 +++++++- app/views/dmsf/_digest.html.erb | 30 ++++++++ app/views/dmsf/digest.js.erb | 22 ++++++ app/views/my/_sidebar.html.erb | 38 +++++++++++ app/views/settings/_dmsf_settings.html.erb | 14 +++- config/locales/cs.yml | 10 +++ config/locales/de.yml | 10 +++ config/locales/en.yml | 10 +++ config/locales/es.yml | 10 +++ config/locales/fa.yml | 10 +++ config/locales/fr.yml | 10 +++ config/locales/hu.yml | 10 +++ config/locales/it.yml | 10 +++ config/locales/ja.yml | 10 +++ config/locales/ko.yml | 10 +++ config/locales/nl.yml | 10 +++ config/locales/pl.yml | 10 +++ config/locales/pt-BR.yml | 10 +++ config/locales/sl.yml | 10 +++ config/locales/uk.yml | 10 +++ config/locales/zh-TW.yml | 10 +++ config/locales/zh.yml | 10 +++ config/routes.rb | 18 +---- init.rb | 3 +- lib/dav4rack/request.rb | 5 +- lib/dav4rack/resource.rb | 2 +- lib/redmine_dmsf/webdav/custom_middleware.rb | 1 + lib/redmine_dmsf/webdav/dmsf_controller.rb | 72 ++++++++++++++++++++ lib/redmine_dmsf/webdav/resource_proxy.rb | 4 ++ test/functional/dmsf_controller_test.rb | 27 ++++++++ 30 files changed, 411 insertions(+), 24 deletions(-) create mode 100644 app/views/dmsf/_digest.html.erb create mode 100644 app/views/dmsf/digest.js.erb create mode 100644 app/views/my/_sidebar.html.erb diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index ab9df95f..ae76b65a 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -22,11 +22,12 @@ class DmsfController < ApplicationController include RedmineDmsf::DmsfZip - before_action :find_project, except: %i[expand_folder index] - before_action :authorize, except: %i[expand_folder index] + before_action :find_project, except: %i[expand_folder index digest reset_digest] + before_action :authorize, except: %i[expand_folder index digest reset_digest] before_action :authorize_global, only: [:index] before_action :find_folder, - except: %i[new create edit_root save_root add_email append_email autocomplete_for_user] + except: %i[new create edit_root save_root add_email append_email autocomplete_for_user digest + reset_digest] before_action :find_parent, only: %i[new create delete] before_action :permissions # Also try to lookup folder by title if this is an API call @@ -35,6 +36,7 @@ class DmsfController < ApplicationController before_action :project_roles, only: %i[new edit create save] before_action :find_target_folder, only: %i[copymove entries_operation] before_action :check_target_folder, only: [:entries_operation] + before_action :require_login, only: %i[digest reset_digest] accept_api_auth :show, :create, :save, :delete, :entries_operation @@ -465,6 +467,27 @@ class DmsfController < ApplicationController redirect_back_or_default trash_dmsf_path id: @project end + # Reset WebDAV digest + def digest; end + + def reset_digest + if request.post? + raise StandardError, l(:notice_account_wrong_password) unless User.current.check_password?(params[:password]) + + # We have to create a token first to prevent an autogenerated token's value + token = Token.create!(user_id: User.current.id, action: 'dmsf-webdav-digest') + token.value = Digest::MD5.hexdigest( + "#{User.current.login}:#{RedmineDmsf::Webdav::AUTHENTICATION_REALM}:#{params[:password]}" + ) + token.save + flash[:notice] = l(:notice_webdav_digest_reset) + end + rescue StandardError => e + flash[:error] = e.message + ensure + redirect_to my_account_path + end + private def users_for_new_users diff --git a/app/views/dmsf/_digest.html.erb b/app/views/dmsf/_digest.html.erb new file mode 100644 index 00000000..0278f9a3 --- /dev/null +++ b/app/views/dmsf/_digest.html.erb @@ -0,0 +1,30 @@ +<% + # Redmine plugin for Document Management System "Features" + # + # Karel Pičman + # + # 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. +%> + +

+ <%= l(:text_dmsf_webdav_digest_reset) %> +

+<%= form_tag(dmsf_reset_digest_path) do %> + + <%= password_field_tag 'password', nil, autofocus: true %> + +<% end %> diff --git a/app/views/dmsf/digest.js.erb b/app/views/dmsf/digest.js.erb new file mode 100644 index 00000000..f8b45663 --- /dev/null +++ b/app/views/dmsf/digest.js.erb @@ -0,0 +1,22 @@ +<% + # Redmine plugin for Document Management System "Features" + # + # Karel Pičman + # + # 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. +%> + +$('#ajax-modal').html('<%= escape_javascript render partial: 'digest' %>'); +showModal('ajax-modal', '30%', '<%= l(:label_dmsf_webdav_digest) %>'); diff --git a/app/views/my/_sidebar.html.erb b/app/views/my/_sidebar.html.erb new file mode 100644 index 00000000..6510dede --- /dev/null +++ b/app/views/my/_sidebar.html.erb @@ -0,0 +1,38 @@ +<% + # encoding: utf-8 + # + # Redmine plugin for Document Management System "Features" + # + # Karel Pičman + # + # 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. +%> + +<%= render partial: "/#{File.dirname(__FILE__)}/../../../../../app/views/my/sidebar.html.erb" %> + +<%# DMSF extension do %> +<% if Setting.plugin_redmine_dmsf['dmsf_webdav_authentication'] == 'Digest' %> +

<%= l(:label_dmsf_webdav_digest) %>

+

+ <% token = Token.find_by(user_id: @user.id, action: 'dmsf-webdav-digest') %> + <% if token %> + <%= l(:label_dmsf_webdav_digest_created_on, distance_of_time_in_words(Time.now, token.created_on)) %> + <% else %> + <%= l(:label_missing_dmsf_webdav_digest) %> + <% end %> + (<%= link_to l(:button_reset), dmsf_digest_path, remote: true %>) +

+<% end %> +<%# end %> diff --git a/app/views/settings/_dmsf_settings.html.erb b/app/views/settings/_dmsf_settings.html.erb index 240a1668..f3c9afa2 100644 --- a/app/views/settings/_dmsf_settings.html.erb +++ b/app/views/settings/_dmsf_settings.html.erb @@ -277,6 +277,17 @@ <% visible_class = @settings['dmsf_webdav'].blank? ? 'dmsf-hidden' : '' %>
+

+ <%= content_tag :label, l(:label_webdav_authentication) %> + <% auth_types = [%w[Basic Basic], %w[Digest Digest]] %> + <% @settings['dmsf_webdav_authentication'] = 'Basic' if @settings['dmsf_webdav_authentication'].blank? %> + <%= select_tag 'settings[dmsf_webdav_authentication]', + options_for_select(auth_types, @settings['dmsf_webdav_authentication']) %> + + <%= l(:note_webdav_authentication) %>
+ <%= l(:label_default)%>: <%= auth_types[0][0] %> +
+

<%= content_tag :label, l(:label_webdav_strategy) %> <%= select_tag'settings[dmsf_webdav_strategy]', @@ -316,13 +327,12 @@ <%= l(:label_default) %>: ^\~\$|\.tmp$

-

<%= content_tag :label, l(:label_webdav_use_project_names) %> <%= check_box_tag 'settings[dmsf_webdav_use_project_names]', true, @settings['dmsf_webdav_use_project_names'] %> <%= l(:note_webdav_use_project_names) %>
- <%= l(:label_default)%>: <%= l(:general_text_No)%> + <%= l(:label_default)%>: <%= l(:general_text_No) %>

diff --git a/config/locales/cs.yml b/config/locales/cs.yml index de4db014..a98333fd 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -479,6 +479,16 @@ cs: error_not_supported_image_format: Nepodporovaný formát obrázku error_not_supported_video_format: Nepodporovaný formát videa + label_webdav_authentication: WebDAV autentifikace + note_webdav_authentication: Autentifikační metoda Basic není považována za bezpečnou a z toho důvodu je blokována + některými klienty. Metoda Digest, na druhou stranu, vyžaduje od uživatelů vytvoření DMSF WebDAV digestu v jejich + profilu za použití hesla, které se pak používá při autentifikaci z WebDAV klienta. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Chybějící Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: Je vyžadováno vaše heslo pro vygenerování nového DMSF WebDAV digestu. + notice_webdav_digest_reset: Váš DMSF WebDAV digest byl resetován. + easy_pages: modules: dmsf_locked_documents: My locked documents diff --git a/config/locales/de.yml b/config/locales/de.yml index bc6669ff..eb57ba44 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -474,6 +474,16 @@ de: error_not_supported_image_format: Nicht unterstütztes Bildformat error_not_supported_video_format: Nicht unterstütztes Videoformat + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: Von mir gesperrte Dokumente diff --git a/config/locales/en.yml b/config/locales/en.yml index b410533a..80fa8b0c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -478,6 +478,16 @@ en: error_not_supported_image_format: Not supported image format error_not_supported_video_format: Not supported video format + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: My locked documents diff --git a/config/locales/es.yml b/config/locales/es.yml index 1bdd9039..e0a2454d 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -478,6 +478,16 @@ es: error_not_supported_image_format: Not supported image format error_not_supported_video_format: Not supported video format + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: My locked documents diff --git a/config/locales/fa.yml b/config/locales/fa.yml index a62f748b..6259366c 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -457,6 +457,16 @@ fa: error_not_supported_image_format: Not supported image format error_not_supported_video_format: Not supported video format + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: اسناد قفل شده‌ی من diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 533c583b..c1455a13 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -479,6 +479,16 @@ fr: error_not_supported_image_format: Not supported image format error_not_supported_video_format: Not supported video format + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: My locked documents diff --git a/config/locales/hu.yml b/config/locales/hu.yml index eebaec07..31c3066e 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -479,6 +479,16 @@ hu: error_not_supported_image_format: Not supported image format error_not_supported_video_format: Not supported video format + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: My locked documents diff --git a/config/locales/it.yml b/config/locales/it.yml index 08a1d914..4206932d 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -478,6 +478,16 @@ it: # Italian strings thx 2 Matteo Arceci! error_not_supported_image_format: Not supported image format error_not_supported_video_format: Not supported video format + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: My locked documents diff --git a/config/locales/ja.yml b/config/locales/ja.yml index fdbdcfb5..57bfff04 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -480,6 +480,16 @@ ja: error_not_supported_image_format: Not supported image format error_not_supported_video_format: Not supported video format + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: 自分がロック中の文書 diff --git a/config/locales/ko.yml b/config/locales/ko.yml index db7fc191..f6f91487 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -478,6 +478,16 @@ ko: error_not_supported_image_format: Not supported image format error_not_supported_video_format: Not supported video format + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: 내 잠긴 파일 diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 41b6647e..0528b09c 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -478,6 +478,16 @@ nl: error_not_supported_image_format: Not supported image format error_not_supported_video_format: Not supported video format + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: My locked documents diff --git a/config/locales/pl.yml b/config/locales/pl.yml index ca630664..cd7a54b5 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -477,6 +477,16 @@ pl: error_not_supported_image_format: Not supported image format error_not_supported_video_format: Not supported video format + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: My locked documents diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index fc3fcf9b..722d2538 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -478,6 +478,16 @@ pt-BR: error_not_supported_image_format: Not supported image format error_not_supported_video_format: Not supported video format + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: My locked documents diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 30dc44a6..61b2f160 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -479,6 +479,16 @@ sl: error_not_supported_image_format: Not supported image format error_not_supported_video_format: Not supported video format + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: My locked documents diff --git a/config/locales/uk.yml b/config/locales/uk.yml index a6547ac1..45a76786 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -480,6 +480,16 @@ uk: error_not_supported_image_format: Not supported image format error_not_supported_video_format: Not supported video format + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: Мої заблоковані документи diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 64fb497c..61663c8e 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -477,6 +477,16 @@ zh-TW: error_not_supported_image_format: Not supported image format error_not_supported_video_format: Not supported video format + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: My locked documents diff --git a/config/locales/zh.yml b/config/locales/zh.yml index c1d791d7..b262a7c0 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -479,6 +479,16 @@ zh: error_not_supported_image_format: Not supported image format error_not_supported_video_format: Not supported video format + label_webdav_authentication: WebDAV Authentication + note_webdav_authentication: Basic authentication method is considered as unsecure and therefore blocked by some + clients. Digest authentication, on the other hand, requires users to create a DMSF WebDAV digest in their profiles + using a password that must by used for authentication in their WebDAV clients after that. + label_dmsf_webdav_digest_created_on: "Dmsf WebDAV digest created %{value} ago" + label_missing_dmsf_webdav_digest: Missing a Dmsf WebDAV digest + label_dmsf_webdav_digest: DMSF WebDAV digest + text_dmsf_webdav_digest_reset: You are supposed to enter your password to generate a new DMSF WebDAV digest. + notice_webdav_digest_reset: Your DMSF WebDAV digest was reset. + easy_pages: modules: dmsf_locked_documents: My locked documents diff --git a/config/routes.rb b/config/routes.rb index 1e1154a5..265ea886 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -56,6 +56,8 @@ if Redmine::Plugin.installed? 'redmine_dmsf' put '/projects/:id/dmsf', controller: 'dmsf', action: 'drop' get '/projects/:id/dmsf/empty_trash', to: 'dmsf#empty_trash', as: 'empty_trash' get '/dmsf', to: 'dmsf#index', as: 'dmsf_index' + get '/dmsf/digest', to: 'dmsf#digest', as: 'dmsf_digest' + post '/dmsf/digest', to: 'dmsf#reset_digest', as: 'dmsf_reset_digest' # dmsf_context_menu_controller match '/projects/dmsf/context_menu', to: 'dmsf_context_menus#dmsf', as: 'dmsf_context_menu', via: %i[get post] @@ -98,22 +100,6 @@ if Redmine::Plugin.installed? 'redmine_dmsf' get '/dmsf_files/:id', controller: 'dmsf_files', action: 'show' get '/dmsf_files/:id/download', controller: 'dmsf_files', action: 'show', download: '' - # - # files_copy controller - # /dmsf/files//copy - ## - post '/dmsf/files/:id/copy/copy', controller: 'dmsf_files_copy', action: 'copy' - post '/dmsf/files/:id/copy/move', controller: 'dmsf_files_copy', action: 'move' - get '/dmsf/files/:id/copy', controller: 'dmsf_files_copy', action: 'new', as: 'copy_file' - - # - # folders_copy controller - # /dmsf/folders//copy - ## - post '/dmsf/folders/:id/copy/copy', controller: 'dmsf_folders_copy', action: 'copy' - post '/dmsf/folders/:id/copy/move', controller: 'dmsf_folders_copy', action: 'move' - get '/dmsf/folders/:id/copy', controller: 'dmsf_folders_copy', action: 'new', as: 'copy_folder' - # # dmsf_files controller # /dmsf/files/ diff --git a/init.rb b/init.rb index 0c575c5a..04b2e4e2 100644 --- a/init.rb +++ b/init.rb @@ -62,7 +62,8 @@ Redmine::Plugin.register :redmine_dmsf do 'dmsf_global_menu_disabled' => nil, 'dmsf_default_query' => nil, 'empty_minor_version_by_default' => nil, - 'remove_original_documents_module' => nil + 'remove_original_documents_module' => nil, + 'dmsf_webdav_authentication' => 'Basic' } end diff --git a/lib/dav4rack/request.rb b/lib/dav4rack/request.rb index 4378f56a..ca939a52 100644 --- a/lib/dav4rack/request.rb +++ b/lib/dav4rack/request.rb @@ -25,7 +25,10 @@ module Dav4rack end def authorization - get_header 'HTTP_AUTHORIZATION' + get_header('HTTP_AUTHORIZATION') || + get_header('X-HTTP_AUTHORIZATION') || + get_header('X_HTTP_AUTHORIZATION') || + get_header('REDIRECT_X_HTTP_AUTHORIZATION') end # path relative to root uri diff --git a/lib/dav4rack/resource.rb b/lib/dav4rack/resource.rb index 4a5cf23c..9451997c 100644 --- a/lib/dav4rack/resource.rb +++ b/lib/dav4rack/resource.rb @@ -505,7 +505,7 @@ module Dav4rack def propnames_xml response = Ox::Element.new(D_RESPONSE) response << ox_element(D_HREF, href) - propstats response, { OK => [propname_properties.map { |p| [p, nil] }].to_h } + propstats response, { OK => propname_properties.index_with { |p| [p, nil] } } response end diff --git a/lib/redmine_dmsf/webdav/custom_middleware.rb b/lib/redmine_dmsf/webdav/custom_middleware.rb index 5033ebb0..8284e153 100644 --- a/lib/redmine_dmsf/webdav/custom_middleware.rb +++ b/lib/redmine_dmsf/webdav/custom_middleware.rb @@ -23,6 +23,7 @@ require "#{File.dirname(__FILE__)}/resource_proxy" module RedmineDmsf module Webdav + AUTHENTICATION_REALM = 'DMSF content' # Custom middleware class CustomMiddleware def initialize(app) diff --git a/lib/redmine_dmsf/webdav/dmsf_controller.rb b/lib/redmine_dmsf/webdav/dmsf_controller.rb index d5d11036..576be978 100644 --- a/lib/redmine_dmsf/webdav/dmsf_controller.rb +++ b/lib/redmine_dmsf/webdav/dmsf_controller.rb @@ -31,6 +31,78 @@ module RedmineDmsf # Switch the locale to English for WebDAV requests in order to have log messages in English I18n.with_locale(:en, &action) end + + def process + return super unless Setting.plugin_redmine_dmsf['dmsf_webdav_authentication'] == 'Digest' + + status = skip_authorization? || authenticate ? process_action || OK : Dav4rack::HttpStatus::Unauthorized + rescue Dav4rack::HttpStatus::Status => e + status = e + ensure + if status + response.status = status.code + if status.code == 401 + time_stamp = Time.now.to_i + h_once = Digest::MD5.hexdigest("#{time_stamp}:#{SecureRandom.hex(32)}") + nonce = Base64.strict_encode64("#{time_stamp}#{h_once}") + response['WWW-Authenticate'] = + %(Digest realm="#{authentication_realm}", nonce="#{nonce}", algorithm="MD5", qop="auth") + end + end + end + + def authenticate + return super unless Setting.plugin_redmine_dmsf['dmsf_webdav_authentication'] == 'Digest' + + auth_header = request.authorization.to_s + scheme = auth_header.split(' ', 2).first&.downcase + if scheme == 'digest' + Rails.logger.info 'Authentication: digest' + auth = Rack::Auth::Digest::Request.new(request.env) + params = auth.params + username = params['username'] + response = params['response'] + cnonce = params['cnonce'] + nonce = params['nonce'] + uri = params['uri'] + qop = params['qop'] + nc = params['nc'] + user = User.find_by(login: username) + unless user + log_error('Digest authentication: provided user name has no match in the DB') + raise Unauthorized + end + token = Token.find_by(user_id: user.id, action: 'dmsf-webdav-digest') + if token.nil? && defined?(EasyExtensions) + if user.easy_digest_token_expired? + log_error('Digest authentication: digest token expired') + raise Unauthorized + end + ha1 = user.easy_digest_token + else + unless token + log_error("Digest authentication: no digest found for #{user}") + raise Unauthorized + end + ha1 = token.value + end + ha2 = Digest::MD5.hexdigest("#{request.env['REQUEST_METHOD']}:#{uri}") + required_response = if qop + Digest::MD5.hexdigest("#{ha1}:#{nonce}:#{nc}:#{cnonce}:#{qop}:#{ha2}") + else + Digest::MD5.hexdigest("#{ha1}:#{nonce}:#{ha2}") + end + if required_response == response + User.current = user + else + Rails.logger.error 'Digest authentication: digest response is incorrect' + end + end + raise Unauthorized if User.current.anonymous? + + Rails.logger.info "Current user: #{User.current}, User-Agent: #{request.user_agent}" + User.current + end end end end diff --git a/lib/redmine_dmsf/webdav/resource_proxy.rb b/lib/redmine_dmsf/webdav/resource_proxy.rb index 22d53ae0..29228035 100644 --- a/lib/redmine_dmsf/webdav/resource_proxy.rb +++ b/lib/redmine_dmsf/webdav/resource_proxy.rb @@ -199,6 +199,10 @@ module RedmineDmsf namespaces[ns_href] || add_namespace(ns_href) end + def authentication_realm + RedmineDmsf::Webdav::AUTHENTICATION_REALM + end + private def get_resource_class(path) diff --git a/test/functional/dmsf_controller_test.rb b/test/functional/dmsf_controller_test.rb index a6783863..32686eed 100644 --- a/test/functional/dmsf_controller_test.rb +++ b/test/functional/dmsf_controller_test.rb @@ -648,4 +648,31 @@ class DmsfControllerTest < RedmineDmsf::Test::TestCase assert_response :redirect assert_nil flash[:error] end + + def test_digest + post '/login', params: { username: 'jsmith', password: 'jsmith' } + get '/dmsf/digest', xhr: true + assert_response :success + end + + def test_digest_unauthorized + get '/dmsf/digest', xhr: true + assert_response :unauthorized + end + + def test_reset_digest + post '/login', params: { username: 'jsmith', password: 'jsmith' } + post '/dmsf/digest', params: { password: 'jsmith' } + assert_response :redirect + assert_redirected_to my_account_path + token = Token.find_by(user_id: @jsmith.id, action: 'dmsf-webdav-digest') + assert token + assert_equal Digest::MD5.hexdigest("jsmith:#{RedmineDmsf::Webdav::AUTHENTICATION_REALM}:jsmith"), token.value + end + + def test_reset_digest_unauthorized + post '/dmsf/digest', params: { password: 'jsmith' } + assert_response :redirect + assert_redirected_to 'http://www.example.com/login?back_url=http%3A%2F%2Fwww.example.com%2Fdmsf%2Fdigest' + end end