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](<https://learn.microsoft.com/en-us/information-protection/develop/>).

## Our sample project

Our sample project, [EPPlus.Samples.SensibilityLabels](<https://github.com/EPPlusSoftware/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/](<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](<https://learn.microsoft.com/en-us/purview/purview-fast-track-setup-guides>)  
Add at least one sensitivity label to use in the sample under *Information Protection* - *Sensibility labels* menu.

### Workstation setup &amp; App registration

To use the Microsoft Information Protection API, you need to register an application ("App Registrations") in the [Azure portal](<https://portal.azure.com>) or the [Microsoft Entra admin center](<https://entra.microsoft.com>).  
To setup your application registration and your workstation, please see this guide [Microsoft Information Protection (MIP) SDK setup and configuration](<https://learn.microsoft.com/en-us/information-protection/develop/setup-configure-mip>).  
When the setup is done, in the application registration, add a redirection Uri to: [http://localhost](<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:

```csharp
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; }
    }
```

```vbnet
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](<https://github.com/EPPlusSoftware/EPPlus.Samples.SensibilityLabels>).
