Working with Sensibility Labels
As part of Office 365, you can apply sensibility labels to your documents. A sensibility label is a meta data tag that can be set on a document. The sensibility labels can also enforce encryption, apply watermarks, and add text to headers and footers. To apply sensitivity labels to your documents, you use the Microsoft Information Protection SDK.
Our sample project¶
Our sample project, EPPlus.Samples.SensibilityLabels, shows how you use EPPlus 8 or later with the Microsoft Information Protection SDK to handle sensibility labels on your Excel files.
This solution contains two projects:
| Method | Description |
|---|---|
| EPPlus.Samples.SensibilityLabels.Integrated | This project integrates with EPPlus using the ISensitivityLabelHandler interface and the MIP SDK. Requires EPPlus 8 or later. |
| EPPlus.Samples.SensibilityLabels.External | This project uses the MIP SDK with EPPlus without using an ISensitivityLabelHandler. This sample can be used with versions prior to EPPlus 8. |
Getting started¶
To use this sample, you will need to the following:
- A Microsoft Purview account
- A Microsoft 365 Business Premium subscription or similar supporting sensibility labels.
- An application registration in Azure to get access to your sensitivity labelling data.
Microsoft Purview settings¶
If you don't have a Microsoft Purview account, you can create one at https://purview.microsoft.com/ using your Azure account.
This tutorial will not cover the setup of your Microsoft Purview account, but you can find guide lines here: Microsoft Purview setup guides
Add at least one sensitivity label to use in the sample under Information Protection - Sensibility labels menu.
Workstation setup & App registration¶
To use the Microsoft Information Protection API, you need to register an application ("App Registrations") in the Azure portal or the Microsoft Entra admin center.
To setup your application registration and your workstation, please see this guide Microsoft Information Protection (MIP) SDK setup and configuration.
When the setup is done, in the application registration, add a redirection Uri to: http://localhost, if you run the sample from Visual Studio.
Running the sample¶
Before you can run the sample you will need to specify a few parameters in the SetupConstants.cs file.
- _tenantId - The Directory (tenant) ID from the App Registration's Overview page.
- _clientId - The Application (client) ID from the App Registration's Overview page.
-
_appName - The application display name from the app registration.
-
_loginAccount - The account used to login and get the access token. This account must have access to the protected content we are working with in the samples.
- _labelSample1 - The name of a sensibility label of you choice that is set on the workbook in sample 1.
- _protectedSampleFile - The path to a protected excel file that will be read and updated by EPPlus in sample 2, to demonstrate how to work with files protected by sensibility labels.
Integrate the MIP SDK with EPPlus¶
Starting with EPPlus 8, you can add a sensitivity label handler to more easily manage documents with sensitivity labels.
EPPlus will identify packages protected by sensitivity labels and use the sensitivity label handler to encrypt and decrypt these workbooks.
To do so, EPPlus provides the ISensitivityLabelHandler interface, which should be used together with the Microsoft MIP SDK.
| Method | Description |
|---|---|
InitAsync |
Called to initiate the handler. This method is called once when the handler is assigned to EPPlus. Here you should initiate the MIP SDK and connect to the Microsoft Entra Application used to connect to you Microsoft Purview Account |
DecryptPackageAsync |
Called to decrypt a protected package. When EPPlus identifies a workbook to be encrypted with a sensibility label, the stream will be passed to this function for decryption. |
ApplyLabelAndSavePackageAsync |
Called from EPPlus when the package has been saved and to apply the active label. EPPlus will supply the package stream and the sensibility label to be applied using the the MIP SDK. Returns a stream containing the output from the MIPS SDK. |
UpdateLabelList |
Should update the supplied list of sensibility labels with name, description and other properties not present in the Sensibility Label XML document inside the package. |
GetLabels |
Get all labels from the MIPS SDK |
The DecryptPackageAsync,ApplyLabelAndSavePackageAsync, UpdateLabelList and the GetLabels has an id property parameter, that is a unique identifier, supplied by EPPlus to identify packages between the calls.
Sample of an implementation:¶
This code below is provided as-is to illustrate an example.
You will have to adapt it to your setup with the appropriate accounts and permissions and the type of sensibility labels you have in your organization:
using Microsoft.InformationProtection;
using Microsoft.InformationProtection.File;
using Microsoft.InformationProtection.Protection;
using OfficeOpenXml.Interfaces.SensitivityLabels;
using SensibilityLabelHandler;
using System.Collections.ObjectModel;
/// <summary>
/// Example of a sensibility label handler
/// </summary>
public class MySensibilityLabelHandler : ISensitivityLabelHandler, IExcelSensibilityLabelUpdate
{
private IFileEngine _fileEngine;
/// <summary>
/// Initialize the MIP api and setup the authentication you want to use.
/// The sample below is just an example, so you should set it up according to your organizations requirements
/// </summary>
/// <returns></returns>
public async Task InitAsync()
{
MIP.Initialize(MipComponent.File);
//Create ApplicationInfo, setting the client ID from Microsoft Entra App Registration as the ApplicationId.
ApplicationInfo appInfo = new ApplicationInfo()
{
ApplicationId = SetupConstants._clientId,
ApplicationName = SetupConstants._appName,
ApplicationVersion = "1.0.0"
};
MipConfiguration mipConfiguration = new MipConfiguration(appInfo, "mip_data", LogLevel.Trace, false);
MipContext mipContext = MIP.CreateMipContext(mipConfiguration);
var profileSettings = new FileProfileSettings(mipContext,
CacheStorageType.OnDiskEncrypted,
new ConsentDelegateImplementation());
// Load the Profile async and wait for the result.
var fileProfile = await MIP.LoadFileProfileAsync(profileSettings);
var authDelegate = new AuthDelegateImplementation(appInfo);
var engineSettings = new FileEngineSettings(SetupConstants._loginAccount, authDelegate, "", "en-US");
engineSettings.Identity = new Identity(SetupConstants._loginAccount);
_fileEngine = await fileProfile.AddEngineAsync(engineSettings);
}
/// <summary>
/// Decrypts the stream and returns it to EPPlus unencrypted.
/// </summary>
/// <param name="packageStream">The package stream to handle. If the sensibility has any type of protection, the stream is encrypted and must be decrypted before returning it to EPPlus.</param>
/// <param name="id">The unique id for the package.</param>
/// <returns>Returns the decrypted package and the protection information to EPPlus.</returns>
public async Task<IPackageInfo> DecryptPackageAsync(MemoryStream packageStream, string id)
{
PackageInformation ret = new PackageInformation();
var fileHandler = await _fileEngine.CreateFileHandlerAsync(packageStream, $@"myfile.xlsx", true);
if (fileHandler.Protection != null) //Is the stream encrypted?
{
//Yes, save the protection information and decrypt the stream.
ret.ProtectionInformation = fileHandler.Protection;
fileHandler.RemoveProtection();
var ms = new MemoryStream();
await fileHandler.CommitAsync(ms);
ret.PackageStream = ms;
}
else
{
//No, use the unencrypted stream directly.
ret.PackageStream = packageStream;
}
return ret;
}
/// <summary>
/// Applies a sensibility label and sets protection using the MIPS SDK.
/// </summary>
/// <param name="package">The package stream, protection information and the sensibilty label to apply.</param>
/// <param name="id">The unique id for the package.</param>
/// <returns></returns>
public async Task<MemoryStream> ApplyLabelAndSavePackageAsync(IPackageInfo package, string id)
{
package.PackageStream.Position = 0;
var fileHandle = await _fileEngine.CreateFileHandlerAsync(package.PackageStream, $@"myfile.xlsx", true);
if (string.IsNullOrEmpty(package.ActiveLabelId) == false)
{
var l = _fileEngine.GetLabelById(package.ActiveLabelId);
fileHandle.SetLabel(l, new LabelingOptions(), new ProtectionSettings() { });
}
if (package.ProtectionInformation is IProtectionHandler protection)
{
fileHandle.SetProtection(protection);
}
var ms = new MemoryStream();
var ret = await fileHandle.CommitAsync(ms);
if (ret)
{
return ms;
}
else
{
throw new InvalidOperationException("Could not commit sensibility label update.");
}
}
/// <summary>
/// Returns all labels from available for the package.
/// </summary>
/// <param name="id">The unique id for the package.</param>
/// <returns>A collection of labels</returns>
public IEnumerable<IExcelSensibilityLabel> GetLabels(string Id)
{
var list = new List<IExcelSensibilityLabel>();
AddLabelCollectionToList(list, _fileEngine.SensitivityLabels);
return list;
}
/// <summary>
/// Updates the labels from EPPlus with properties missing in the package, such as Name, Description and Color.
/// </summary>
/// <param name="labels"></param>
/// <param name="Id"></param>
public void UpdateLabelList(IEnumerable<IExcelSensibilityLabel> labels, string Id)
{
var lblDict = LabelsDictionary;
foreach (var sl in labels)
{
UpdateItem(lblDict, sl);
}
}
private void AddLabelCollectionToList(List<IExcelSensibilityLabel> list, ReadOnlyCollection<Label> sensitivityLabels)
{
foreach (var ss in sensitivityLabels.OrderBy(x => x.Parent))
{
list.Add(new SensibilityLabel()
{
Id = ss.Id,
Name = ss.Name,
Description = ss.Description,
Tooltip = ss.Tooltip,
Enabled = ss.IsActive,
Removed = !ss.IsActive,
SiteId = SetupConstants._tenantId,
Color = ss.Color,
Method = ss.ActionSource == ActionSource.Automatic ? eMethod.Privileged : eMethod.Standard,
ContentBits = ss.Sensitivity == 0 ? 0 : eContentBits.Encryption
});
AddLabelCollectionToList(list, ss.Children);
}
}
private static void UpdateItem(Dictionary<string, Label> lblDict, IExcelSensibilityLabel sl)
{
if (lblDict.TryGetValue(sl.Id, out Label? lbl))
{
if (sl is IExcelSensibilityLabelUpdate upd)
{
IExcelSensibilityLabel parent;
if (lbl.Parent != null)
{
parent = GetLabel(lbl.Parent);
}
else
{
parent = null;
}
upd.Update(lbl.Name, lbl.Tooltip, lbl.Description, lbl.Color, parent);
}
}
}
private static IExcelSensibilityLabel GetLabel(Label lbl)
{
if(lbl==null)
{
return null;
}
return new SensibilityLabel()
{
Id = lbl.Id,
Name = lbl.Name,
Description = lbl.Description,
Tooltip = lbl.Tooltip,
Enabled = false,
Removed = false,
SiteId = SetupConstants._tenantId,
Color = lbl.Color,
Parent = GetLabel(lbl.Parent)
};
}
Dictionary<string, Label> _labelsDictionary = null;
private Dictionary<string, Label> LabelsDictionary
{
get
{
if (_labelsDictionary==null)
{
_labelsDictionary = new Dictionary<string, Label>();
if (_fileEngine != null)
{
AddListToDict(_labelsDictionary, _fileEngine.SensitivityLabels);
}
}
return _labelsDictionary;
}
}
private static void AddListToDict(Dictionary<string, Label> lbls, IList<Label> labels)
{
foreach (var l in labels)
{
if (lbls.ContainsKey(l.Id) == false)
{
lbls.Add(l.Id, l);
if (l.Children != null && l.Children.Count > 0)
{
AddListToDict(lbls, l.Children);
}
}
}
}
}
/// <summary>
/// Implementation of the IPackageInfo interface, that passes information to EPPlus.
/// </summary>
public class PackageInformation : IPackageInfo
{
/// <summary>
/// The package stream
/// </summary>
public MemoryStream PackageStream { get; set; }
/// <summary>
/// Protection information passed to EPPlus.
/// This property may hold the protection information or any other class used to hold information between the decryption operation and saving and applying the sensibility label.
/// </summary>
public object ProtectionInformation { get; set; }
/// <summary>
/// The label id of the sensibility label to apply.
/// </summary>
public string ActiveLabelId { get; set; }
}
Imports Microsoft.InformationProtection
Imports Microsoft.InformationProtection.File
Imports Microsoft.InformationProtection.Protection
Imports OfficeOpenXml.Interfaces.SensitivityLabels
Imports SensibilityLabelHandler
Imports System.Collections.ObjectModel
''' <summary>
''' Example of a sensibility label handler
''' </summary>
Public Class MySensibilityLabelHandler
Implements ISensitivityLabelHandler, IExcelSensibilityLabelUpdate
Private _fileEngine As IFileEngine
''' <summary>
''' Initialize the MIP api and setup the authentication you want to use.
''' The sample below is just an example, so you should set it up according to your organizations requirements
''' </summary>
''' <returns></returns>
Public Async Function InitAsync() As Task Implements ISensitivityLabelHandler.InitAsync
MIP.Initialize(MipComponent.File)
'Create ApplicationInfo, setting the client ID from Microsoft Entra App Registration as the ApplicationId.
Dim appInfo As New ApplicationInfo() With {
.ApplicationId = SetupConstants._clientId,
.ApplicationName = SetupConstants._appName,
.ApplicationVersion = "1.0.0"
}
Dim mipConfiguration As New MipConfiguration(appInfo, "mip_data", LogLevel.Trace, False)
Dim mipContext As MipContext = MIP.CreateMipContext(mipConfiguration)
Dim profileSettings As New FileProfileSettings(mipContext,
CacheStorageType.OnDiskEncrypted,
New ConsentDelegateImplementation())
' Load the Profile async and wait for the result.
Dim fileProfile = Await MIP.LoadFileProfileAsync(profileSettings)
Dim authDelegate As New AuthDelegateImplementation(appInfo)
Dim engineSettings As New FileEngineSettings(SetupConstants._loginAccount, authDelegate, "", "en-US")
engineSettings.Identity = New Identity(SetupConstants._loginAccount)
_fileEngine = Await fileProfile.AddEngineAsync(engineSettings)
End Function
''' <summary>
''' Decrypts the stream and returns it to EPPlus unencrypted.
''' </summary>
''' <param name="packageStream">The package stream to handle. If the sensibility has any type of protection, the stream is encrypted and must be decrypted before returning it to EPPlus.</param>
''' <param name="id">The unique id for the package.</param>
''' <returns>Returns the decrypted package and the protection information to EPPlus.</returns>
Public Async Function DecryptPackageAsync(packageStream As MemoryStream, id As String) As Task(Of IPackageInfo) Implements ISensitivityLabelHandler.DecryptPackageAsync
Dim ret As New PackageInformation()
Dim fileHandler = Await _fileEngine.CreateFileHandlerAsync(packageStream, "myfile.xlsx", True)
If fileHandler.Protection IsNot Nothing Then 'Is the stream encrypted?
'Yes, save the protection information and decrypt the stream.
ret.ProtectionInformation = fileHandler.Protection
fileHandler.RemoveProtection()
Dim ms As New MemoryStream()
Await fileHandler.CommitAsync(ms)
ret.PackageStream = ms
Else
'No, use the unencrypted stream directly.
ret.PackageStream = packageStream
End If
Return ret
End Function
''' <summary>
''' Applies a sensibility label and sets protection using the MIPS SDK.
''' </summary>
''' <param name="package">The package stream, protection information and the sensibilty label to apply.</param>
''' <param name="id">The unique id for the package.</param>
''' <returns></returns>
Public Async Function ApplyLabelAndSavePackageAsync(package As IPackageInfo, id As String) As Task(Of MemoryStream) Implements ISensitivityLabelHandler.ApplyLabelAndSavePackageAsync
package.PackageStream.Position = 0
Dim fileHandle = Await _fileEngine.CreateFileHandlerAsync(package.PackageStream, "myfile.xlsx", True)
If String.IsNullOrEmpty(package.ActiveLabelId) = False Then
Dim l = _fileEngine.GetLabelById(package.ActiveLabelId)
fileHandle.SetLabel(l, New LabelingOptions(), New ProtectionSettings())
End If
If TypeOf package.ProtectionInformation Is IProtectionHandler Then
Dim protection = DirectCast(package.ProtectionInformation, IProtectionHandler)
fileHandle.SetProtection(protection)
End If
Dim ms As New MemoryStream()
Dim ret = Await fileHandle.CommitAsync(ms)
If ret Then
Return ms
Else
Throw New InvalidOperationException("Could not commit sensibility label update.")
End If
End Function
''' <summary>
''' Returns all labels from available for the package.
''' </summary>
''' <param name="id">The unique id for the package.</param>
''' <returns>A collection of labels</returns>
Public Function GetLabels(Id As String) As IEnumerable(Of IExcelSensibilityLabel) Implements ISensitivityLabelHandler.GetLabels
Dim list As New List(Of IExcelSensibilityLabel)()
AddLabelCollectionToList(list, _fileEngine.SensitivityLabels)
Return list
End Function
''' <summary>
''' Updates the labels from EPPlus with properties missing in the package, such as Name, Description and Color.
''' </summary>
''' <param name="labels"></param>
''' <param name="Id"></param>
Public Sub UpdateLabelList(labels As IEnumerable(Of IExcelSensibilityLabel), Id As String) Implements ISensitivityLabelHandler.UpdateLabelList
Dim lblDict = LabelsDictionary
For Each sl In labels
UpdateItem(lblDict, sl)
Next
End Sub
Private Sub AddLabelCollectionToList(list As List(Of IExcelSensibilityLabel), sensitivityLabels As ReadOnlyCollection(Of Label))
For Each ss In sensitivityLabels.OrderBy(Function(x) x.Parent)
list.Add(New SensibilityLabel() With {
.Id = ss.Id,
.Name = ss.Name,
.Description = ss.Description,
.Tooltip = ss.Tooltip,
.Enabled = ss.IsActive,
.Removed = Not ss.IsActive,
.SiteId = SetupConstants._tenantId,
.Color = ss.Color,
.Method = If(ss.ActionSource = ActionSource.Automatic, eMethod.Privileged, eMethod.Standard),
.ContentBits = If(ss.Sensitivity = 0, CType(0, eContentBits), eContentBits.Encryption)
})
AddLabelCollectionToList(list, ss.Children)
Next
End Sub
Private Shared Sub UpdateItem(lblDict As Dictionary(Of String, Label), sl As IExcelSensibilityLabel)
Dim lbl As Label = Nothing
If lblDict.TryGetValue(sl.Id, lbl) Then
If TypeOf sl Is IExcelSensibilityLabelUpdate Then
Dim upd = DirectCast(sl, IExcelSensibilityLabelUpdate)
Dim parent As IExcelSensibilityLabel
If lbl.Parent IsNot Nothing Then
parent = GetLabel(lbl.Parent)
Else
parent = Nothing
End If
upd.Update(lbl.Name, lbl.Tooltip, lbl.Description, lbl.Color, parent)
End If
End If
End Sub
Private Shared Function GetLabel(lbl As Label) As IExcelSensibilityLabel
If lbl Is Nothing Then
Return Nothing
End If
Return New SensibilityLabel() With {
.Id = lbl.Id,
.Name = lbl.Name,
.Description = lbl.Description,
.Tooltip = lbl.Tooltip,
.Enabled = False,
.Removed = False,
.SiteId = SetupConstants._tenantId,
.Color = lbl.Color,
.Parent = GetLabel(lbl.Parent)
}
End Function
Private _labelsDictionary As Dictionary(Of String, Label) = Nothing
Private ReadOnly Property LabelsDictionary As Dictionary(Of String, Label)
Get
If _labelsDictionary Is Nothing Then
_labelsDictionary = New Dictionary(Of String, Label)()
If _fileEngine IsNot Nothing Then
AddListToDict(_labelsDictionary, _fileEngine.SensitivityLabels)
End If
End If
Return _labelsDictionary
End Get
End Property
Private Shared Sub AddListToDict(lbls As Dictionary(Of String, Label), labels As IList(Of Label))
For Each l In labels
If lbls.ContainsKey(l.Id) = False Then
lbls.Add(l.Id, l)
If l.Children IsNot Nothing AndAlso l.Children.Count > 0 Then
AddListToDict(lbls, l.Children)
End If
End If
Next
End Sub
End Class
''' <summary>
''' Implementation of the IPackageInfo interface, that passes information to EPPlus.
''' </summary>
Public Class PackageInformation
Implements IPackageInfo
''' <summary>
''' The package stream
''' </summary>
Public Property PackageStream As MemoryStream Implements IPackageInfo.PackageStream
''' <summary>
''' Protection information passed to EPPlus.
''' This property may hold the protection information or any other class used to hold information between the decryption operation and saving and applying the sensibility label.
''' </summary>
Public Property ProtectionInformation As Object Implements IPackageInfo.ProtectionInformation
''' <summary>
''' The label id of the sensibility label to apply.
''' </summary>
Public Property ActiveLabelId As String Implements IPackageInfo.ActiveLabelId
End Class
See also¶
- See our sample project.