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
10 #include <sfx2/classificationhelper.hxx>
11
12 #include <map>
13 #include <algorithm>
14 #include <iterator>
15
16 #include <com/sun/star/beans/XPropertyContainer.hpp>
17 #include <com/sun/star/beans/Property.hpp>
18 #include <com/sun/star/beans/XPropertySet.hpp>
19 #include <com/sun/star/document/XDocumentProperties.hpp>
20 #include <com/sun/star/xml/sax/Parser.hpp>
21 #include <com/sun/star/xml/sax/XDocumentHandler.hpp>
22 #include <com/sun/star/xml/sax/SAXParseException.hpp>
23 #include <com/sun/star/beans/PropertyAttribute.hpp>
24
25 #include <sal/log.hxx>
26 #include <i18nlangtag/languagetag.hxx>
27 #include <sfx2/infobar.hxx>
28 #include <comphelper/processfactory.hxx>
29 #include <unotools/pathoptions.hxx>
30 #include <unotools/ucbstreamhelper.hxx>
31 #include <unotools/streamwrap.hxx>
32 #include <cppuhelper/implbase.hxx>
33 #include <sfx2/strings.hrc>
34 #include <sfx2/sfxresid.hxx>
35 #include <sfx2/viewfrm.hxx>
36 #include <tools/datetime.hxx>
37 #include <tools/diagnose_ex.h>
38 #include <unotools/datetime.hxx>
39 #include <vcl/svapp.hxx>
40 #include <vcl/settings.hxx>
41 #include <vcl/weld.hxx>
42 #include <svl/fstathelper.hxx>
43
44 #include <officecfg/Office/Common.hxx>
45
46 using namespace com::sun::star;
47
48 namespace
49 {
50
PROP_BACNAME()51 const OUString& PROP_BACNAME()
52 {
53 static const OUString sProp("BusinessAuthorizationCategory:Name");
54 return sProp;
55 }
56
PROP_STARTVALIDITY()57 const OUString& PROP_STARTVALIDITY()
58 {
59 static const OUString sProp("Authorization:StartValidity");
60 return sProp;
61 }
62
PROP_NONE()63 const OUString& PROP_NONE()
64 {
65 static const OUString sProp("None");
66 return sProp;
67 }
68
PROP_IMPACTSCALE()69 const OUString& PROP_IMPACTSCALE()
70 {
71 static const OUString sProp("Impact:Scale");
72 return sProp;
73 }
74
PROP_IMPACTLEVEL()75 const OUString& PROP_IMPACTLEVEL()
76 {
77 static const OUString sProp("Impact:Level:Confidentiality");
78 return sProp;
79 }
80
PROP_PREFIX_EXPORTCONTROL()81 const OUString& PROP_PREFIX_EXPORTCONTROL()
82 {
83 static const OUString sProp("urn:bails:ExportControl:");
84 return sProp;
85 }
86
PROP_PREFIX_NATIONALSECURITY()87 const OUString& PROP_PREFIX_NATIONALSECURITY()
88 {
89 static const OUString sProp("urn:bails:NationalSecurity:");
90 return sProp;
91 }
92
93 /// Represents one category of a classification policy.
94 class SfxClassificationCategory
95 {
96 public:
97 /// PROP_BACNAME() is stored separately for easier lookup.
98 OUString m_aName;
99 OUString m_aAbbreviatedName; //< An abbreviation to display instead of m_aName.
100 OUString m_aIdentifier; //< The Identifier of this entry.
101 size_t m_nConfidentiality; //< 0 is the lowest (least-sensitive).
102 std::map<OUString, OUString> m_aLabels;
103 };
104
105 /// Parses a policy XML conforming to the TSCP BAF schema.
106 class SfxClassificationParser : public cppu::WeakImplHelper<xml::sax::XDocumentHandler>
107 {
108 public:
109 std::vector<SfxClassificationCategory> m_aCategories;
110 std::vector<OUString> m_aMarkings;
111 std::vector<OUString> m_aIPParts;
112 std::vector<OUString> m_aIPPartNumbers;
113
114 OUString m_aPolicyAuthorityName;
115 bool m_bInPolicyAuthorityName = false;
116 OUString m_aPolicyName;
117 bool m_bInPolicyName = false;
118 OUString m_aProgramID;
119 bool m_bInProgramID = false;
120 OUString m_aScale;
121 bool m_bInScale = false;
122 OUString m_aConfidentalityValue;
123 bool m_bInConfidentalityValue = false;
124 OUString m_aIdentifier;
125 bool m_bInIdentifier = false;
126 OUString m_aValue;
127 bool m_bInValue = false;
128
129 /// Pointer to a value in m_aCategories, the currently parsed category.
130 SfxClassificationCategory* m_pCategory = nullptr;
131
132 SfxClassificationParser();
133
134 void SAL_CALL startDocument() override;
135
136 void SAL_CALL endDocument() override;
137
138 void SAL_CALL startElement(const OUString& rName, const uno::Reference<xml::sax::XAttributeList>& xAttribs) override;
139
140 void SAL_CALL endElement(const OUString& rName) override;
141
142 void SAL_CALL characters(const OUString& rChars) override;
143
144 void SAL_CALL ignorableWhitespace(const OUString& rWhitespaces) override;
145
146 void SAL_CALL processingInstruction(const OUString& rTarget, const OUString& rData) override;
147
148 void SAL_CALL setDocumentLocator(const uno::Reference<xml::sax::XLocator>& xLocator) override;
149 };
150
151 SfxClassificationParser::SfxClassificationParser() = default;
152
startDocument()153 void SAL_CALL SfxClassificationParser::startDocument()
154 {
155 }
156
endDocument()157 void SAL_CALL SfxClassificationParser::endDocument()
158 {
159 }
160
startElement(const OUString & rName,const uno::Reference<xml::sax::XAttributeList> & xAttribs)161 void SAL_CALL SfxClassificationParser::startElement(const OUString& rName, const uno::Reference<xml::sax::XAttributeList>& xAttribs)
162 {
163 if (rName == "baf:PolicyAuthorityName")
164 {
165 m_aPolicyAuthorityName.clear();
166 m_bInPolicyAuthorityName = true;
167 }
168 else if (rName == "baf:PolicyName")
169 {
170 m_aPolicyName.clear();
171 m_bInPolicyName = true;
172 }
173 else if (rName == "baf:ProgramID")
174 {
175 m_aProgramID.clear();
176 m_bInProgramID = true;
177 }
178 else if (rName == "baf:BusinessAuthorizationCategory")
179 {
180 const OUString aName = xAttribs->getValueByName("Name");
181 if (!m_pCategory && !aName.isEmpty())
182 {
183 OUString aIdentifier = xAttribs->getValueByName("Identifier");
184
185 // Create a new category and initialize it with the data that's true for all categories.
186 m_aCategories.emplace_back();
187 SfxClassificationCategory& rCategory = m_aCategories.back();
188
189 rCategory.m_aName = aName;
190 // Set the abbreviated name, if any, otherwise fallback on the full name.
191 const OUString aAbbreviatedName = xAttribs->getValueByName("loextAbbreviatedName");
192 rCategory.m_aAbbreviatedName = !aAbbreviatedName.isEmpty() ? aAbbreviatedName : aName;
193 rCategory.m_aIdentifier = aIdentifier;
194
195 rCategory.m_aLabels["PolicyAuthority:Name"] = m_aPolicyAuthorityName;
196 rCategory.m_aLabels["Policy:Name"] = m_aPolicyName;
197 rCategory.m_aLabels["BusinessAuthorization:Identifier"] = m_aProgramID;
198 rCategory.m_aLabels["BusinessAuthorizationCategory:Identifier"] = aIdentifier;
199
200 // Also initialize defaults.
201 rCategory.m_aLabels["PolicyAuthority:Identifier"] = PROP_NONE();
202 rCategory.m_aLabels["PolicyAuthority:Country"] = PROP_NONE();
203 rCategory.m_aLabels["Policy:Identifier"] = PROP_NONE();
204 rCategory.m_aLabels["BusinessAuthorization:Name"] = PROP_NONE();
205 rCategory.m_aLabels["BusinessAuthorization:Locator"] = PROP_NONE();
206 rCategory.m_aLabels["BusinessAuthorizationCategory:Identifier:OID"] = PROP_NONE();
207 rCategory.m_aLabels["BusinessAuthorizationCategory:Locator"] = PROP_NONE();
208 rCategory.m_aLabels["BusinessAuthorization:Locator"] = PROP_NONE();
209 rCategory.m_aLabels["MarkingPrecedence"] = PROP_NONE();
210 rCategory.m_aLabels["Marking:general-summary"].clear();
211 rCategory.m_aLabels["Marking:general-warning-statement"].clear();
212 rCategory.m_aLabels["Marking:general-warning-statement:ext:2"].clear();
213 rCategory.m_aLabels["Marking:general-warning-statement:ext:3"].clear();
214 rCategory.m_aLabels["Marking:general-warning-statement:ext:4"].clear();
215 rCategory.m_aLabels["Marking:general-distribution-statement"].clear();
216 rCategory.m_aLabels["Marking:general-distribution-statement:ext:2"].clear();
217 rCategory.m_aLabels["Marking:general-distribution-statement:ext:3"].clear();
218 rCategory.m_aLabels["Marking:general-distribution-statement:ext:4"].clear();
219 rCategory.m_aLabels[SfxClassificationHelper::PROP_DOCHEADER()].clear();
220 rCategory.m_aLabels[SfxClassificationHelper::PROP_DOCFOOTER()].clear();
221 rCategory.m_aLabels[SfxClassificationHelper::PROP_DOCWATERMARK()].clear();
222 rCategory.m_aLabels["Marking:email-first-line-of-text"].clear();
223 rCategory.m_aLabels["Marking:email-last-line-of-text"].clear();
224 rCategory.m_aLabels["Marking:email-subject-prefix"].clear();
225 rCategory.m_aLabels["Marking:email-subject-suffix"].clear();
226 rCategory.m_aLabels[PROP_STARTVALIDITY()] = PROP_NONE();
227 rCategory.m_aLabels["Authorization:StopValidity"] = PROP_NONE();
228 m_pCategory = &rCategory;
229 }
230 }
231 else if (rName == "loext:Marking")
232 {
233 OUString aName = xAttribs->getValueByName("Name");
234 m_aMarkings.push_back(aName);
235 }
236 else if (rName == "loext:IntellectualPropertyPart")
237 {
238 OUString aName = xAttribs->getValueByName("Name");
239 m_aIPParts.push_back(aName);
240 }
241 else if (rName == "loext:IntellectualPropertyPartNumber")
242 {
243 OUString aName = xAttribs->getValueByName("Name");
244 m_aIPPartNumbers.push_back(aName);
245 }
246 else if (rName == "baf:Scale")
247 {
248 m_aScale.clear();
249 m_bInScale = true;
250 }
251 else if (rName == "baf:ConfidentalityValue")
252 {
253 m_aConfidentalityValue.clear();
254 m_bInConfidentalityValue = true;
255 }
256 else if (rName == "baf:Identifier")
257 {
258 m_aIdentifier.clear();
259 m_bInIdentifier = true;
260 }
261 else if (rName == "baf:Value")
262 {
263 m_aValue.clear();
264 m_bInValue = true;
265 }
266 }
267
endElement(const OUString & rName)268 void SAL_CALL SfxClassificationParser::endElement(const OUString& rName)
269 {
270 if (rName == "baf:PolicyAuthorityName")
271 m_bInPolicyAuthorityName = false;
272 else if (rName == "baf:PolicyName")
273 m_bInPolicyName = false;
274 else if (rName == "baf:ProgramID")
275 m_bInProgramID = false;
276 else if (rName == "baf:BusinessAuthorizationCategory")
277 m_pCategory = nullptr;
278 else if (rName == "baf:Scale")
279 {
280 m_bInScale = false;
281 if (m_pCategory)
282 m_pCategory->m_aLabels[PROP_IMPACTSCALE()] = m_aScale;
283 }
284 else if (rName == "baf:ConfidentalityValue")
285 {
286 m_bInConfidentalityValue = false;
287 if (m_pCategory)
288 {
289 std::map<OUString, OUString>& rLabels = m_pCategory->m_aLabels;
290 rLabels[PROP_IMPACTLEVEL()] = m_aConfidentalityValue;
291 m_pCategory->m_nConfidentiality = m_aConfidentalityValue.toInt32(); // 0-based class sensitivity; 0 is lowest.
292 // Set the two other type of levels as well, if they're not set
293 // yet: they're optional in BAF, but not in BAILS.
294 rLabels.try_emplace("Impact:Level:Integrity", m_aConfidentalityValue);
295 rLabels.try_emplace("Impact:Level:Availability", m_aConfidentalityValue);
296 }
297 }
298 else if (rName == "baf:Identifier")
299 m_bInIdentifier = false;
300 else if (rName == "baf:Value")
301 {
302 if (m_pCategory)
303 {
304 if (m_aIdentifier == "Document: Header")
305 m_pCategory->m_aLabels[SfxClassificationHelper::PROP_DOCHEADER()] = m_aValue;
306 else if (m_aIdentifier == "Document: Footer")
307 m_pCategory->m_aLabels[SfxClassificationHelper::PROP_DOCFOOTER()] = m_aValue;
308 else if (m_aIdentifier == "Document: Watermark")
309 m_pCategory->m_aLabels[SfxClassificationHelper::PROP_DOCWATERMARK()] = m_aValue;
310 }
311 }
312 }
313
characters(const OUString & rChars)314 void SAL_CALL SfxClassificationParser::characters(const OUString& rChars)
315 {
316 if (m_bInPolicyAuthorityName)
317 m_aPolicyAuthorityName += rChars;
318 else if (m_bInPolicyName)
319 m_aPolicyName += rChars;
320 else if (m_bInProgramID)
321 m_aProgramID += rChars;
322 else if (m_bInScale)
323 m_aScale += rChars;
324 else if (m_bInConfidentalityValue)
325 m_aConfidentalityValue += rChars;
326 else if (m_bInIdentifier)
327 m_aIdentifier += rChars;
328 else if (m_bInValue)
329 m_aValue += rChars;
330 }
331
ignorableWhitespace(const OUString &)332 void SAL_CALL SfxClassificationParser::ignorableWhitespace(const OUString& /*rWhitespace*/)
333 {
334 }
335
processingInstruction(const OUString &,const OUString &)336 void SAL_CALL SfxClassificationParser::processingInstruction(const OUString& /*rTarget*/, const OUString& /*rData*/)
337 {
338 }
339
setDocumentLocator(const uno::Reference<xml::sax::XLocator> &)340 void SAL_CALL SfxClassificationParser::setDocumentLocator(const uno::Reference<xml::sax::XLocator>& /*xLocator*/)
341 {
342 }
343
344 } // anonymous namespace
345
346 /// Implementation details of SfxClassificationHelper.
347 class SfxClassificationHelper::Impl
348 {
349 public:
350 /// Selected categories, one category for each policy type.
351 std::map<SfxClassificationPolicyType, SfxClassificationCategory> m_aCategory;
352 /// Possible categories of a policy to choose from.
353 std::vector<SfxClassificationCategory> m_aCategories;
354 std::vector<OUString> m_aMarkings;
355 std::vector<OUString> m_aIPParts;
356 std::vector<OUString> m_aIPPartNumbers;
357
358 uno::Reference<document::XDocumentProperties> m_xDocumentProperties;
359
360 bool m_bUseLocalized;
361
362 explicit Impl(uno::Reference<document::XDocumentProperties> xDocumentProperties, bool bUseLocalized);
363 void parsePolicy();
364 /// Synchronize m_aLabels back to the document properties.
365 void pushToDocumentProperties();
366 /// Set the classification start date to the system time.
367 void setStartValidity(SfxClassificationPolicyType eType);
368 };
369
Impl(uno::Reference<document::XDocumentProperties> xDocumentProperties,bool bUseLocalized)370 SfxClassificationHelper::Impl::Impl(uno::Reference<document::XDocumentProperties> xDocumentProperties, bool bUseLocalized)
371 : m_xDocumentProperties(std::move(xDocumentProperties))
372 , m_bUseLocalized(bUseLocalized)
373 {
374 parsePolicy();
375 }
376
parsePolicy()377 void SfxClassificationHelper::Impl::parsePolicy()
378 {
379 uno::Reference<uno::XComponentContext> xComponentContext = comphelper::getProcessComponentContext();
380 SvtPathOptions aOptions;
381 OUString aPath = aOptions.GetClassificationPath();
382
383 // See if there is a localized variant next to the configured XML.
384 OUString aExtension(".xml");
385 if (aPath.endsWith(aExtension) && m_bUseLocalized)
386 {
387 OUString aBase = aPath.copy(0, aPath.getLength() - aExtension.getLength());
388 const LanguageTag& rLanguageTag = Application::GetSettings().GetLanguageTag();
389 // Expected format is "<original path>_xx-XX.xml".
390 OUString aLocalized = aBase + "_" + rLanguageTag.getBcp47() + aExtension;
391 if (FStatHelper::IsDocument(aLocalized))
392 aPath = aLocalized;
393 }
394
395 std::unique_ptr<SvStream> pStream = utl::UcbStreamHelper::CreateStream(aPath, StreamMode::READ);
396 uno::Reference<io::XInputStream> xInputStream(new utl::OStreamWrapper(std::move(pStream)));
397 xml::sax::InputSource aParserInput;
398 aParserInput.aInputStream = xInputStream;
399
400 uno::Reference<xml::sax::XParser> xParser = xml::sax::Parser::create(xComponentContext);
401 rtl::Reference<SfxClassificationParser> xClassificationParser(new SfxClassificationParser());
402 xParser->setDocumentHandler(xClassificationParser);
403 try
404 {
405 xParser->parseStream(aParserInput);
406 }
407 catch (const xml::sax::SAXParseException&)
408 {
409 TOOLS_WARN_EXCEPTION("sfx.view", "parsePolicy() failed");
410 }
411 m_aCategories = xClassificationParser->m_aCategories;
412 m_aMarkings = xClassificationParser->m_aMarkings;
413 m_aIPParts = xClassificationParser->m_aIPParts;
414 m_aIPPartNumbers = xClassificationParser->m_aIPPartNumbers;
415 }
416
lcl_containsProperty(const uno::Sequence<beans::Property> & rProperties,std::u16string_view rName)417 static bool lcl_containsProperty(const uno::Sequence<beans::Property>& rProperties, std::u16string_view rName)
418 {
419 return std::any_of(rProperties.begin(), rProperties.end(), [&](const beans::Property& rProperty)
420 {
421 return rProperty.Name == rName;
422 });
423 }
424
setStartValidity(SfxClassificationPolicyType eType)425 void SfxClassificationHelper::Impl::setStartValidity(SfxClassificationPolicyType eType)
426 {
427 auto itCategory = m_aCategory.find(eType);
428 if (itCategory == m_aCategory.end())
429 return;
430
431 SfxClassificationCategory& rCategory = itCategory->second;
432 auto it = rCategory.m_aLabels.find(policyTypeToString(eType) + PROP_STARTVALIDITY());
433 if (it != rCategory.m_aLabels.end())
434 {
435 if (it->second == PROP_NONE())
436 {
437 // The policy left the start date unchanged, replace it with the system time.
438 util::DateTime aDateTime = DateTime(DateTime::SYSTEM).GetUNODateTime();
439 it->second = utl::toISO8601(aDateTime);
440 }
441 }
442 }
443
pushToDocumentProperties()444 void SfxClassificationHelper::Impl::pushToDocumentProperties()
445 {
446 uno::Reference<beans::XPropertyContainer> xPropertyContainer = m_xDocumentProperties->getUserDefinedProperties();
447 uno::Reference<beans::XPropertySet> xPropertySet(xPropertyContainer, uno::UNO_QUERY);
448 uno::Sequence<beans::Property> aProperties = xPropertySet->getPropertySetInfo()->getProperties();
449 for (auto& rPair : m_aCategory)
450 {
451 SfxClassificationPolicyType eType = rPair.first;
452 SfxClassificationCategory& rCategory = rPair.second;
453 std::map<OUString, OUString> aLabels = rCategory.m_aLabels;
454 aLabels[policyTypeToString(eType) + PROP_BACNAME()] = rCategory.m_aName;
455 for (const auto& rLabel : aLabels)
456 {
457 try
458 {
459 if (lcl_containsProperty(aProperties, rLabel.first))
460 xPropertySet->setPropertyValue(rLabel.first, uno::makeAny(rLabel.second));
461 else
462 xPropertyContainer->addProperty(rLabel.first, beans::PropertyAttribute::REMOVABLE, uno::makeAny(rLabel.second));
463 }
464 catch (const uno::Exception&)
465 {
466 TOOLS_WARN_EXCEPTION("sfx.view", "pushDocumentProperties() failed for property " << rLabel.first);
467 }
468 }
469 }
470 }
471
IsClassified(const uno::Reference<document::XDocumentProperties> & xDocumentProperties)472 bool SfxClassificationHelper::IsClassified(const uno::Reference<document::XDocumentProperties>& xDocumentProperties)
473 {
474 uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
475 if (!xPropertyContainer.is())
476 return false;
477
478 uno::Reference<beans::XPropertySet> xPropertySet(xPropertyContainer, uno::UNO_QUERY);
479 const uno::Sequence<beans::Property> aProperties = xPropertySet->getPropertySetInfo()->getProperties();
480 for (const beans::Property& rProperty : aProperties)
481 {
482 if (rProperty.Name.startsWith("urn:bails:"))
483 return true;
484 }
485
486 return false;
487 }
488
CheckPaste(const uno::Reference<document::XDocumentProperties> & xSource,const uno::Reference<document::XDocumentProperties> & xDestination)489 SfxClassificationCheckPasteResult SfxClassificationHelper::CheckPaste(const uno::Reference<document::XDocumentProperties>& xSource,
490 const uno::Reference<document::XDocumentProperties>& xDestination)
491 {
492 if (!SfxClassificationHelper::IsClassified(xSource))
493 // No classification on the source side. Return early, regardless the
494 // state of the destination side.
495 return SfxClassificationCheckPasteResult::None;
496
497 if (!SfxClassificationHelper::IsClassified(xDestination))
498 {
499 // Paste from a classified document to a non-classified one -> deny.
500 return SfxClassificationCheckPasteResult::TargetDocNotClassified;
501 }
502
503 // Remaining case: paste between two classified documents.
504 SfxClassificationHelper aSource(xSource);
505 SfxClassificationHelper aDestination(xDestination);
506 if (aSource.GetImpactScale() != aDestination.GetImpactScale())
507 // It's possible to compare them if they have the same scale.
508 return SfxClassificationCheckPasteResult::None;
509
510 if (aSource.GetImpactLevel() > aDestination.GetImpactLevel())
511 // Paste from a doc that has higher classification -> deny.
512 return SfxClassificationCheckPasteResult::DocClassificationTooLow;
513
514 return SfxClassificationCheckPasteResult::None;
515 }
516
ShowPasteInfo(SfxClassificationCheckPasteResult eResult)517 bool SfxClassificationHelper::ShowPasteInfo(SfxClassificationCheckPasteResult eResult)
518 {
519 switch (eResult)
520 {
521 case SfxClassificationCheckPasteResult::None:
522 {
523 return true;
524 }
525 break;
526 case SfxClassificationCheckPasteResult::TargetDocNotClassified:
527 {
528 if (!Application::IsHeadlessModeEnabled())
529 {
530 std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr,
531 VclMessageType::Info, VclButtonsType::Ok,
532 SfxResId(STR_TARGET_DOC_NOT_CLASSIFIED)));
533 xBox->run();
534 }
535 return false;
536 }
537 break;
538 case SfxClassificationCheckPasteResult::DocClassificationTooLow:
539 {
540 if (!Application::IsHeadlessModeEnabled())
541 {
542 std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr,
543 VclMessageType::Info, VclButtonsType::Ok,
544 SfxResId(STR_DOC_CLASSIFICATION_TOO_LOW)));
545 xBox->run();
546 }
547 return false;
548 }
549 break;
550 }
551
552 return true;
553 }
554
SfxClassificationHelper(const uno::Reference<document::XDocumentProperties> & xDocumentProperties,bool bUseLocalizedPolicy)555 SfxClassificationHelper::SfxClassificationHelper(const uno::Reference<document::XDocumentProperties>& xDocumentProperties, bool bUseLocalizedPolicy)
556 : m_pImpl(std::make_unique<Impl>(xDocumentProperties, bUseLocalizedPolicy))
557 {
558 if (!xDocumentProperties.is())
559 return;
560
561 uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
562 if (!xPropertyContainer.is())
563 return;
564
565 uno::Reference<beans::XPropertySet> xPropertySet(xPropertyContainer, uno::UNO_QUERY);
566 const uno::Sequence<beans::Property> aProperties = xPropertySet->getPropertySetInfo()->getProperties();
567 for (const beans::Property& rProperty : aProperties)
568 {
569 if (!rProperty.Name.startsWith("urn:bails:"))
570 continue;
571
572 uno::Any aAny = xPropertySet->getPropertyValue(rProperty.Name);
573 OUString aValue;
574 if (aAny >>= aValue)
575 {
576 SfxClassificationPolicyType eType = stringToPolicyType(rProperty.Name);
577 OUString aPrefix = policyTypeToString(eType);
578 if (!rProperty.Name.startsWith(aPrefix))
579 // It's a prefix we did not recognize, ignore.
580 continue;
581
582 //TODO: Support abbreviated names(?)
583 if (rProperty.Name == (aPrefix + PROP_BACNAME()))
584 m_pImpl->m_aCategory[eType].m_aName = aValue;
585 else
586 m_pImpl->m_aCategory[eType].m_aLabels[rProperty.Name] = aValue;
587 }
588 }
589 }
590
591 SfxClassificationHelper::~SfxClassificationHelper() = default;
592
GetMarkings() const593 std::vector<OUString> const & SfxClassificationHelper::GetMarkings() const
594 {
595 return m_pImpl->m_aMarkings;
596 }
597
GetIntellectualPropertyParts() const598 std::vector<OUString> const & SfxClassificationHelper::GetIntellectualPropertyParts() const
599 {
600 return m_pImpl->m_aIPParts;
601 }
602
GetIntellectualPropertyPartNumbers() const603 std::vector<OUString> const & SfxClassificationHelper::GetIntellectualPropertyPartNumbers() const
604 {
605 return m_pImpl->m_aIPPartNumbers;
606 }
607
GetBACName(SfxClassificationPolicyType eType) const608 const OUString& SfxClassificationHelper::GetBACName(SfxClassificationPolicyType eType) const
609 {
610 return m_pImpl->m_aCategory[eType].m_aName;
611 }
612
GetAbbreviatedBACName(const OUString & sFullName)613 const OUString& SfxClassificationHelper::GetAbbreviatedBACName(const OUString& sFullName)
614 {
615 for (const auto& category : m_pImpl->m_aCategories)
616 {
617 if (category.m_aName == sFullName)
618 return category.m_aAbbreviatedName;
619 }
620
621 return sFullName;
622 }
623
GetBACNameForIdentifier(std::u16string_view sIdentifier)624 OUString SfxClassificationHelper::GetBACNameForIdentifier(std::u16string_view sIdentifier)
625 {
626 if (sIdentifier.empty())
627 return "";
628
629 for (const auto& category : m_pImpl->m_aCategories)
630 {
631 if (category.m_aIdentifier == sIdentifier)
632 return category.m_aName;
633 }
634
635 return "";
636 }
637
GetHigherClass(const OUString & first,const OUString & second)638 OUString SfxClassificationHelper::GetHigherClass(const OUString& first, const OUString& second)
639 {
640 size_t nFirstConfidentiality = 0;
641 size_t nSecondConfidentiality = 0;
642 for (const auto& category : m_pImpl->m_aCategories)
643 {
644 if (category.m_aName == first)
645 nFirstConfidentiality = category.m_nConfidentiality;
646 if (category.m_aName == second)
647 nSecondConfidentiality = category.m_nConfidentiality;
648 }
649
650 return nFirstConfidentiality >= nSecondConfidentiality ? first : second;
651 }
652
HasImpactLevel()653 bool SfxClassificationHelper::HasImpactLevel()
654 {
655 auto itCategory = m_pImpl->m_aCategory.find(SfxClassificationPolicyType::IntellectualProperty);
656 if (itCategory == m_pImpl->m_aCategory.end())
657 return false;
658
659 SfxClassificationCategory& rCategory = itCategory->second;
660 auto it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_IMPACTSCALE());
661 if (it == rCategory.m_aLabels.end())
662 return false;
663
664 it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_IMPACTLEVEL());
665 return it != rCategory.m_aLabels.end();
666 }
667
HasDocumentHeader()668 bool SfxClassificationHelper::HasDocumentHeader()
669 {
670 auto itCategory = m_pImpl->m_aCategory.find(SfxClassificationPolicyType::IntellectualProperty);
671 if (itCategory == m_pImpl->m_aCategory.end())
672 return false;
673
674 SfxClassificationCategory& rCategory = itCategory->second;
675 auto it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_DOCHEADER());
676 return it != rCategory.m_aLabels.end() && !it->second.isEmpty();
677 }
678
HasDocumentFooter()679 bool SfxClassificationHelper::HasDocumentFooter()
680 {
681 auto itCategory = m_pImpl->m_aCategory.find(SfxClassificationPolicyType::IntellectualProperty);
682 if (itCategory == m_pImpl->m_aCategory.end())
683 return false;
684
685 SfxClassificationCategory& rCategory = itCategory->second;
686 auto it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_DOCFOOTER());
687 return it != rCategory.m_aLabels.end() && !it->second.isEmpty();
688 }
689
GetImpactLevelType()690 InfobarType SfxClassificationHelper::GetImpactLevelType()
691 {
692 InfobarType aRet;
693
694 aRet = InfobarType::WARNING;
695
696 auto itCategory = m_pImpl->m_aCategory.find(SfxClassificationPolicyType::IntellectualProperty);
697 if (itCategory == m_pImpl->m_aCategory.end())
698 return aRet;
699
700 SfxClassificationCategory& rCategory = itCategory->second;
701 auto it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_IMPACTSCALE());
702 if (it == rCategory.m_aLabels.end())
703 return aRet;
704 OUString aScale = it->second;
705
706 it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_IMPACTLEVEL());
707 if (it == rCategory.m_aLabels.end())
708 return aRet;
709 OUString aLevel = it->second;
710
711 // The spec defines two valid scale values: FIPS-199 and UK-Cabinet.
712 if (aScale == "UK-Cabinet")
713 {
714 if (aLevel == "0")
715 aRet = InfobarType::SUCCESS;
716 else if (aLevel == "1")
717 aRet = InfobarType::WARNING;
718 else if (aLevel == "2")
719 aRet = InfobarType::WARNING;
720 else if (aLevel == "3")
721 aRet = InfobarType::DANGER;
722 }
723 else if (aScale == "FIPS-199")
724 {
725 if (aLevel == "Low")
726 aRet = InfobarType::SUCCESS;
727 else if (aLevel == "Moderate")
728 aRet = InfobarType::WARNING;
729 else if (aLevel == "High")
730 aRet = InfobarType::DANGER;
731 }
732 return aRet;
733 }
734
GetImpactLevel()735 sal_Int32 SfxClassificationHelper::GetImpactLevel()
736 {
737 sal_Int32 nRet = -1;
738
739 auto itCategory = m_pImpl->m_aCategory.find(SfxClassificationPolicyType::IntellectualProperty);
740 if (itCategory == m_pImpl->m_aCategory.end())
741 return nRet;
742
743 SfxClassificationCategory& rCategory = itCategory->second;
744 auto it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_IMPACTSCALE());
745 if (it == rCategory.m_aLabels.end())
746 return nRet;
747 OUString aScale = it->second;
748
749 it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_IMPACTLEVEL());
750 if (it == rCategory.m_aLabels.end())
751 return nRet;
752 OUString aLevel = it->second;
753
754 if (aScale == "UK-Cabinet")
755 {
756 sal_Int32 nValue = aLevel.toInt32();
757 if (nValue < 0 || nValue > 3)
758 return nRet;
759 nRet = nValue;
760 }
761 else if (aScale == "FIPS-199")
762 {
763 static std::map<OUString, sal_Int32> const aValues
764 {
765 { "Low", 0 },
766 { "Moderate", 1 },
767 { "High", 2 }
768 };
769 auto itValues = aValues.find(aLevel);
770 if (itValues == aValues.end())
771 return nRet;
772 nRet = itValues->second;
773 }
774
775 return nRet;
776 }
777
GetImpactScale()778 OUString SfxClassificationHelper::GetImpactScale()
779 {
780 auto itCategory = m_pImpl->m_aCategory.find(SfxClassificationPolicyType::IntellectualProperty);
781 if (itCategory == m_pImpl->m_aCategory.end())
782 return OUString();
783
784 SfxClassificationCategory& rCategory = itCategory->second;
785 auto it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_IMPACTSCALE());
786 if (it != rCategory.m_aLabels.end())
787 return it->second;
788
789 return OUString();
790 }
791
GetDocumentWatermark()792 OUString SfxClassificationHelper::GetDocumentWatermark()
793 {
794 auto itCategory = m_pImpl->m_aCategory.find(SfxClassificationPolicyType::IntellectualProperty);
795 if (itCategory == m_pImpl->m_aCategory.end())
796 return OUString();
797
798 SfxClassificationCategory& rCategory = itCategory->second;
799 auto it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_DOCWATERMARK());
800 if (it != rCategory.m_aLabels.end())
801 return it->second;
802
803 return OUString();
804 }
805
GetBACNames()806 std::vector<OUString> SfxClassificationHelper::GetBACNames()
807 {
808 if (m_pImpl->m_aCategories.empty())
809 m_pImpl->parsePolicy();
810
811 std::vector<OUString> aRet;
812 std::transform(m_pImpl->m_aCategories.begin(), m_pImpl->m_aCategories.end(), std::back_inserter(aRet), [](const SfxClassificationCategory& rCategory)
813 {
814 return rCategory.m_aName;
815 });
816 return aRet;
817 }
818
GetBACIdentifiers()819 std::vector<OUString> SfxClassificationHelper::GetBACIdentifiers()
820 {
821 if (m_pImpl->m_aCategories.empty())
822 m_pImpl->parsePolicy();
823
824 std::vector<OUString> aRet;
825 std::transform(m_pImpl->m_aCategories.begin(), m_pImpl->m_aCategories.end(), std::back_inserter(aRet), [](const SfxClassificationCategory& rCategory)
826 {
827 return rCategory.m_aIdentifier;
828 });
829 return aRet;
830 }
831
GetAbbreviatedBACNames()832 std::vector<OUString> SfxClassificationHelper::GetAbbreviatedBACNames()
833 {
834 if (m_pImpl->m_aCategories.empty())
835 m_pImpl->parsePolicy();
836
837 std::vector<OUString> aRet;
838 std::transform(m_pImpl->m_aCategories.begin(), m_pImpl->m_aCategories.end(), std::back_inserter(aRet), [](const SfxClassificationCategory& rCategory)
839 {
840 return rCategory.m_aAbbreviatedName;
841 });
842 return aRet;
843 }
844
SetBACName(const OUString & rName,SfxClassificationPolicyType eType)845 void SfxClassificationHelper::SetBACName(const OUString& rName, SfxClassificationPolicyType eType)
846 {
847 if (m_pImpl->m_aCategories.empty())
848 m_pImpl->parsePolicy();
849
850 auto it = std::find_if(m_pImpl->m_aCategories.begin(), m_pImpl->m_aCategories.end(), [&](const SfxClassificationCategory& rCategory)
851 {
852 return rCategory.m_aName == rName;
853 });
854 if (it == m_pImpl->m_aCategories.end())
855 {
856 SAL_WARN("sfx.view", "'" << rName << "' is not a recognized category name");
857 return;
858 }
859
860 m_pImpl->m_aCategory[eType].m_aName = it->m_aName;
861 m_pImpl->m_aCategory[eType].m_aAbbreviatedName = it->m_aAbbreviatedName;
862 m_pImpl->m_aCategory[eType].m_nConfidentiality = it->m_nConfidentiality;
863 m_pImpl->m_aCategory[eType].m_aLabels.clear();
864 const OUString& rPrefix = policyTypeToString(eType);
865 for (const auto& rLabel : it->m_aLabels)
866 m_pImpl->m_aCategory[eType].m_aLabels[rPrefix + rLabel.first] = rLabel.second;
867
868 m_pImpl->setStartValidity(eType);
869 m_pImpl->pushToDocumentProperties();
870 SfxViewFrame* pViewFrame = SfxViewFrame::Current();
871 if (!pViewFrame)
872 return;
873
874 UpdateInfobar(*pViewFrame);
875 }
876
UpdateInfobar(SfxViewFrame & rViewFrame)877 void SfxClassificationHelper::UpdateInfobar(SfxViewFrame& rViewFrame)
878 {
879 OUString aBACName = GetBACName(SfxClassificationPolicyType::IntellectualProperty);
880 bool bImpactLevel = HasImpactLevel();
881 if (!aBACName.isEmpty() && bImpactLevel)
882 {
883 OUString aMessage = SfxResId(STR_CLASSIFIED_DOCUMENT);
884 aMessage = aMessage.replaceFirst("%1", aBACName);
885
886 rViewFrame.RemoveInfoBar(u"classification");
887 rViewFrame.AppendInfoBar("classification", "", aMessage, GetImpactLevelType());
888 }
889 }
890
stringToPolicyType(const OUString & rType)891 SfxClassificationPolicyType SfxClassificationHelper::stringToPolicyType(const OUString& rType)
892 {
893 if (rType.startsWith(PROP_PREFIX_EXPORTCONTROL()))
894 return SfxClassificationPolicyType::ExportControl;
895 else if (rType.startsWith(PROP_PREFIX_NATIONALSECURITY()))
896 return SfxClassificationPolicyType::NationalSecurity;
897 else
898 return SfxClassificationPolicyType::IntellectualProperty;
899 }
900
policyTypeToString(SfxClassificationPolicyType eType)901 const OUString& SfxClassificationHelper::policyTypeToString(SfxClassificationPolicyType eType)
902 {
903 switch (eType)
904 {
905 case SfxClassificationPolicyType::ExportControl:
906 return PROP_PREFIX_EXPORTCONTROL();
907 case SfxClassificationPolicyType::NationalSecurity:
908 return PROP_PREFIX_NATIONALSECURITY();
909 case SfxClassificationPolicyType::IntellectualProperty:
910 break;
911 }
912
913 return PROP_PREFIX_INTELLECTUALPROPERTY();
914 }
915
PROP_DOCHEADER()916 const OUString& SfxClassificationHelper::PROP_DOCHEADER()
917 {
918 static const OUString sProp("Marking:document-header");
919 return sProp;
920 }
921
PROP_DOCFOOTER()922 const OUString& SfxClassificationHelper::PROP_DOCFOOTER()
923 {
924 static const OUString sProp("Marking:document-footer");
925 return sProp;
926 }
927
PROP_DOCWATERMARK()928 const OUString& SfxClassificationHelper::PROP_DOCWATERMARK()
929 {
930 static const OUString sProp("Marking:document-watermark");
931 return sProp;
932 }
933
PROP_PREFIX_INTELLECTUALPROPERTY()934 const OUString& SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY()
935 {
936 static const OUString sProp("urn:bails:IntellectualProperty:");
937 return sProp;
938 }
939
getPolicyType()940 SfxClassificationPolicyType SfxClassificationHelper::getPolicyType()
941 {
942 sal_Int32 nPolicyTypeNumber = officecfg::Office::Common::Classification::Policy::get();
943 auto eType = static_cast<SfxClassificationPolicyType>(nPolicyTypeNumber);
944 return eType;
945 }
946
947 namespace sfx
948 {
949
950 namespace
951 {
952
getProperty(uno::Reference<beans::XPropertyContainer> const & rxPropertyContainer,OUString const & rName)953 OUString getProperty(uno::Reference<beans::XPropertyContainer> const& rxPropertyContainer,
954 OUString const& rName)
955 {
956 try
957 {
958 uno::Reference<beans::XPropertySet> xPropertySet(rxPropertyContainer, uno::UNO_QUERY);
959 return xPropertySet->getPropertyValue(rName).get<OUString>();
960 }
961 catch (const css::uno::Exception&)
962 {
963 }
964
965 return OUString();
966 }
967
968 } // end anonymous namespace
969
getCreationOriginProperty(uno::Reference<beans::XPropertyContainer> const & rxPropertyContainer,sfx::ClassificationKeyCreator const & rKeyCreator)970 sfx::ClassificationCreationOrigin getCreationOriginProperty(uno::Reference<beans::XPropertyContainer> const & rxPropertyContainer,
971 sfx::ClassificationKeyCreator const & rKeyCreator)
972 {
973 OUString sValue = getProperty(rxPropertyContainer, rKeyCreator.makeCreationOriginKey());
974 if (sValue.isEmpty())
975 return sfx::ClassificationCreationOrigin::NONE;
976
977 return (sValue == "BAF_POLICY")
978 ? sfx::ClassificationCreationOrigin::BAF_POLICY
979 : sfx::ClassificationCreationOrigin::MANUAL;
980 }
981
982 }
983
984 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
985