diff options
-rw-r--r-- | comphelper/source/misc/docpasswordhelper.cxx | 57 | ||||
-rw-r--r-- | include/comphelper/docpasswordhelper.hxx | 17 | ||||
-rw-r--r-- | include/sfx2/objsh.hxx | 3 | ||||
-rw-r--r-- | sfx2/source/dialog/securitypage.cxx | 26 | ||||
-rw-r--r-- | sfx2/source/doc/objserv.cxx | 31 | ||||
-rw-r--r-- | sw/qa/uitest/data/tdf128744.docx | bin | 0 -> 21228 bytes | |||
-rw-r--r-- | sw/qa/uitest/writer_tests7/tdf128744.py | 71 | ||||
-rw-r--r-- | sw/source/uibase/uiview/view2.cxx | 57 |
8 files changed, 237 insertions, 25 deletions
diff --git a/comphelper/source/misc/docpasswordhelper.cxx b/comphelper/source/misc/docpasswordhelper.cxx index e894b0d77bb7..5edb3949c977 100644 --- a/comphelper/source/misc/docpasswordhelper.cxx +++ b/comphelper/source/misc/docpasswordhelper.cxx @@ -137,6 +137,63 @@ DocPasswordHelper::GenerateNewModifyPasswordInfoOOXML(std::u16string_view aPassw } +uno::Sequence< beans::PropertyValue > DocPasswordHelper::ConvertPasswordInfo( const uno::Sequence< beans::PropertyValue >& aInfo ) +{ + uno::Sequence< beans::PropertyValue > aResult; + OUString sAlgorithm, sHash, sSalt, sCount; + sal_Int32 nAlgorithm = 0; + + for ( const auto & prop : aInfo ) + { + if ( prop.Name == "cryptAlgorithmSid" ) + { + prop.Value >>= sAlgorithm; + nAlgorithm = sAlgorithm.toInt32(); + } + else if ( prop.Name == "salt" ) + prop.Value >>= sSalt; + else if ( prop.Name == "cryptSpinCount" ) + prop.Value >>= sCount; + else if ( prop.Name == "hash" ) + prop.Value >>= sHash; + } + + if (nAlgorithm == 1) + sAlgorithm = "MD2"; + else if (nAlgorithm == 2) + sAlgorithm = "MD4"; + else if (nAlgorithm == 3) + sAlgorithm = "MD5"; + else if (nAlgorithm == 4) + sAlgorithm = "SHA-1"; + else if (nAlgorithm == 5) + sAlgorithm = "MAC"; + else if (nAlgorithm == 6) + sAlgorithm = "RIPEMD"; + else if (nAlgorithm == 7) + sAlgorithm = "RIPEMD-160"; + else if (nAlgorithm == 9) + sAlgorithm = "HMAC"; + else if (nAlgorithm == 12) + sAlgorithm = "SHA-256"; + else if (nAlgorithm == 13) + sAlgorithm = "SHA-384"; + else if (nAlgorithm == 14) + sAlgorithm = "SHA-512"; + + if ( !sCount.isEmpty() ) + { + sal_Int32 nCount = sCount.toInt32(); + aResult = { comphelper::makePropertyValue("algorithm-name", sAlgorithm), + comphelper::makePropertyValue("salt", sSalt), + comphelper::makePropertyValue("iteration-count", nCount), + comphelper::makePropertyValue("hash", 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 0b9e646bd2b8..64d7ba9782ec 100644 --- a/include/comphelper/docpasswordhelper.hxx +++ b/include/comphelper/docpasswordhelper.hxx @@ -113,6 +113,23 @@ public: static css::uno::Sequence< css::beans::PropertyValue > GenerateNewModifyPasswordInfo( std::u16string_view aPassword ); + /** This helper function converts a grab-bagged password, e.g. the + trackChanges password which has no complete inner equivalent to + the inner format. The result sequence contains the hash and the + algorithm-related info to use e.g. in IsModifyPasswordCorrect(). + + @param aInfo + The sequence containing the hash and the algorithm-related info + according to the OOXML origin, used by grab-bagging. + + @return + The sequence containing the hash and the algorithm-related info + in the inner format. + */ + + static css::uno::Sequence< css::beans::PropertyValue > ConvertPasswordInfo( + const css::uno::Sequence< css::beans::PropertyValue >& aInfo ); + static css::uno::Sequence<css::beans::PropertyValue> GenerateNewModifyPasswordInfoOOXML(std::u16string_view aPassword); diff --git a/include/sfx2/objsh.hxx b/include/sfx2/objsh.hxx index b7f9e1fa0688..428995bff493 100644 --- a/include/sfx2/objsh.hxx +++ b/include/sfx2/objsh.hxx @@ -781,6 +781,9 @@ public: /// Gets the certificate that is already picked by the user but not yet used for signing. css::uno::Reference<css::security::XCertificate> GetSignPDFCertificate() const; + /// Gets grab-bagged password info to unprotect change tracking with verification + css::uno::Sequence< css::beans::PropertyValue > GetDocumentProtectionFromGrabBag() const; + // Lock all unlocked views, and returns a guard object which unlocks those views when destructed virtual std::unique_ptr<LockAllViewsGuard> LockAllViews() { diff --git a/sfx2/source/dialog/securitypage.cxx b/sfx2/source/dialog/securitypage.cxx index 0ff73b44f6f9..39bdc7cb1eba 100644 --- a/sfx2/source/dialog/securitypage.cxx +++ b/sfx2/source/dialog/securitypage.cxx @@ -33,6 +33,7 @@ #include <svl/poolitem.hxx> #include <svl/intitem.hxx> #include <svl/PasswordHelper.hxx> +#include <comphelper/docpasswordhelper.hxx> #include <sfx2/strings.hrc> @@ -114,12 +115,29 @@ static bool lcl_IsPasswordCorrect( std::u16string_view rPassword ) pCurDocShell->GetProtectionHash( aPasswordHash ); // check if supplied password was correct - uno::Sequence< sal_Int8 > aNewPasswd( aPasswordHash ); - SvPasswordHelper::GetHashPassword( aNewPasswd, rPassword ); - if (SvPasswordHelper::CompareHashPassword( aPasswordHash, rPassword )) - bRes = true; // password was correct + if (aPasswordHash.getLength() == 1 && aPasswordHash[0] == 1) + { + // dummy RedlinePassword from OOXML import: get real password info + // from the grab-bag to verify the password + const css::uno::Sequence< css::beans::PropertyValue > aDocumentProtection = + pCurDocShell->GetDocumentProtectionFromGrabBag(); + bRes = + // password is ok, if there is no DocumentProtection in the GrabBag, + // i.e. the dummy RedlinePassword imported from an OpenDocument file + !aDocumentProtection.hasElements() || + // verify password with the password info imported from OOXML + ::comphelper::DocPasswordHelper::IsModifyPasswordCorrect( rPassword, + ::comphelper::DocPasswordHelper::ConvertPasswordInfo ( aDocumentProtection ) ); + } else { + uno::Sequence< sal_Int8 > aNewPasswd( aPasswordHash ); + SvPasswordHelper::GetHashPassword( aNewPasswd, rPassword ); + bRes = SvPasswordHelper::CompareHashPassword( aPasswordHash, rPassword ); + } + + if ( !bRes ) + { std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(nullptr, VclMessageType::Info, VclButtonsType::Ok, SfxResId(RID_SVXSTR_INCORRECT_PASSWORD))); diff --git a/sfx2/source/doc/objserv.cxx b/sfx2/source/doc/objserv.cxx index 452d059d0c16..3a6a0ceea2a8 100644 --- a/sfx2/source/doc/objserv.cxx +++ b/sfx2/source/doc/objserv.cxx @@ -104,6 +104,7 @@ #include <unotools/ucbstreamhelper.hxx> #include <unotools/streamwrap.hxx> #include <comphelper/sequenceashashmap.hxx> +#include <editeng/unoprnms.hxx> #include <autoredactdialog.hxx> @@ -2097,4 +2098,34 @@ const uno::Sequence<sal_Int8>& SfxObjectShell::getUnoTunnelId() return theSfxObjectShellUnoTunnelId.getSeq(); } +uno::Sequence< beans::PropertyValue > SfxObjectShell::GetDocumentProtectionFromGrabBag() const +{ + uno::Reference<frame::XModel> xModel = GetBaseModel(); + + if (!xModel.is()) + { + return uno::Sequence< beans::PropertyValue>(); + } + + uno::Reference< beans::XPropertySet > xPropSet( xModel, uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo(); + const OUString aGrabBagName = UNO_NAME_MISC_OBJ_INTEROPGRABBAG; + if ( xPropSetInfo->hasPropertyByName( aGrabBagName ) ) + { + uno::Sequence< beans::PropertyValue > propList; + xPropSet->getPropertyValue( aGrabBagName ) >>= propList; + for( const auto& rProp : std::as_const(propList) ) + { + if (rProp.Name == "DocumentProtection") + { + uno::Sequence< beans::PropertyValue > rAttributeList; + rProp.Value >>= rAttributeList; + return rAttributeList; + } + } + } + + return uno::Sequence< beans::PropertyValue>(); +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/qa/uitest/data/tdf128744.docx b/sw/qa/uitest/data/tdf128744.docx Binary files differnew file mode 100644 index 000000000000..b03bef21baa4 --- /dev/null +++ b/sw/qa/uitest/data/tdf128744.docx diff --git a/sw/qa/uitest/writer_tests7/tdf128744.py b/sw/qa/uitest/writer_tests7/tdf128744.py new file mode 100644 index 000000000000..34201e858675 --- /dev/null +++ b/sw/qa/uitest/writer_tests7/tdf128744.py @@ -0,0 +1,71 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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 uitest.uihelper.common import get_state_as_dict +from uitest.uihelper.common import select_pos +from uitest.uihelper.common import get_url_for_data_file + +class tdf128744(UITestCase): + + def test_tdf128744(self): + # load the sample file + with self.ui_test.load_file(get_url_for_data_file("tdf128744.docx")): + + # first try to unprotect Record Changes with an invalid password + + with self.ui_test.execute_dialog_through_command(".uno:SetDocumentProperties") as xDialog: + xRecordChangesCheckbox = xDialog.getChild("recordchanges") + self.assertEqual(get_state_as_dict(xRecordChangesCheckbox)["Selected"], "true") + xTabs = xDialog.getChild("tabcontrol") + select_pos(xTabs, "3") #tab Security + xProtectBtn = xDialog.getChild("protect") + + # No close_button: click on the "Ok" inside to check the "Invalid password" infobox + with self.ui_test.execute_blocking_action(xProtectBtn.executeAction, args=('CLICK', ()), close_button="") as xPasswordDialog: + self.assertEqual(get_state_as_dict(xPasswordDialog)["DisplayText"], "Enter Password") + xPassword = xPasswordDialog.getChild("pass1ed") + xPassword.executeAction("TYPE", mkPropertyValues({"TEXT": "bad password"})) + print(xPasswordDialog.getChildren()) + xOkBtn = xPasswordDialog.getChild("ok") + with self.ui_test.execute_blocking_action(xOkBtn.executeAction, args=('CLICK', ())) as xInfoBox: + # "Invalid password" infobox + self.assertEqual(get_state_as_dict(xInfoBox)["DisplayText"], 'Information') + + # now open the dialog again and read the properties, Record Changes checkbox is still enabled + with self.ui_test.execute_dialog_through_command(".uno:SetDocumentProperties", close_button="cancel") as xDialog: + xRecordChangesCheckbox = xDialog.getChild("recordchanges") + self.assertEqual(get_state_as_dict(xRecordChangesCheckbox)["Selected"], "true") + xResetBtn = xDialog.getChild("reset") + xResetBtn.executeAction("CLICK", tuple()) + + # unprotect Record Changes with the valid password "test" + + with self.ui_test.execute_dialog_through_command(".uno:SetDocumentProperties") as xDialog: + xRecordChangesCheckbox = xDialog.getChild("recordchanges") + self.assertEqual(get_state_as_dict(xRecordChangesCheckbox)["Selected"], "true") + xTabs = xDialog.getChild("tabcontrol") + select_pos(xTabs, "3") #tab Security + xProtectBtn = xDialog.getChild("protect") + + with self.ui_test.execute_blocking_action(xProtectBtn.executeAction, args=('CLICK', ())) as xPasswordDialog: + self.assertEqual(get_state_as_dict(xPasswordDialog)["DisplayText"], "Enter Password") + xPassword = xPasswordDialog.getChild("pass1ed") + # give the correct password + xPassword.executeAction("TYPE", mkPropertyValues({"TEXT": "test"})) + + # now open the dialog again and read the properties, Record Changes checkbox is disabled now + with self.ui_test.execute_dialog_through_command(".uno:SetDocumentProperties", close_button="cancel") as xDialog: + xRecordChangesCheckbox = xDialog.getChild("recordchanges") + self.assertEqual(get_state_as_dict(xRecordChangesCheckbox)["Selected"], "false") + xResetBtn = xDialog.getChild("reset") + xResetBtn.executeAction("CLICK", tuple()) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/sw/source/uibase/uiview/view2.cxx b/sw/source/uibase/uiview/view2.cxx index 2cb38325ba67..970064c6c164 100644 --- a/sw/source/uibase/uiview/view2.cxx +++ b/sw/source/uibase/uiview/view2.cxx @@ -117,6 +117,7 @@ #include <reffld.hxx> #include <comphelper/lok.hxx> #include <comphelper/string.hxx> +#include <comphelper/docpasswordhelper.hxx> #include <PostItMgr.hxx> @@ -682,34 +683,48 @@ void SwView::Execute(SfxRequest &rReq) { OSL_ENSURE( !static_cast<const SfxBoolItem*>(pItem)->GetValue(), "SwView::Execute(): password set and redlining off doesn't match!" ); - // dummy password from OOXML import: only confirmation dialog + // xmlsec05: new password dialog + SfxPasswordDialog aPasswdDlg(GetFrameWeld()); + aPasswdDlg.SetMinLen(1); + //#i69751# the result of Execute() can be ignored + (void)aPasswdDlg.run(); + OUString sNewPasswd(aPasswdDlg.GetPassword()); + + // password verification + bool bPasswordOk = false; if (aPasswd.getLength() == 1 && aPasswd[0] == 1) { - std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(m_pWrtShell->GetView().GetFrameWeld(), - VclMessageType::Warning, VclButtonsType::YesNo, - SfxResId(RID_SVXSTR_END_REDLINING_WARNING))); - xWarn->set_default_response(RET_NO); - if (xWarn->run() == RET_YES) - rIDRA.SetRedlinePassword(Sequence <sal_Int8> ()); - else - break; + // dummy RedlinePassword from OOXML import: get real password info + // from the grab-bag to verify the password + const css::uno::Sequence< css::beans::PropertyValue > aDocumentProtection = + static_cast<SfxObjectShell*>(GetDocShell())-> + GetDocumentProtectionFromGrabBag(); + + bPasswordOk = + // password is ok, if there is no DocumentProtection in the GrabBag, + // i.e. the dummy RedlinePassword imported from an OpenDocument file + !aDocumentProtection.hasElements() || + // verify password with the password info imported from OOXML + ::comphelper::DocPasswordHelper::IsModifyPasswordCorrect(sNewPasswd, + ::comphelper::DocPasswordHelper::ConvertPasswordInfo ( aDocumentProtection ) ); } else { - // xmlsec05: new password dialog - SfxPasswordDialog aPasswdDlg(GetFrameWeld()); - aPasswdDlg.SetMinLen(1); - //#i69751# the result of Execute() can be ignored - (void)aPasswdDlg.run(); - OUString sNewPasswd(aPasswdDlg.GetPassword()); + // the simplified RedlinePassword Sequence <sal_Int8> aNewPasswd = rIDRA.GetRedlinePassword(); SvPasswordHelper::GetHashPassword( aNewPasswd, sNewPasswd ); - if(SvPasswordHelper::CompareHashPassword(aPasswd, sNewPasswd)) - rIDRA.SetRedlinePassword(Sequence <sal_Int8> ()); - else - { // xmlsec05: message box for wrong password - break; - } + bPasswordOk = SvPasswordHelper::CompareHashPassword(aPasswd, sNewPasswd); + } + + if (bPasswordOk) + rIDRA.SetRedlinePassword(Sequence <sal_Int8> ()); + else + { // xmlsec05: message box for wrong password + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Info, VclButtonsType::Ok, + SfxResId(RID_SVXSTR_INCORRECT_PASSWORD))); + xInfoBox->run(); + break; } } |