1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20 #include <sfx2/htmlmode.hxx>
21
22 #include <sfx2/sfxresid.hxx>
23
24 #include <sfx2/sfxsids.hrc>
25 #include <sfx2/objsh.hxx>
26 #include <sfx2/viewsh.hxx>
27 #include <sfx2/dispatch.hxx>
28 #include <sfx2/passwd.hxx>
29
30 #include <vcl/svapp.hxx>
31 #include <vcl/weld.hxx>
32 #include <svl/eitem.hxx>
33 #include <svl/poolitem.hxx>
34 #include <svl/intitem.hxx>
35 #include <svl/PasswordHelper.hxx>
36 #include <comphelper/docpasswordhelper.hxx>
37
38 #include <sfx2/strings.hrc>
39
40 #include "securitypage.hxx"
41
42 using namespace ::com::sun::star;
43
44 namespace
45 {
46 enum RedliningMode { RL_NONE, RL_WRITER, RL_CALC };
47
QueryState(TypedWhichId<SfxBoolItem> _nSlot,bool & _rValue)48 bool QueryState( TypedWhichId<SfxBoolItem> _nSlot, bool& _rValue )
49 {
50 bool bRet = false;
51 SfxViewShell* pViewSh = SfxViewShell::Current();
52 if (pViewSh)
53 {
54 SfxPoolItemHolder aResult;
55 const SfxItemState nState(pViewSh->GetDispatcher()->QueryState(_nSlot, aResult));
56 bRet = SfxItemState::DEFAULT <= nState;
57 if (bRet)
58 _rValue = static_cast<const SfxBoolItem*>(aResult.getItem())->GetValue();
59 }
60 return bRet;
61 }
62
63
QueryRecordChangesProtectionState(RedliningMode _eMode,bool & _rValue)64 bool QueryRecordChangesProtectionState( RedliningMode _eMode, bool& _rValue )
65 {
66 bool bRet = false;
67 if (_eMode != RL_NONE)
68 {
69 TypedWhichId<SfxBoolItem> nSlot = _eMode == RL_WRITER ? FN_REDLINE_PROTECT : SID_CHG_PROTECT;
70 bRet = QueryState( nSlot, _rValue );
71 }
72 return bRet;
73 }
74
75
QueryRecordChangesState(RedliningMode _eMode,bool & _rValue)76 bool QueryRecordChangesState( RedliningMode _eMode, bool& _rValue )
77 {
78 bool bRet = false;
79 if (_eMode != RL_NONE)
80 {
81 TypedWhichId<SfxBoolItem> nSlot = _eMode == RL_WRITER ? FN_REDLINE_ON : FID_CHG_RECORD;
82 bRet = QueryState( nSlot, _rValue );
83 }
84 return bRet;
85 }
86 }
87
88
lcl_GetPassword(weld::Window * pParent,bool bProtect,OUString & rPassword)89 static bool lcl_GetPassword(
90 weld::Window *pParent,
91 bool bProtect,
92 /*out*/OUString &rPassword )
93 {
94 bool bRes = false;
95 SfxPasswordDialog aPasswdDlg(pParent);
96 aPasswdDlg.SetMinLen(1);
97 if (bProtect)
98 aPasswdDlg.ShowExtras( SfxShowExtras::CONFIRM );
99 if (RET_OK == aPasswdDlg.run() && !aPasswdDlg.GetPassword().isEmpty())
100 {
101 rPassword = aPasswdDlg.GetPassword();
102 bRes = true;
103 }
104 return bRes;
105 }
106
107
lcl_IsPasswordCorrect(weld::Window * pParent,std::u16string_view rPassword)108 static bool lcl_IsPasswordCorrect(weld::Window *pParent, std::u16string_view rPassword)
109 {
110 SfxObjectShell* pCurDocShell = SfxObjectShell::Current();
111 if (!pCurDocShell)
112 return false;
113
114 bool bRes = false;
115 uno::Sequence< sal_Int8 > aPasswordHash;
116 pCurDocShell->GetProtectionHash( aPasswordHash );
117
118 // check if supplied password was correct
119 if (aPasswordHash.getLength() == 1 && aPasswordHash[0] == 1)
120 {
121 // dummy RedlinePassword from OOXML import: get real password info
122 // from the grab-bag to verify the password
123 const css::uno::Sequence< css::beans::PropertyValue > aDocumentProtection =
124 pCurDocShell->GetDocumentProtectionFromGrabBag();
125 bRes =
126 // password is ok, if there is no DocumentProtection in the GrabBag,
127 // i.e. the dummy RedlinePassword imported from an OpenDocument file
128 !aDocumentProtection.hasElements() ||
129 // verify password with the password info imported from OOXML
130 ::comphelper::DocPasswordHelper::IsModifyPasswordCorrect( rPassword,
131 ::comphelper::DocPasswordHelper::ConvertPasswordInfo ( aDocumentProtection ) );
132 }
133 else
134 {
135 uno::Sequence< sal_Int8 > aNewPasswd( aPasswordHash );
136 SvPasswordHelper::GetHashPassword( aNewPasswd, rPassword );
137 bRes = SvPasswordHelper::CompareHashPassword( aPasswordHash, rPassword );
138 }
139
140 if ( !bRes )
141 {
142 std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pParent,
143 VclMessageType::Info, VclButtonsType::Ok,
144 SfxResId(RID_SVXSTR_INCORRECT_PASSWORD)));
145 xInfoBox->run();
146 }
147
148 return bRes;
149 }
150
151 struct SfxSecurityPage_Impl
152 {
153 SfxSecurityPage & m_rMyTabPage;
154
155 RedliningMode m_eRedlingMode; // for record changes
156
157 bool m_bOrigPasswordIsConfirmed;
158 bool m_bNewPasswordIsValid;
159 OUString m_aNewPassword;
160
161 OUString m_aEndRedliningWarning;
162 bool m_bEndRedliningWarningDone;
163
164 std::unique_ptr<weld::CheckButton> m_xOpenReadonlyCB;
165 std::unique_ptr<weld::CheckButton> m_xRecordChangesCB; // for record changes
166 std::unique_ptr<weld::Button> m_xProtectPB; // for record changes
167 std::unique_ptr<weld::Button> m_xUnProtectPB; // for record changes
168
169 DECL_LINK(RecordChangesCBToggleHdl, weld::Toggleable&, void);
170 DECL_LINK(ChangeProtectionPBHdl, weld::Button&, void);
171
172 SfxSecurityPage_Impl( SfxSecurityPage &rDlg );
173
174 bool FillItemSet_Impl();
175 void Reset_Impl();
176 };
177
SfxSecurityPage_Impl(SfxSecurityPage & rTabPage)178 SfxSecurityPage_Impl::SfxSecurityPage_Impl(SfxSecurityPage &rTabPage)
179 : m_rMyTabPage(rTabPage)
180 , m_eRedlingMode(RL_NONE)
181 , m_bOrigPasswordIsConfirmed(false)
182 , m_bNewPasswordIsValid(false)
183 , m_aEndRedliningWarning(SfxResId(RID_SVXSTR_END_REDLINING_WARNING))
184 , m_bEndRedliningWarningDone(false)
185 , m_xOpenReadonlyCB(rTabPage.GetBuilder().weld_check_button(u"readonly"_ustr))
186 , m_xRecordChangesCB(rTabPage.GetBuilder().weld_check_button(u"recordchanges"_ustr))
187 , m_xProtectPB(rTabPage.GetBuilder().weld_button(u"protect"_ustr))
188 , m_xUnProtectPB(rTabPage.GetBuilder().weld_button(u"unprotect"_ustr))
189 {
190 m_xProtectPB->show();
191 m_xUnProtectPB->hide();
192
193 m_xRecordChangesCB->connect_toggled(LINK(this, SfxSecurityPage_Impl, RecordChangesCBToggleHdl));
194 m_xProtectPB->connect_clicked(LINK(this, SfxSecurityPage_Impl, ChangeProtectionPBHdl));
195 m_xUnProtectPB->connect_clicked(LINK(this, SfxSecurityPage_Impl, ChangeProtectionPBHdl));
196 }
197
FillItemSet_Impl()198 bool SfxSecurityPage_Impl::FillItemSet_Impl()
199 {
200 bool bModified = false;
201
202 SfxObjectShell* pCurDocShell = SfxObjectShell::Current();
203 if (pCurDocShell && !pCurDocShell->IsReadOnly())
204 {
205 if (m_eRedlingMode != RL_NONE )
206 {
207 const bool bDoRecordChanges = m_xRecordChangesCB->get_active();
208 const bool bDoChangeProtection = m_xUnProtectPB->get_visible();
209
210 // sanity checks
211 DBG_ASSERT( bDoRecordChanges || !bDoChangeProtection, "no change recording should imply no change protection" );
212 DBG_ASSERT( bDoChangeProtection || !bDoRecordChanges, "no change protection should imply no change recording" );
213 DBG_ASSERT( !bDoChangeProtection || !m_aNewPassword.isEmpty(), "change protection should imply password length is > 0" );
214 DBG_ASSERT( bDoChangeProtection || m_aNewPassword.isEmpty(), "no change protection should imply password length is 0" );
215
216 // change recording
217 if (bDoRecordChanges != pCurDocShell->IsChangeRecording())
218 {
219 pCurDocShell->SetChangeRecording( bDoRecordChanges );
220 bModified = true;
221 }
222
223 // change record protection
224 if (m_bNewPasswordIsValid &&
225 bDoChangeProtection != pCurDocShell->HasChangeRecordProtection())
226 {
227 DBG_ASSERT( !bDoChangeProtection || bDoRecordChanges,
228 "change protection requires record changes to be active!" );
229 pCurDocShell->SetProtectionPassword( m_aNewPassword );
230 bModified = true;
231 }
232 }
233
234 // open read-only?
235 const bool bDoOpenReadonly = m_xOpenReadonlyCB->get_active();
236 if (bDoOpenReadonly != pCurDocShell->IsSecurityOptOpenReadOnly())
237 {
238 pCurDocShell->SetSecurityOptOpenReadOnly( bDoOpenReadonly );
239 bModified = true;
240 }
241 }
242
243 return bModified;
244 }
245
246
Reset_Impl()247 void SfxSecurityPage_Impl::Reset_Impl()
248 {
249 SfxObjectShell* pCurDocShell = SfxObjectShell::Current();
250
251 if (!pCurDocShell)
252 {
253 // no doc -> hide document settings
254 m_xOpenReadonlyCB->set_sensitive(false);
255 m_xRecordChangesCB->set_sensitive(false);
256 m_xProtectPB->show();
257 m_xProtectPB->set_sensitive(false);
258 m_xUnProtectPB->hide();
259 m_xUnProtectPB->set_sensitive(false);
260 }
261 else
262 {
263 bool bIsHTMLDoc = false;
264 bool bProtect = true, bUnProtect = false;
265 SfxViewShell* pViewSh = SfxViewShell::Current();
266 if (pViewSh)
267 {
268 SfxPoolItemHolder aResult;
269
270 if (SfxItemState::DEFAULT <= pViewSh->GetDispatcher()->QueryState(SID_HTML_MODE, aResult))
271 {
272 const SfxUInt16Item* pItem(static_cast<const SfxUInt16Item*>(aResult.getItem()));
273 const sal_uInt16 nMode(pItem->GetValue());
274 bIsHTMLDoc = ( ( nMode & HTMLMODE_ON ) != 0 );
275 }
276 }
277
278 bool bIsReadonly = pCurDocShell->IsReadOnly();
279 if (!bIsHTMLDoc)
280 {
281 m_xOpenReadonlyCB->set_active(pCurDocShell->IsSecurityOptOpenReadOnly());
282 m_xOpenReadonlyCB->set_sensitive(!bIsReadonly);
283 }
284 else
285 m_xOpenReadonlyCB->set_sensitive(false);
286
287 bool bRecordChanges;
288 if (QueryRecordChangesState( RL_WRITER, bRecordChanges ) && !bIsHTMLDoc)
289 m_eRedlingMode = RL_WRITER;
290 else if (QueryRecordChangesState( RL_CALC, bRecordChanges ))
291 m_eRedlingMode = RL_CALC;
292 else
293 m_eRedlingMode = RL_NONE;
294
295 if (m_eRedlingMode != RL_NONE)
296 {
297 bool bProtection(false);
298 QueryRecordChangesProtectionState( m_eRedlingMode, bProtection );
299
300 m_xProtectPB->set_sensitive(!bIsReadonly);
301 m_xUnProtectPB->set_sensitive(!bIsReadonly);
302 // set the right text
303 if (bProtection)
304 {
305 bProtect = false;
306 bUnProtect = true;
307 }
308
309 m_xRecordChangesCB->set_active(bRecordChanges);
310 m_xRecordChangesCB->set_sensitive(/*!bProtection && */!bIsReadonly);
311
312 m_bOrigPasswordIsConfirmed = true; // default case if no password is set
313 uno::Sequence< sal_Int8 > aPasswordHash;
314 // check if password is available
315 if (pCurDocShell->GetProtectionHash( aPasswordHash ) &&
316 aPasswordHash.hasElements())
317 m_bOrigPasswordIsConfirmed = false; // password found, needs to be confirmed later on
318 }
319 else
320 {
321 // A Calc document that is shared will have 'm_eRedlingMode == RL_NONE'
322 // In shared documents change recording and protection must be disabled,
323 // similar to documents that do not support change recording at all.
324 m_xRecordChangesCB->set_active(false);
325 m_xRecordChangesCB->set_sensitive(false);
326 m_xProtectPB->set_sensitive(false);
327 m_xUnProtectPB->set_sensitive(false);
328 }
329
330 m_xProtectPB->set_visible(bProtect);
331 m_xUnProtectPB->set_visible(bUnProtect);
332 }
333 }
334
IMPL_LINK_NOARG(SfxSecurityPage_Impl,RecordChangesCBToggleHdl,weld::Toggleable &,void)335 IMPL_LINK_NOARG(SfxSecurityPage_Impl, RecordChangesCBToggleHdl, weld::Toggleable&, void)
336 {
337 // when change recording gets disabled protection must be disabled as well
338 if (m_xRecordChangesCB->get_active()) // the new check state is already present, thus the '!'
339 return;
340
341 bool bAlreadyDone = false;
342 if (!m_bEndRedliningWarningDone)
343 {
344 std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(m_rMyTabPage.GetFrameWeld(),
345 VclMessageType::Warning, VclButtonsType::YesNo,
346 m_aEndRedliningWarning));
347 xWarn->set_default_response(RET_NO);
348 if (xWarn->run() != RET_YES)
349 bAlreadyDone = true;
350 else
351 m_bEndRedliningWarningDone = true;
352 }
353
354 const bool bNeedPassword = !m_bOrigPasswordIsConfirmed
355 && m_xUnProtectPB->get_visible(); // tdf#128230 Require password if the Unprotect button is visible
356 if (!bAlreadyDone && bNeedPassword)
357 {
358 OUString aPasswordText;
359
360 // dialog canceled or no password provided
361 if (!lcl_GetPassword( m_rMyTabPage.GetFrameWeld(), false, aPasswordText ))
362 bAlreadyDone = true;
363
364 // ask for password and if dialog is canceled or no password provided return
365 if (lcl_IsPasswordCorrect(m_rMyTabPage.GetFrameWeld(), aPasswordText))
366 m_bOrigPasswordIsConfirmed = true;
367 else
368 bAlreadyDone = true;
369 }
370
371 if (bAlreadyDone)
372 m_xRecordChangesCB->set_active(true); // restore original state
373 else
374 {
375 // remember required values to change protection and change recording in
376 // FillItemSet_Impl later on if password was correct.
377 m_bNewPasswordIsValid = true;
378 m_aNewPassword.clear();
379 m_xProtectPB->show();
380 m_xUnProtectPB->hide();
381 }
382 }
383
IMPL_LINK_NOARG(SfxSecurityPage_Impl,ChangeProtectionPBHdl,weld::Button &,void)384 IMPL_LINK_NOARG(SfxSecurityPage_Impl, ChangeProtectionPBHdl, weld::Button&, void)
385 {
386 if (m_eRedlingMode == RL_NONE)
387 return;
388
389 // the push button text is always the opposite of the current state. Thus:
390 const bool bCurrentProtection = m_xUnProtectPB->get_visible();
391
392 // ask user for password (if still necessary)
393 OUString aPasswordText;
394 bool bNewProtection = !bCurrentProtection;
395 const bool bNeedPassword = bNewProtection || !m_bOrigPasswordIsConfirmed;
396 if (bNeedPassword)
397 {
398 // ask for password and if dialog is canceled or no password provided return
399 if (!lcl_GetPassword(m_rMyTabPage.GetFrameWeld(), bNewProtection, aPasswordText))
400 return;
401
402 // provided password still needs to be checked?
403 if (!bNewProtection && !m_bOrigPasswordIsConfirmed)
404 {
405 if (lcl_IsPasswordCorrect(m_rMyTabPage.GetFrameWeld(), aPasswordText))
406 m_bOrigPasswordIsConfirmed = true;
407 else
408 return;
409 }
410 }
411 DBG_ASSERT( m_bOrigPasswordIsConfirmed, "ooops... this should not have happened!" );
412
413 // remember required values to change protection and change recording in
414 // FillItemSet_Impl later on if password was correct.
415 m_bNewPasswordIsValid = true;
416 m_aNewPassword = bNewProtection? aPasswordText : OUString();
417
418 m_xRecordChangesCB->set_active(bNewProtection);
419
420 m_xUnProtectPB->set_visible(bNewProtection);
421 m_xProtectPB->set_visible(!bNewProtection);
422 }
423
Create(weld::Container * pPage,weld::DialogController * pController,const SfxItemSet * rItemSet)424 std::unique_ptr<SfxTabPage> SfxSecurityPage::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet * rItemSet)
425 {
426 return std::make_unique<SfxSecurityPage>(pPage, pController, *rItemSet);
427 }
428
SfxSecurityPage(weld::Container * pPage,weld::DialogController * pController,const SfxItemSet & rItemSet)429 SfxSecurityPage::SfxSecurityPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rItemSet)
430 : SfxTabPage(pPage, pController, u"sfx/ui/securityinfopage.ui"_ustr, u"SecurityInfoPage"_ustr, &rItemSet)
431 {
432 m_pImpl.reset(new SfxSecurityPage_Impl( *this ));
433 }
434
FillItemSet(SfxItemSet *)435 bool SfxSecurityPage::FillItemSet( SfxItemSet * /*rItemSet*/ )
436 {
437 bool bModified = false;
438 DBG_ASSERT(m_pImpl, "implementation pointer is 0. Still in c-tor?");
439 if (m_pImpl != nullptr)
440 bModified = m_pImpl->FillItemSet_Impl();
441 return bModified;
442 }
443
Reset(const SfxItemSet *)444 void SfxSecurityPage::Reset( const SfxItemSet * /*rItemSet*/ )
445 {
446 DBG_ASSERT(m_pImpl, "implementation pointer is 0. Still in c-tor?");
447 if (m_pImpl != nullptr)
448 m_pImpl->Reset_Impl();
449 }
450
451 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
452