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