summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--comphelper/source/misc/docpasswordhelper.cxx33
-rw-r--r--include/comphelper/docpasswordhelper.hxx2
-rw-r--r--sfx2/source/dialog/filedlghelper.cxx23
-rw-r--r--sw/qa/uitest/writer_tests6/tdf144374.py63
-rw-r--r--sw/source/filter/ww8/docxexport.cxx61
5 files changed, 175 insertions, 7 deletions
diff --git a/comphelper/source/misc/docpasswordhelper.cxx b/comphelper/source/misc/docpasswordhelper.cxx
index 980faff14698..81ed4bcfed77 100644
--- a/comphelper/source/misc/docpasswordhelper.cxx
+++ b/comphelper/source/misc/docpasswordhelper.cxx
@@ -110,6 +110,39 @@ uno::Sequence< beans::PropertyValue > DocPasswordHelper::GenerateNewModifyPasswo
}
+uno::Sequence<beans::PropertyValue>
+DocPasswordHelper::GenerateNewModifyPasswordInfoOOXML(std::u16string_view aPassword)
+{
+ uno::Sequence<beans::PropertyValue> aResult;
+
+ uno::Sequence<sal_Int8> aSalt = GenerateRandomByteSequence(16);
+ OUStringBuffer aBuffer;
+ comphelper::Base64::encode(aBuffer, aSalt);
+ OUString sSalt = aBuffer.toString();
+
+ sal_Int32 const nIterationCount = 100000;
+ OUString sAlgorithm("SHA-512");
+
+ const OUString sHash(GetOoxHashAsBase64(OUString(aPassword), sSalt, nIterationCount,
+ comphelper::Hash::IterCount::APPEND, sAlgorithm));
+
+ if (!sHash.isEmpty())
+ {
+ aResult.realloc(4);
+ aResult[0].Name = "algorithm-name";
+ aResult[0].Value <<= sAlgorithm;
+ aResult[1].Name = "salt";
+ aResult[1].Value <<= sSalt;
+ aResult[2].Name = "iteration-count";
+ aResult[2].Value <<= nIterationCount;
+ aResult[3].Name = "hash";
+ aResult[3].Value <<= sHash;
+ }
+
+ return aResult;
+}
+
+
bool DocPasswordHelper::IsModifyPasswordCorrect( std::u16string_view aPassword, const uno::Sequence< beans::PropertyValue >& aInfo )
{
bool bResult = false;
diff --git a/include/comphelper/docpasswordhelper.hxx b/include/comphelper/docpasswordhelper.hxx
index bf36cb9250aa..fc40739184bc 100644
--- a/include/comphelper/docpasswordhelper.hxx
+++ b/include/comphelper/docpasswordhelper.hxx
@@ -113,6 +113,8 @@ public:
static css::uno::Sequence< css::beans::PropertyValue >
GenerateNewModifyPasswordInfo( std::u16string_view aPassword );
+ static css::uno::Sequence<css::beans::PropertyValue>
+ GenerateNewModifyPasswordInfoOOXML(std::u16string_view aPassword);
/** This helper function allows to check whether
the "Password to modify" provided by user is the correct one.
diff --git a/sfx2/source/dialog/filedlghelper.cxx b/sfx2/source/dialog/filedlghelper.cxx
index 99e0de639070..35fc5d166e11 100644
--- a/sfx2/source/dialog/filedlghelper.cxx
+++ b/sfx2/source/dialog/filedlghelper.cxx
@@ -2944,11 +2944,24 @@ ErrCode RequestPassword(const std::shared_ptr<const SfxFilter>& pCurrentFilter,
if ( bMSType )
{
- // the empty password has 0 as Hash
- sal_Int32 nHash = SfxMedium::CreatePasswordToModifyHash( pPasswordRequest->getPasswordToModify(),
- pCurrentFilter->GetServiceName() == "com.sun.star.text.TextDocument" );
- if ( nHash )
- pSet->Put( SfxUnoAnyItem( SID_MODIFYPASSWORDINFO, uno::makeAny( nHash ) ) );
+ if (bOOXML)
+ {
+ uno::Sequence<beans::PropertyValue> aModifyPasswordInfo
+ = ::comphelper::DocPasswordHelper::GenerateNewModifyPasswordInfoOOXML(
+ pPasswordRequest->getPasswordToModify());
+ if (aModifyPasswordInfo.hasElements())
+ pSet->Put(
+ SfxUnoAnyItem(SID_MODIFYPASSWORDINFO, uno::makeAny(aModifyPasswordInfo)));
+ }
+ else
+ {
+ // the empty password has 0 as Hash
+ sal_Int32 nHash = SfxMedium::CreatePasswordToModifyHash(
+ pPasswordRequest->getPasswordToModify(),
+ pCurrentFilter->GetServiceName() == "com.sun.star.text.TextDocument");
+ if (nHash)
+ pSet->Put(SfxUnoAnyItem(SID_MODIFYPASSWORDINFO, uno::makeAny(nHash)));
+ }
}
else
{
diff --git a/sw/qa/uitest/writer_tests6/tdf144374.py b/sw/qa/uitest/writer_tests6/tdf144374.py
new file mode 100644
index 000000000000..c484b79b54cd
--- /dev/null
+++ b/sw/qa/uitest/writer_tests6/tdf144374.py
@@ -0,0 +1,63 @@
+# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+from uitest.framework import UITestCase
+from libreoffice.uno.propertyvalue import mkPropertyValues
+from org.libreoffice.unotest import systemPathToFileUrl
+from uitest.uihelper.common import select_by_text
+from tempfile import TemporaryDirectory
+import os.path
+
+#Bug 144374 - Writer: FILESAVE to DOCX as read-only with additional password protection for editing not working
+
+class tdf144374(UITestCase):
+
+ def test_tdf144374_DOCX(self):
+ with self.ui_test.create_doc_in_start_center("writer"):
+ with TemporaryDirectory() as tempdir:
+ xFilePath = os.path.join(tempdir, "tdf144374-tmp.docx")
+
+ # Save the document
+ with self.ui_test.execute_dialog_through_command(".uno:Save", close_button="") as xSaveDialog:
+ xFileName = xSaveDialog.getChild("file_name")
+ xFileName.executeAction("TYPE", mkPropertyValues({"KEYCODE":"CTRL+A"}))
+ xFileName.executeAction("TYPE", mkPropertyValues({"KEYCODE":"BACKSPACE"}))
+ xFileName.executeAction("TYPE", mkPropertyValues({"TEXT": xFilePath}))
+ xFileTypeCombo = xSaveDialog.getChild("file_type")
+ select_by_text(xFileTypeCombo, "Office Open XML Text (Transitional) (.docx)")
+ xPasswordCheckButton = xSaveDialog.getChild("password")
+ xPasswordCheckButton.executeAction("CLICK", tuple())
+ xOpen = xSaveDialog.getChild("open")
+
+ with self.ui_test.execute_dialog_through_action(xOpen, "CLICK") as xPasswordDialog:
+ xReadonly = xPasswordDialog.getChild("readonly")
+ xReadonly.executeAction("CLICK", tuple())
+ xNewPassword = xPasswordDialog.getChild("newpassroEntry")
+ xNewPassword.executeAction("TYPE", mkPropertyValues({"TEXT": "password"}))
+ xConfirmPassword = xPasswordDialog.getChild("confirmropassEntry")
+ xConfirmPassword.executeAction("TYPE", mkPropertyValues({"TEXT": "password"}))
+
+ # DOCX confirmation dialog is displayed
+ xWarnDialog = self.xUITest.getTopFocusWindow()
+ xSave = xWarnDialog.getChild("save")
+ self.ui_test.close_dialog_through_button(xSave)
+
+ self.ui_test.close_doc()
+
+ with self.ui_test.load_file(systemPathToFileUrl(xFilePath)):
+ xWriterEdit = self.xUITest.getTopFocusWindow().getChild("writer_edit")
+ document = self.ui_test.get_component()
+
+ self.assertTrue(document.isReadonly())
+
+ #Without the fix in place, this dialog wouldn't have been displayed
+ with self.ui_test.execute_dialog_through_action(xWriterEdit, "TYPE", mkPropertyValues({"KEYCODE": "CTRL+SHIFT+M"})) as xDialog:
+ xPassword = xDialog.getChild("newpassEntry")
+ xPassword.executeAction("TYPE", mkPropertyValues({"TEXT": "password"}))
+
+ self.assertFalse(document.isReadonly())
+
+# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/sw/source/filter/ww8/docxexport.cxx b/sw/source/filter/ww8/docxexport.cxx
index b90fc8c74a02..a1ead25a3b7e 100644
--- a/sw/source/filter/ww8/docxexport.cxx
+++ b/sw/source/filter/ww8/docxexport.cxx
@@ -987,6 +987,8 @@ void DocxExport::WriteSettings()
if( !pViewShell && !m_aSettings.hasData() && !m_pAttrOutput->HasFootnotes() && !m_pAttrOutput->HasEndnotes())
return;
+ SwDocShell* pDocShell = m_rDoc.GetDocShell();
+
m_rFilter.addRelation( m_pDocumentFS->getOutputStream(),
oox::getRelationship(Relationship::SETTINGS),
u"settings.xml" );
@@ -998,6 +1000,61 @@ void DocxExport::WriteSettings()
pFS->startElementNS( XML_w, XML_settings,
FSNS( XML_xmlns, XML_w ), m_rFilter.getNamespaceURL(OOX_NS(doc)) );
+ // Write protection
+ const uno::Sequence<beans::PropertyValue> aInfo = pDocShell->GetModifyPasswordInfo();
+ if (aInfo.hasElements())
+ {
+ OUString sAlgorithm, sSalt, sHash;
+ sal_Int32 nCount = 0;
+ for (const auto& prop : aInfo)
+ {
+ if (prop.Name == "algorithm-name")
+ prop.Value >>= sAlgorithm;
+ else if (prop.Name == "salt")
+ prop.Value >>= sSalt;
+ else if (prop.Name == "iteration-count")
+ prop.Value >>= nCount;
+ else if (prop.Name == "hash")
+ prop.Value >>= sHash;
+ }
+ if (!sAlgorithm.isEmpty() && !sSalt.isEmpty() && !sHash.isEmpty())
+ {
+ sal_Int32 nAlgorithmSid = 0;
+ if (sAlgorithm == "MD2")
+ nAlgorithmSid = 1;
+ else if (sAlgorithm == "MD4")
+ nAlgorithmSid = 2;
+ else if (sAlgorithm == "MD5")
+ nAlgorithmSid = 3;
+ else if (sAlgorithm == "SHA-1")
+ nAlgorithmSid = 4;
+ else if (sAlgorithm == "MAC")
+ nAlgorithmSid = 5;
+ else if (sAlgorithm == "RIPEMD")
+ nAlgorithmSid = 6;
+ else if (sAlgorithm == "RIPEMD-160")
+ nAlgorithmSid = 7;
+ else if (sAlgorithm == "HMAC")
+ nAlgorithmSid = 9;
+ else if (sAlgorithm == "SHA-256")
+ nAlgorithmSid = 12;
+ else if (sAlgorithm == "SHA-384")
+ nAlgorithmSid = 13;
+ else if (sAlgorithm == "SHA-512")
+ nAlgorithmSid = 14;
+
+ if (nAlgorithmSid != 0)
+ pFS->singleElementNS(XML_w, XML_writeProtection,
+ FSNS(XML_w, XML_cryptProviderType), "rsaAES",
+ FSNS(XML_w, XML_cryptAlgorithmClass), "hash",
+ FSNS(XML_w, XML_cryptAlgorithmType), "typeAny",
+ FSNS(XML_w, XML_cryptAlgorithmSid), OString::number(nAlgorithmSid).getStr(),
+ FSNS(XML_w, XML_cryptSpinCount), OString::number(nCount).getStr(),
+ FSNS(XML_w, XML_hash), sHash.toUtf8().getStr(),
+ FSNS(XML_w, XML_salt), sSalt.toUtf8().getStr());
+ }
+ }
+
// View
if (pViewShell && pViewShell->GetViewOptions()->getBrowseMode())
{
@@ -1114,7 +1171,7 @@ void DocxExport::WriteSettings()
DocxAttributeOutput::WriteFootnoteEndnotePr( pFS, XML_endnotePr, m_rDoc.GetEndNoteInfo(), XML_endnote );
// Has themeFontLang information
- uno::Reference< beans::XPropertySet > xPropSet( m_rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW );
+ uno::Reference< beans::XPropertySet > xPropSet( pDocShell->GetBaseModel(), uno::UNO_QUERY_THROW );
bool bUseGrabBagProtection = false;
bool bWriterWantsToProtect = false;
@@ -1274,7 +1331,7 @@ void DocxExport::WriteSettings()
else if ( nToken == XML_edit && sValue == "readOnly" )
{
// Ignore the case where read-only was not enforced, but now is. That is handled by _MarkAsFinal
- bReadOnlyStatusUnchanged = m_rDoc.GetDocShell()->IsSecurityOptOpenReadOnly();
+ bReadOnlyStatusUnchanged = pDocShell->IsSecurityOptOpenReadOnly();
}
else if ( nToken == XML_enforcement )
bEnforced = sValue.toBoolean();