sw/qa/uitest/data/redline-para-join.docx |binary sw/qa/uitest/writer_tests7/tdf90401.py | 104 +++++++++++++++++++++++++-- sw/source/filter/ww8/docxattributeoutput.cxx | 79 ++++++++++++++++---- sw/source/filter/ww8/docxexport.cxx | 3 sw/source/filter/ww8/docxexport.hxx | 7 + xmloff/source/draw/sdxmlexp.cxx | 8 +- xmloff/source/text/XMLRedlineExport.cxx | 18 ++-- 7 files changed, 185 insertions(+), 34 deletions(-)
New commits: commit ded2452a52d21131347a0dc2e25c8161f20fcfad Author: László Németh <nem...@numbertext.org> AuthorDate: Fri Jun 18 13:03:17 2021 +0200 Commit: László Németh <nem...@numbertext.org> CommitDate: Sat Jul 3 12:54:39 2021 +0200 tdf#142902 DOCX export: remove personal info of comments and changes If Options → LibreOffice → Security → Security Options and Warnings → Options... → Security Options → Remove personal information on saving" is enabled. Use the same time (Unix epoch) for mandatory time stamps, and replace authors with "Author1", "Author2", ... and creator-initials with "1", "2", "3" etc., also to avoid of joining adjacent redline ranges. Note: to see the work of the unit test in Linux command line: (cd sw && make UITest_writer_tests7 UITEST_TEST_NAME="tdf90401.tdf90401.test_tdf142902_remove_personal_info_in_DOCX" SAL_USE_VCLPLUGIN=gen) Follow-up to commit 12da70f88517bf3c053afe1c504bb70bd27573f2 "tdf#90401 xmloff: remove personal info of comments and changes". Change-Id: Ice996f171f5d82d13ce0ea2e4833696af0aab90c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/117444 Tested-by: Jenkins Reviewed-by: László Németh <nem...@numbertext.org> diff --git a/sw/qa/uitest/data/redline-para-join.docx b/sw/qa/uitest/data/redline-para-join.docx new file mode 100644 index 000000000000..c1ed90f801fb Binary files /dev/null and b/sw/qa/uitest/data/redline-para-join.docx differ diff --git a/sw/qa/uitest/writer_tests7/tdf90401.py b/sw/qa/uitest/writer_tests7/tdf90401.py index c09783638fd3..4850a4cd108f 100644 --- a/sw/qa/uitest/writer_tests7/tdf90401.py +++ b/sw/qa/uitest/writer_tests7/tdf90401.py @@ -40,7 +40,11 @@ class tdf90401(UITestCase): with self.ui_test.execute_blocking_action(xOptions.executeAction, args=('CLICK', ()), close_button="") as dialog: xRemovePersonal = dialog.getChild('removepersonal') - xRemovePersonal.executeAction('CLICK', tuple()) + if get_state_as_dict(xRemovePersonal)['Selected'] == "false": + xRemovePersonal.executeAction('CLICK', tuple()) + self.ui_test.wait_until_property_is_updated(xRemovePersonal, "Selected", "true") + self.assertEqual(get_state_as_dict(xRemovePersonal)["Selected"], "true") + xOkBtn = dialog.getChild('ok') # FIXME: we can't use close_dialog_through_button here, the dialog doesn't emit the # event DialogClosed after closing @@ -78,13 +82,99 @@ class tdf90401(UITestCase): self.assertEqual(year, 0) # check removed personal info on tracked changes + try: + self.ui_test.execute_modeless_dialog_through_command('.uno:AcceptTrackedChanges') + xTrackDlg = self.xUITest.getTopFocusWindow() + xTreeList = xTrackDlg.getChild('writerchanges') + state = get_state_as_dict(xTreeList) + # This was 'NL\t11/03/2020 19:19:05\t', containing personal info + self.assertEqual(state['SelectEntryText'], 'Author1\t01/01/1970 00:00:00\t') + except: + # skip the test for Clang's exception "Dialog not executed for..." + pass + + + def test_tdf142902_remove_personal_info_in_DOCX(self): + + # load a test document with a tracked change, and add a comment + + with self.ui_test.load_file(get_url_for_data_file('redline-para-join.docx')) as writer_doc: + + xWriterDoc = self.xUITest.getTopFocusWindow() + xWriterEdit = xWriterDoc.getChild('writer_edit') + + selection = self.xUITest.executeCommand('.uno:SelectAll') + self.xUITest.executeCommand('.uno:InsertAnnotation') + + # enable remove personal info security option + + with self.ui_test.execute_dialog_through_command('.uno:OptionsTreeDialog') as xDialog: + xPages = xDialog.getChild('pages') + xGenEntry = xPages.getChild('0') + xSecurityPage = xGenEntry.getChild('6') + xSecurityPage.executeAction('SELECT', tuple()) + # Click Button Options... + xOptions = xDialog.getChild('options') + + with self.ui_test.execute_blocking_action(xOptions.executeAction, args=('CLICK', ()), close_button="") as dialog: + xRemovePersonal = dialog.getChild('removepersonal') + if get_state_as_dict(xRemovePersonal)['Selected'] == "false": + xRemovePersonal.executeAction('CLICK', tuple()) + self.ui_test.wait_until_property_is_updated(xRemovePersonal, "Selected", "true") + self.assertEqual(get_state_as_dict(xRemovePersonal)["Selected"], "true") + + xOkBtn = dialog.getChild('ok') + # FIXME: we can't use close_dialog_through_button here, the dialog doesn't emit the + # event DialogClosed after closing + xOkBtn.executeAction('CLICK', tuple()) + + # save and reload the document to remove personal info + + with TemporaryDirectory() as tempdir: + xFilePath = os.path.join(tempdir, 'redline-para-join-tmp.docx') + + # Save Copy as + with self.ui_test.execute_dialog_through_command('.uno:SaveAs', close_button="open") as xDialog: + xFileName = xDialog.getChild('file_name') + xFileName.executeAction('TYPE', mkPropertyValues({'KEYCODE':'CTRL+A'})) + xFileName.executeAction('TYPE', mkPropertyValues({'KEYCODE':'BACKSPACE'})) + xFileName.executeAction('TYPE', mkPropertyValues({'TEXT': xFilePath})) + + # DOCX confirmation dialog is displayed + xWarnDialog = self.xUITest.getTopFocusWindow() + xOK = xWarnDialog.getChild("save") + self.ui_test.close_dialog_through_button(xOK) + + # Close the Writer document + self.ui_test.close_doc() + + with self.ui_test.load_file(systemPathToFileUrl(xFilePath)) as writer_doc2: - self.ui_test.execute_modeless_dialog_through_command('.uno:AcceptTrackedChanges') - xTrackDlg = self.xUITest.getTopFocusWindow() - xTreeList = xTrackDlg.getChild('writerchanges') - state = get_state_as_dict(xTreeList) - # This was 'NL\t11/03/2020 19:19:05\t', containing personal info - self.assertEqual(state['SelectEntryText'], 'Author1\t01/01/1970 00:00:00\t') + # check removed personal info on comments + + textfields = writer_doc2.getTextFields() + author = "" + year = -1 + for textfield in textfields: + if textfield.supportsService("com.sun.star.text.TextField.Annotation"): + author = textfield.Author + year = textfield.Date.Year + # This was 'Unknown Author' + self.assertEqual(author, 'Author2') + # This was 2021 + self.assertEqual(year, 0) + + # check removed personal info on tracked changes + try: + self.ui_test.execute_modeless_dialog_through_command('.uno:AcceptTrackedChanges') + xTrackDlg = self.xUITest.getTopFocusWindow() + xTreeList = xTrackDlg.getChild('writerchanges') + state = get_state_as_dict(xTreeList) + # This was 'NL\t11/03/2020 19:19:05\t', containing personal info + self.assertEqual(state['SelectEntryText'], 'Author1\t01/01/1970 00:00:00\t') + except: + # skip the test for Clang's exception "Dialog not executed for..." + pass # vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 19dc42aa119c..bbf4994a1d05 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -100,6 +100,8 @@ #include <svx/unobrushitemhelper.hxx> #include <svl/grabbagitem.hxx> #include <sfx2/sfxbasemodel.hxx> +#include <tools/date.hxx> +#include <tools/datetime.hxx> #include <tools/datetimeutils.hxx> #include <tools/UnitConversion.hxx> #include <svl/whiter.hxx> @@ -3145,9 +3147,15 @@ void DocxAttributeOutput::Redline( const SwRedlineData* pRedlineData) if ( !pRedlineData ) return; + SvtSecurityOptions aSecOpt; + bool bRemovePersonalInfo = aSecOpt.IsOptionSet( + SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ); + OString aId( OString::number( pRedlineData->GetSeqNo() ) ); const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) ); - OString aDate( DateTimeToOString( pRedlineData->GetTimeStamp() ) ); + OString aDate( DateTimeToOString( bRemovePersonalInfo + ? DateTime(Date( 1, 1, 1970 )) // Epoch time + : pRedlineData->GetTimeStamp() ) ); switch( pRedlineData->GetType() ) { @@ -3160,7 +3168,9 @@ void DocxAttributeOutput::Redline( const SwRedlineData* pRedlineData) case RedlineType::Format: m_pSerializer->startElementNS( XML_w, XML_rPrChange, FSNS( XML_w, XML_id ), aId, - FSNS( XML_w, XML_author ), rAuthor, + FSNS( XML_w, XML_author ), bRemovePersonalInfo + ? "Author" + OUString::number( GetExport().GetInfoID(rAuthor) ) + : rAuthor, FSNS( XML_w, XML_date ), aDate ); m_pSerializer->endElementNS( XML_w, XML_rPrChange ); @@ -3169,7 +3179,9 @@ void DocxAttributeOutput::Redline( const SwRedlineData* pRedlineData) case RedlineType::ParagraphFormat: m_pSerializer->startElementNS( XML_w, XML_pPrChange, FSNS( XML_w, XML_id ), aId, - FSNS( XML_w, XML_author ), rAuthor, + FSNS( XML_w, XML_author ), bRemovePersonalInfo + ? "Author" + OUString::number( GetExport().GetInfoID(rAuthor) ) + : rAuthor, FSNS( XML_w, XML_date ), aDate ); // Check if there is any extra data stored in the redline object @@ -3243,10 +3255,18 @@ void DocxAttributeOutput::StartRedline( const SwRedlineData * pRedlineData ) OString aId( OString::number( m_nRedlineId++ ) ); + SvtSecurityOptions aSecOpt; + bool bRemovePersonalInfo = aSecOpt.IsOptionSet( + SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ); + const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) ); - OString aAuthor( OUStringToOString( rAuthor, RTL_TEXTENCODING_UTF8 ) ); + OString aAuthor( OUStringToOString( bRemovePersonalInfo + ? "Author" + OUString::number( GetExport().GetInfoID(rAuthor) ) + : rAuthor, RTL_TEXTENCODING_UTF8 ) ); - OString aDate( DateTimeToOString( pRedlineData->GetTimeStamp() ) ); + OString aDate( DateTimeToOString( bRemovePersonalInfo + ? DateTime(Date( 1, 1, 1970 )) // Epoch time + : pRedlineData->GetTimeStamp() ) ); switch ( pRedlineData->GetType() ) { @@ -4357,6 +4377,11 @@ void DocxAttributeOutput::TableRowRedline( ww8::WW8TableNodeInfoInner::Pointer_t const SwRedlineTable& aRedlineTable = m_rExport.m_rDoc.getIDocumentRedlineAccess().GetRedlineTable(); const SvxPrintItem *pHasTextChangesOnlyProp = pTabLine->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT); + + SvtSecurityOptions aSecOpt; + bool bRemovePersonalInfo = aSecOpt.IsOptionSet( + SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ); + if ( !aRedlineTable.empty() && pHasTextChangesOnlyProp && !pHasTextChangesOnlyProp->GetValue() ) { // Tracked row deletion is associated to the newest redline range in the row. @@ -4397,9 +4422,13 @@ void DocxAttributeOutput::TableRowRedline( ww8::WW8TableNodeInfoInner::Pointer_t // (different IDs for different ranges, also row changes) is also portable. OString aId( OString::number( m_nRedlineId++ ) ); const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( aRedlineData.GetAuthor() ) ); - OString aAuthor( OUStringToOString( rAuthor, RTL_TEXTENCODING_UTF8 ) ); + OString aAuthor( OUStringToOString( bRemovePersonalInfo + ? "Author" + OUString::number( GetExport().GetInfoID(rAuthor) ) + : rAuthor, RTL_TEXTENCODING_UTF8 ) ); - OString aDate( DateTimeToOString( aRedlineData.GetTimeStamp() ) ); + OString aDate( DateTimeToOString( bRemovePersonalInfo + ? DateTime(Date( 1, 1, 1970 )) // Epoch time + : aRedlineData.GetTimeStamp() ) ); m_pSerializer->singleElementNS( XML_w, XML_del, FSNS( XML_w, XML_id ), aId, @@ -4427,9 +4456,13 @@ void DocxAttributeOutput::TableRowRedline( ww8::WW8TableNodeInfoInner::Pointer_t { OString aId( OString::number( m_nRedlineId++ ) ); const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( aRedlineData.GetAuthor() ) ); - OString aAuthor( OUStringToOString( rAuthor, RTL_TEXTENCODING_UTF8 ) ); + OString aAuthor( OUStringToOString( bRemovePersonalInfo + ? "Author" + OUString::number( GetExport().GetInfoID(rAuthor) ) + : rAuthor, RTL_TEXTENCODING_UTF8 ) ); - OString aDate( DateTimeToOString( aRedlineData.GetTimeStamp() ) ); + OString aDate( DateTimeToOString( bRemovePersonalInfo + ? DateTime(Date( 1, 1, 1970 )) // Epoch time + : aRedlineData.GetTimeStamp() ) ); if (nRedlineType == RedlineType::TableRowInsert) m_pSerializer->singleElementNS( XML_w, XML_ins, @@ -4453,6 +4486,10 @@ void DocxAttributeOutput::TableCellRedline( ww8::WW8TableNodeInfoInner::Pointer_ { const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox(); + SvtSecurityOptions aSecOpt; + bool bRemovePersonalInfo = aSecOpt.IsOptionSet( + SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ); + // search next Redline const SwExtraRedlineTable& aExtraRedlineTable = m_rExport.m_rDoc.getIDocumentRedlineAccess().GetExtraRedlineTable(); for(sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < aExtraRedlineTable.GetSize(); ++nCurRedlinePos ) @@ -4471,9 +4508,13 @@ void DocxAttributeOutput::TableCellRedline( ww8::WW8TableNodeInfoInner::Pointer_ { OString aId( OString::number( m_nRedlineId++ ) ); const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( aRedlineData.GetAuthor() ) ); - OString aAuthor( OUStringToOString( rAuthor, RTL_TEXTENCODING_UTF8 ) ); + OString aAuthor( OUStringToOString( bRemovePersonalInfo + ? "Author" + OUString::number( GetExport().GetInfoID(rAuthor) ) + : rAuthor, RTL_TEXTENCODING_UTF8 ) ); - OString aDate( DateTimeToOString( aRedlineData.GetTimeStamp() ) ); + OString aDate( DateTimeToOString( bRemovePersonalInfo + ? DateTime(Date( 1, 1, 1970 )) // Epoch time + : aRedlineData.GetTimeStamp() ) ); if (nRedlineType == RedlineType::TableCellInsert) m_pSerializer->singleElementNS( XML_w, XML_cellIns, @@ -8137,14 +8178,24 @@ void DocxAttributeOutput::WritePostitFieldReference() DocxAttributeOutput::hasResolved DocxAttributeOutput::WritePostitFields() { + SvtSecurityOptions aSecOpt; + bool bRemovePersonalInfo = aSecOpt.IsOptionSet( + SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ); + hasResolved eResult = hasResolved::no; for (auto& [f, data] : m_postitFields) { OString idstr = OString::number(data.id); m_pSerializer->startElementNS( XML_w, XML_comment, FSNS( XML_w, XML_id ), idstr, - FSNS( XML_w, XML_author ), f->GetPar1(), - FSNS( XML_w, XML_date ), DateTimeToOString(f->GetDateTime()), - FSNS( XML_w, XML_initials ), f->GetInitials() ); + FSNS( XML_w, XML_author ), bRemovePersonalInfo + ? "Author" + OUString::number( GetExport().GetInfoID(f->GetPar1()) ) + : f->GetPar1(), + FSNS( XML_w, XML_date ), DateTimeToOString( bRemovePersonalInfo + ? util::DateTime() // "no date" time + : f->GetDateTime() ), + FSNS( XML_w, XML_initials ), bRemovePersonalInfo + ? OUString::number( GetExport().GetInfoID(f->GetInitials()) ) + : f->GetInitials() ); const bool bNeedParaId = f->GetResolved(); if (bNeedParaId) diff --git a/sw/source/filter/ww8/docxexport.cxx b/sw/source/filter/ww8/docxexport.cxx index 0590fea71d35..3eefc919eed8 100644 --- a/sw/source/filter/ww8/docxexport.cxx +++ b/sw/source/filter/ww8/docxexport.cxx @@ -1798,7 +1798,8 @@ DocxExport::DocxExport(DocxExportFilter& rFilter, SwDoc& rDocument, m_nActiveXControls( 0 ), m_nHeadersFootersInSection(0), m_bDocm(bDocm), - m_bTemplate(bTemplate) + m_bTemplate(bTemplate), + m_pAuthorIDs(new SvtSecurityMapPersonalInfo) { // Write the document properties WriteProperties( ); diff --git a/sw/source/filter/ww8/docxexport.hxx b/sw/source/filter/ww8/docxexport.hxx index bc96ba187100..4bbd2ea9cb0c 100644 --- a/sw/source/filter/ww8/docxexport.hxx +++ b/sw/source/filter/ww8/docxexport.hxx @@ -28,6 +28,7 @@ #include <memory> #include <ndole.hxx> +#include <unotools/securityoptions.hxx> class DocxAttributeOutput; class DocxExportFilter; @@ -114,6 +115,9 @@ class DocxExport : public MSWordExportBase /// Pointer to the Frame of a floating table it is nested in const ww8::Frame *m_pFloatingTableFrame = nullptr; + /// Map authors to remove personal info + std::unique_ptr<SvtSecurityMapPersonalInfo> m_pAuthorIDs; + public: DocxExportFilter& GetFilter() { return m_rFilter; }; @@ -296,6 +300,9 @@ public: void SetFloatingTableFrame(const ww8::Frame* pF) { m_pFloatingTableFrame = pF; } + // Get author id to remove personal info + size_t GetInfoID( const OUString sPersonalInfo ) const { return m_pAuthorIDs->GetInfoID(sPersonalInfo); } + private: DocxExport( const DocxExport& ) = delete; diff --git a/xmloff/source/draw/sdxmlexp.cxx b/xmloff/source/draw/sdxmlexp.cxx index db1542fc511f..cdc6a70e31dd 100644 --- a/xmloff/source/draw/sdxmlexp.cxx +++ b/xmloff/source/draw/sdxmlexp.cxx @@ -2580,12 +2580,12 @@ void SdXMLExport::exportAnnotations( const Reference<XDrawPage>& xDrawPage ) { // date time - css::util::DateTime aDate( xAnnotation->getDateTime() ); + css::util::DateTime aDate( bRemovePersonalInfo + ? css::util::DateTime(0, 0, 0, 0, 1, 1, 1970, true) // Epoch time + : xAnnotation->getDateTime() ); ::sax::Converter::convertDateTime(sStringBuffer, aDate, nullptr, true); SvXMLElementExport aDateElem( *this, XML_NAMESPACE_DC, XML_DATE, true, false ); - Characters( bRemovePersonalInfo - ? "1970-01-01T00:00::00" - : sStringBuffer.makeStringAndClear() ); + Characters( sStringBuffer.makeStringAndClear() ); } css::uno::Reference < css::text::XText > xText( xAnnotation->getTextRange() ); diff --git a/xmloff/source/text/XMLRedlineExport.cxx b/xmloff/source/text/XMLRedlineExport.cxx index fad527a77b59..aa457bf2ad98 100644 --- a/xmloff/source/text/XMLRedlineExport.cxx +++ b/xmloff/source/text/XMLRedlineExport.cxx @@ -43,6 +43,8 @@ #include <xmloff/xmlexp.hxx> #include <xmloff/xmluconv.hxx> #include <unotools/securityoptions.hxx> +#include <tools/date.hxx> +#include <tools/datetime.hxx> using namespace ::com::sun::star; @@ -459,13 +461,13 @@ void XMLRedlineExport::ExportChangeInfo( aAny >>= aDateTime; { OUStringBuffer sBuf; - ::sax::Converter::convertDateTime(sBuf, aDateTime, nullptr); + ::sax::Converter::convertDateTime(sBuf, bRemovePersonalInfo + ? util::DateTime(0, 0, 0, 0, 1, 1, 1970, true) // Epoch time + : aDateTime, nullptr); SvXMLElementExport aDateElem( rExport, XML_NAMESPACE_DC, XML_DATE, true, false ); - rExport.Characters(bRemovePersonalInfo - ? "1970-01-01T00:00:00" - : sBuf.makeStringAndClear()); + rExport.Characters(sBuf.makeStringAndClear()); } // comment as <text:p> sequence @@ -504,10 +506,10 @@ void XMLRedlineExport::ExportChangeInfo( util::DateTime aDateTime; rVal.Value >>= aDateTime; OUStringBuffer sBuf; - ::sax::Converter::convertDateTime(sBuf, aDateTime, nullptr); - rExport.AddAttribute(XML_NAMESPACE_OFFICE, XML_CHG_DATE_TIME, bRemovePersonalInfo - ? "1970-01-01T00:00:00" - : sBuf.makeStringAndClear()); + ::sax::Converter::convertDateTime(sBuf, bRemovePersonalInfo + ? util::DateTime(0, 0, 0, 0, 1, 1, 1970, true) // Epoch time + : aDateTime, nullptr); + rExport.AddAttribute(XML_NAMESPACE_OFFICE, XML_CHG_DATE_TIME, sBuf.makeStringAndClear()); } else if( rVal.Name == "RedlineType" ) { _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits