Coverage Report

Created: 2026-03-31 11:00

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