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