|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Description:
// DocumentRightsManagementManager is an internal API for Mongoose to deal
// with Rights Management.
#pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Packaging;
using System.Security.RightsManagement;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Windows.Threading;
using System.Windows.TrustUI;
using MS.Internal.Documents.Application;
namespace MS.Internal.Documents
{
/// <summary>
/// RightsManagementManager is a internal Avalon class used to expose the RM Document API .
/// </summary>
/// <remarks>
/// This class serves as the controller that is between the UI and the facade for
/// the RM APIs.
/// </remarks>
internal sealed class DocumentRightsManagementManager
{
#region Constructors
//------------------------------------------------------
// Constructors
//------------------------------------------------------
/// <summary>
/// A constructor that takes the RM provider to use
/// </summary>
private DocumentRightsManagementManager(IRightsManagementProvider rmProvider)
{
ArgumentNullException.ThrowIfNull(rmProvider);
_rmProviderCache = rmProvider;
//Create dictionary for Credential Management
//used to map between CredManResources and RM Users
_userMap = new Dictionary<string, RightsManagementUser>();
// notify the documentmanager when the license changes
PublishLicenseChange += DocumentManager.OnModify;
}
#endregion Constructors
#region Internal Methods
//------------------------------------------------------
// Internal Methods
//------------------------------------------------------
/// <summary>
/// Initializes a new singleton instance of the DocumentRightsManagementManager
/// </summary>
/// <param name="rmProvider">The provider underlying the manager</param>
internal static void Initialize(IRightsManagementProvider rmProvider)
{
Trace.SafeWrite(Trace.Rights, "Initializing RightsManagementManager");
System.Diagnostics.Debug.Assert(
_currentManager == null,
"RightsManagementManager initialized twice.");
if (_currentManager == null)
{
_currentManager = new DocumentRightsManagementManager(rmProvider);
}
}
/// <summary>
/// Decrypts the encrypted document using the appropriate credentials.
/// Returns null if the document is not actually encrypted or the
/// encrypted document could not be decrypted.
/// </summary>
/// <returns>A decrypted stream containing an XPS document.</returns>
internal Stream DecryptPackage()
{
// If the RM client is not installed, prompt the user to install it.
// If the user accepts, this will cause a navigation to the install site
// in a separate window.
if (!IsRMClientInstalled())
{
PromptToInstallRM();
// Return null to abort document loading.
return null;
}
Stream returnPackage = null;
if (_rmProvider.IsProtected)
{
Trace.SafeWrite(Trace.Rights, "Document is protected.");
// Try to get a set of credentials so we can try to decrypt
// the document. If we can't choose any credentials at all,
// we can't decrypt the document.
if (ChooseCredentials(true))
{
// See if the set of credentials chosen has permission to
// view the document.
RightsManagementLicense license = GetUseLicense();
if (license != null &&
license.HasPermission(RightsManagementPermissions.AllowView))
{
Trace.SafeWrite(
Trace.Rights,
"User has enough rights; decrypting package.");
try
{
returnPackage = _rmProvider.DecryptPackage();
}
catch (RightsManagementException exception)
{
RightsManagementErrorHandler.HandleOrRethrowException(
RightsManagementOperation.Decrypt,
exception);
}
}
}
}
return returnPackage;
}
/// <summary>
/// Forces an Evaluate which will result in RMPolicy event being fired. This is
/// only valid after a decrypted Package is available (i.e. either the protected
/// package has been decrypted, or the package was never protected).
/// </summary>
internal void Evaluate()
{
RightsManagementStatus calcRMStatus = RightsManagementStatus.Unprotected;
RightsManagementPolicy calcRMPolicy = RightsManagementPolicy.AllowView;
//Check to see if document is protected.
if (_rmProvider.IsProtected)
{
calcRMStatus = RightsManagementStatus.Protected;
// Get the rights granted from the RM license
RightsManagementLicense rmLicense = GetUseLicense();
if (rmLicense == null)
{
calcRMPolicy = RightsManagementPolicy.AllowNothing;
}
else
{
calcRMPolicy = rmLicense.ConvertToPolicy();
}
}
else
{
Trace.SafeWrite(
Trace.Rights,
"Evaluate: Document is not protected; using default policy.");
//There is no RM policy applied to this document.
calcRMPolicy =
RightsManagementPolicy.AllowAnnotate |
RightsManagementPolicy.AllowCopy |
RightsManagementPolicy.AllowSign |
RightsManagementPolicy.AllowPrint |
RightsManagementPolicy.AllowView;
}
//Fire the events.
OnRMStatusChange(calcRMStatus);
OnRMPolicyChange(calcRMPolicy);
}
/// <summary>
/// Sets the EncryptedPackageEnvelope on the provider to a new value
/// and invalidates the saved publish and use licenses if necessary.
/// The package passed can be null to indicate that the new package is
/// not protected.
/// </summary>
/// <param name="newPackage">The new encrypted package (possibly null)
/// </param>
internal void SetEncryptedPackage(EncryptedPackageEnvelope newPackage)
{
bool publishLicenseChanged = true;
try
{
_rmProvider.SetEncryptedPackage(
newPackage,
out publishLicenseChanged);
}
catch (RightsManagementException exception)
{
RightsManagementErrorHandler.HandleOrRethrowException(
RightsManagementOperation.Decrypt,
exception);
// On an exception here we can't continue; rethrow it in an
// XPSViewer exception
throw new XpsViewerException(
SR.XpsViewerRightsManagementException,
exception);
}
if (_rmProvider.IsProtected)
{
// Get a use license for the new package
RightsManagementLicense license = GetUseLicense();
if (license == null ||
!license.HasPermission(RightsManagementPermissions.AllowView))
{
throw new XpsViewerException(
SR.RMProviderExceptionNoRightsToDocument);
}
}
if (publishLicenseChanged)
{
// Fire the event to indicate a new publish license
OnPublishLicenseChange();
}
// Evaluate the status of the new package (to fire events)
Evaluate();
}
/// <summary>
/// Displays the Credential Management UI.
/// </summary>
internal void ShowCredentialManagementUI()
{
ShowCredentialManagementUI(false);
}
/// <summary>
/// Displays the Credential Management UI.
/// </summary>
/// <param name="decrypting">Whether the dialog is being shown in the
/// process of decrypting the document for the first time</param>
/// <returns>The result of the credential management dialog</returns>
internal DialogResult ShowCredentialManagementUI(bool decrypting)
{
DialogResult result = DialogResult.Cancel;
IList<string> accountList = GetCredentialManagementResourceList();
string userAccount = GetDefaultCredentialManagementResource();
RightsManagementUser previousDefaultUser = _rmProvider.GetDefaultCredentials();
_credManagerDialog = new CredentialManagerDialog(accountList, userAccount, this);
result = _credManagerDialog.ShowDialog();
if (_credManagerDialog != null)
{
_credManagerDialog.Dispose();
}
RightsManagementUser newDefaultUser = _rmProvider.GetDefaultCredentials();
// When a new rm account is added a new SecureEnvironment is created in order
// to validate the new account. If a document is currently protected this will
// mean that the existing SecureEnvironment is destroyed, which puts the document
// in an unreliable state. The only way to correct this is to reload the document.
// Check if the document is done decrypting, is protected, and if the
// SecureEnvironment is in an unreliable state or the default user has been
// changed. If so reload the document.
if (!decrypting && _rmProvider.IsProtected &&
(!_isSecureEnvironmentReliable || !previousDefaultUser.Equals(newDefaultUser)))
{
DocumentManager.CreateDefault().Reload(null);
}
// Check if the default user has changed, and setup an environment for this user.
// This case will only be hit for unprotected, or undecrypted documents (by the
// previous if statement) where it is reliable to create a new SecureEnvironment.
else if (!previousDefaultUser.Equals(newDefaultUser))
{
try
{
_rmProvider.InitializeEnvironment(newDefaultUser);
}
catch (RightsManagementException exception)
{
bool handled = RightsManagementErrorHandler.HandleOrRethrowException(
RightsManagementOperation.Other,
exception);
}
}
return result;
}
/// <summary>
/// ShowEnrollment: Displays the RM enrollment.
/// </summary>
internal void ShowEnrollment()
{
DialogResult result = DialogResult.Cancel;
EnrollmentAccountType enrollmentAccountType = EnrollmentAccountType.None;
// skip first page if already enrolled
if (_rmProvider.CurrentUser != null || _rmProvider.GetDefaultCredentials() != null)
{
result = DialogResult.OK;
}
else
{
//Show the First page
using (RMEnrollmentPage1 page1 = new RMEnrollmentPage1())
{
result = page1.ShowDialog();
}
}
if (result == DialogResult.OK)
{
using (RMEnrollmentPage2 page2 = new RMEnrollmentPage2())
{
//Show the second page
result = page2.ShowDialog();
enrollmentAccountType = page2.AccountTypeSelected;
}
}
if (result == System.Windows.Forms.DialogResult.OK)
{
//It is now time to enroll.
Enroll(enrollmentAccountType);
}
}
/// <summary>
/// Enroll.
/// </summary>
/// <param name="accountType">The account type to enroll</param>
/// <returns>Whether or not enrollment succeeded</returns>
internal bool Enroll(EnrollmentAccountType accountType)
{
bool rtn = false;
//User has selected to proceed. Now we need to Enroll. This will
//Require two thing. 1) start the blocking enroll call
//(call QueueWorkItem) and 2) showing a status/progress Dialog while
//we are enrolling.
RMEnrollmentPage3 rmEnrollmentPage3 = new RMEnrollmentPage3();
RightsManagementEnrollThreadInfo rmEnrollThreadInfo = new RightsManagementEnrollThreadInfo();
//Setup Fields
rmEnrollThreadInfo.AccountType = accountType;
rmEnrollThreadInfo.ProgressForm = rmEnrollmentPage3;
// Pass work off so UI doesn't block.
// We use WaitCallback here because that is the delegate that is
// supposed to be passed to QueueUserWorkItem
if (ThreadPool.QueueUserWorkItem(new WaitCallback(RMEnrollThreadProc), rmEnrollThreadInfo))
{
rmEnrollmentPage3.ShowDialog();
rtn = (_rmProvider.CurrentUser != null);
}
// Since we've called the enrollment, the current SecureEnvironment is not reliable.
_isSecureEnvironmentReliable = false;
return rtn;
}
/// <summary>
/// Launches the My Permissions UI.
/// </summary>
internal void ShowPermissions()
{
RightsManagementLicense rmLicense = GetUseLicense();
// A null license means the user has no rights to the document, and
// this should never have been called.
Invariant.Assert(
rmLicense != null,
"Permissions dialog requested with no rights on the document.");
// If this is the owner call ShowPublishing instead.
if (rmLicense.HasPermission(RightsManagementPermissions.AllowOwner))
{
ShowPublishing();
}
else
{
// User wants to view the My Permissions dialog.
RMPermissionsDialog rmPermissionsPage = new RMPermissionsDialog(rmLicense);
rmPermissionsPage.ShowDialog();
if (rmPermissionsPage != null)
{
rmPermissionsPage.Dispose();
}
}
}
/// <summary>
/// Displays the publishing UI.
/// </summary>
internal void ShowPublishing()
{
if (!ChooseCredentials(false))
{
// If there are no credentials, and we can't select any, we
// should just bail out. If there was an error somewhere
// during choosing credentials, an explanatory message box
// should already have been shown.
return;
}
if (_rmProvider.IsProtected)
{
RightsManagementLicense license = GetUseLicense();
if (license == null ||
!(license.HasPermission(RightsManagementPermissions.AllowOwner)))
{
throw new InvalidOperationException(
SR.RMProviderExceptionNotOwnerOfDocument);
}
}
IDictionary<RightsManagementUser, RightsManagementLicense> allRights = null;
if (_rmProvider.IsProtected)
{
allRights = _rmProvider.GetAllAccessRights();
}
// Create the publishing dialog outside of the while loop so that it is only
// constructed once, rather than on each loop.
RMPublishingDialog rmPublish = null;
bool statusChanged = false;
try
{
rmPublish =
new RMPublishingDialog(_rmProvider.CurrentUser, allRights);
// Keep showing the dialog until it succeeds or the user cancels
while (true)
{
DialogResult result = DialogResult.Cancel;
// Display the dialog to the user.
result = rmPublish.ShowDialog();
// If the user canceled out of the dialog, break out of the loop and
// do not apply changes.
if (result != DialogResult.OK)
{
break;
}
bool publishingSuccess = false;
bool publishLicenseChanged = false;
bool exitDialog = false;
// Save the current set of licenses for rollback
_rmProvider.SaveCurrentLicenses();
// If a template exists on the publishing dialog then user has selected
// a template rather than individual settings.
if (rmPublish.Template != null)
{
publishingSuccess = ProcessRMTemplate(rmPublish.Template, out exitDialog);
publishLicenseChanged = true;
}
// Template was not selected, so carry on with individual permissions.
else
{
publishingSuccess = ProcessRMLicenses(
rmPublish.Licenses,
out exitDialog,
out publishLicenseChanged);
}
// Check if a signed publish license was successfully created.
// If not take the appropriate action.
if (!publishingSuccess)
{
// If exitDialog is true, then an error has happened that is severe
// enough to stop running the Publish dialog.
if (exitDialog)
{
return;
}
// Otherwise continue with running the dialog for another attempt
// to get the settings correct.
else
{
continue;
}
}
// If the publish license actually changed during the
// publishing operation, fire the event to indicate this
if (publishLicenseChanged)
{
OnPublishLicenseChange();
}
// Save the protected document.
DocumentManager docManager = DocumentManager.CreateDefault();
bool saveSuccess = false;
if (docManager != null)
{
if (rmPublish.IsSaveAs)
{
saveSuccess = docManager.SaveAs(null);
}
else
{
saveSuccess = docManager.Save(null);
}
}
if (publishingSuccess && !saveSuccess)
{
_rmProvider.RevertToSavedLicenses();
continue;
}
statusChanged =
publishingSuccess && saveSuccess && publishLicenseChanged;
break;
} // end while(true)
}
finally
{
if (rmPublish != null)
{
rmPublish.Dispose();
}
}
// If the status changed, call Evaluate to re-evaluate the RM
// status of the document and fire the appropriate events
if (statusChanged)
{
Evaluate();
}
}
/// <summary>
/// Get User account from credentialManagementResources and sets as default.
/// </summary>
internal void OnCredentialManagementSetDefault(string defaultAccount)
{
//Get user from user name
RightsManagementUser user = _userMap[defaultAccount];
//if found set default
if (user != null)
{
_rmProvider.SetDefaultCredentials(user);
}
}
/// <summary>
/// Removes user from available user list.
/// </summary>
internal void OnCredentialManagementRemove(string accountName)
{
//Get user from hash
RightsManagementUser user = _userMap[accountName];
//if found remove (if this user isn't in use, if it is in use warn user)
if (user != null && !user.Equals(_rmProvider.CurrentUser))
{
try
{
_rmProvider.RemoveCredentials(user);
if (_credManagerDialog != null)
{
//Set the data source for the listbox
_credManagerDialog.SetCredentialManagementList(
GetCredentialManagementResourceList(),
GetDefaultCredentialManagementResource());
}
}
catch (RightsManagementException exception)
{
RightsManagementErrorHandler.HandleOrRethrowException(
RightsManagementOperation.Other,
exception);
// If the remove failed just bail out
return;
}
}
else
{
System.Windows.MessageBox.Show(
SR.RightsManagementWarnErrorFailedToRemoveUser,
SR.RightsManagementWarnErrorTitle,
System.Windows.MessageBoxButton.OK,
System.Windows.MessageBoxImage.Exclamation
);
}
}
/// <summary>
/// Calls the Enrollment and refreshes the listbox.
/// </summary>
internal void OnCredentialManagementShowEnrollment()
{
ShowEnrollment();
if (_credManagerDialog != null)
{
//Set the data source for the listbox
_credManagerDialog.SetCredentialManagementList(
GetCredentialManagementResourceList(),
GetDefaultCredentialManagementResource());
}
}
/// <summary>
/// Prompts the user to see if he or she would like to install the RM Client.
/// If the user so chooses, we will then start a top-level navigation to
/// the external install location for the RM Client.
/// </summary>
internal void PromptToInstallRM()
{
// Notify the user that RM is not installed, and ask
// if they would like to install it now.
System.Windows.MessageBoxResult dialogResult = System.Windows.MessageBox.Show(
SR.RightsManagementWarnErrorRMNotInstalled,
SR.RightsManagementWarnErrorTitle,
System.Windows.MessageBoxButton.YesNo,
System.Windows.MessageBoxImage.Warning);
if (dialogResult == System.Windows.MessageBoxResult.Yes)
{
// Navigate to the install location
const string rmClientInstallLocation = "http://go.microsoft.com/fwlink/?LinkID=18134";
NavigationHelper.NavigateToExternalUri(new Uri(rmClientInstallLocation));
}
}
/// <summary>
/// Gets a List of CredentialManagmentResources.
/// </summary>
internal IList<string> GetCredentialManagementResourceList()
{
_userMap.Clear();
//create list for CredentialManagementResources
IList<string> accountList = new List<string>();
//Loop through each user and create listbox string and add to return list
foreach (RightsManagementUser user in _rmProvider.GetAvailableCredentials())
{
string accountName =
RightsManagementResourceHelper.GetCredentialManagementResources(user);
if (!accountList.Contains(accountName))
{
//Add to map and to list.
_userMap.Add(accountName, user);
accountList.Add(accountName);
}
}
return accountList;
}
/// <summary>
/// Gets default CredentialManagmentResources.
/// </summary>
internal string GetDefaultCredentialManagementResource()
{
string userAccount = null;
RightsManagementUser user = _rmProvider.GetDefaultCredentials();
if (user != null)
{
userAccount = RightsManagementResourceHelper.GetCredentialManagementResources(user);
}
return userAccount;
}
#endregion Internal Methods
#region Internal Properties
//------------------------------------------------------
// Internal Properties
//------------------------------------------------------
/// <summary>
/// Gets the current singleton instance of the DocumentRightsManagementManager
/// </summary>
internal static DocumentRightsManagementManager Current
{
get
{
return _currentManager;
}
}
/// <summary>
/// Gets the current publish license associated with the document.
/// </summary>
internal PublishLicense PublishLicense
{
// Commented out since not used, but left to show that a set only property
// was implemented by design.
// get { return _rmProvider.CurrentPublishLicense; }
set
{
if (_rmProvider.CurrentPublishLicense != value)
{
_rmProvider.CurrentPublishLicense = value;
OnPublishLicenseChange();
}
}
}
/// <summary>
/// Gets whether or not the user has permission to save the document.
/// </summary>
internal bool HasPermissionToSave
{
get
{
return
!_rmProvider.IsProtected ||
GetUseLicense().HasPermission(RightsManagementPermissions.AllowCopy);
}
}
/// <summary>
/// Gets whether or not the user has permission to edit the document.
/// </summary>
internal bool HasPermissionToEdit
{
get
{
return
!_rmProvider.IsProtected ||
GetUseLicense().HasPermission(RightsManagementPermissions.AllowEdit);
}
}
/// <summary>
/// Returns true if we find the RM Client is installed on the machine.
/// </summary>
/// <remarks>
/// We only make the actual check if RM is installed
/// once per application instance. Is this okay? Or should we keep
/// checking to see if this gets installed over the course of the
/// application?
/// </remarks>
internal bool IsRMInstalled
{
get
{
if (! _determinedRMInstallState)
{
// Find out if the RM Client is installed.
_isRMInstalled = IsRMClientInstalled();
_determinedRMInstallState = true;
}
return _isRMInstalled;
}
}
#endregion Internal Properties
#region Internal Event
//------------------------------------------------------
// Internal Event
//------------------------------------------------------
internal event RMStatusChangeHandler RMStatusChange;
internal event EventHandler PublishLicenseChange;
internal event RMPolicyChangeHandler RMPolicyChange;
#endregion Internal Event
#region Internal Delegate
//------------------------------------------------------
// Internal Delegate
//------------------------------------------------------
internal delegate void RMStatusChangeHandler(object sender,
RightsManagementStatusEventArgs args
);
internal delegate void RMPolicyChangeHandler(object sender,
RightsManagementPolicyEventArgs args
);
#endregion Internal Delegate
#region Private Methods
//------------------------------------------------------
// Private Methods
//------------------------------------------------------
/// <summary>
/// Saves the use license back into the package.
/// </summary>
private void SaveUseLicense()
{
//This action should occur after other work has been completed.
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
(DispatcherOperationCallback)delegate(object arg)
{
DocumentManager docManager = DocumentManager.CreateDefault();
if (docManager.CanSave)
{
Trace.SafeWrite(
Trace.Rights,
"Re-opening document for edit before saving Use License");
docManager.EnableEdit(null);
}
// Check if saving is still possible (i.e. the file could
// be reopened for edit).
//
// This breaks encapsulation -- we happen to know that
// docManager.CanSave indicates only that the file could
// be opened writeable. We also know that saving an RM
// protected document will save the use license back to
// the file.
if (docManager.CanSave)
{
Trace.SafeWrite(
Trace.Rights,
"Opened original file writeable; saving Use License");
docManager.Save(null);
}
return null;
}, null /* DispatcherOperation argument, none specified */);
}
/// <summary>
/// Determines if the RM client is installed by looking for
/// MSDRM.DLL in the user's system folder.
/// </summary>
/// <returns>Whether RM Client DLL, msdrm.dll, was found</returns>
private bool IsRMClientInstalled()
{
bool foundRMClient = false;
// Build the path for the MSDRM.DLL
string systemPath = Environment.GetFolderPath(Environment.SpecialFolder.System);
// Append the DLL filename.
const string msdrmDLLName = "MSDRM.DLL";
string msdrmdllPath = Path.Combine(systemPath, msdrmDLLName);
// Check if the DLL exists.
foundRMClient = File.Exists(msdrmdllPath);
return foundRMClient;
}
/// <summary>
/// Attempts to automatically choose the credentials to use to access
/// the RM-protected document or add RM protection to an existing
/// document.
/// </summary>
/// <param name="decrypting">Whether or not we are choosing credentials
/// to decrypt or encrypt a document</param>
/// <returns>Whether or not credentials were successfully chosen</returns>
private bool ChooseCredentials(bool decrypting)
{
bool foundCredentials = false;
// If there already is a user selected there is no need to select
// credentials again
if (_rmProvider.CurrentUser != null)
{
return true;
}
//Get default user
RightsManagementUser user = _rmProvider.GetDefaultCredentials();
// Save whether or not RM has been initialized so that we don't do
// it twice
bool initialized = false;
//Check to see if we have a user. If we don't we need to enroll.
if (user == null)
{
//Show enrollment UI
ShowEnrollment();
// Get the current user. If we don't have one after enrollment
// that means the user cancelled enrollment or something else
// went wrong.
user = _rmProvider.CurrentUser;
// If the enrollment was successful
if (user != null)
{
// Set the enrolled user as the default user in the registry
_rmProvider.SetDefaultCredentials(user);
initialized = true;
}
}
// Now we will see if this user account has rights for the document
while (user != null)
{
if (!initialized)
{
try
{
// Attempt to initialize the environment
_rmProvider.InitializeEnvironment(user);
initialized = true;
}
catch (RightsManagementException exception)
{
bool handled =
RightsManagementErrorHandler.HandleOrRethrowException(
decrypting ?
RightsManagementOperation.Initialize :
RightsManagementOperation.Other,
exception);
// If the error couldn't be handled cleanly, give up
if (!handled)
{
break;
}
}
}
// If the initialization succeeded and the document is
// protected by RM, check if the current user can view the
// document
if (initialized && _rmProvider.IsProtected)
{
RightsManagementLicense license = GetUseLicense();
// Check if the user has permissions to view the document
if (license != null &&
license.HasPermission(RightsManagementPermissions.AllowView))
{
foundCredentials = true;
break;
}
// The current user doesn't have permissions
else
{
// Ask the user whether we should try a different set of
// credentials or just give up.
System.Windows.MessageBoxResult result =
System.Windows.MessageBox.Show(
SR.RightsManagementWarnErrorNoPermission,
SR.RightsManagementWarnErrorTitle,
System.Windows.MessageBoxButton.YesNo,
System.Windows.MessageBoxImage.Exclamation
);
// If the user didn't want to continue, bail out
if (result != System.Windows.MessageBoxResult.Yes)
{
break;
}
}
}
// If the document is not protected, there is no need to check
// whether the user can view the document as long as the
// environment was properly initialized
else if (initialized)
{
foundCredentials = true;
break;
}
// The current default user doesn't have permission to view
// this container, or there was an error when attempting to
// initialize the environment with the current default user.
//
// Show the CredMan to allow user to change default account
if(ShowCredentialManagementUI(decrypting) != DialogResult.OK)
{
break;
}
//Get new default user.
user = _rmProvider.GetDefaultCredentials();
// The provider's CurrentUser property is set only when an
// environment has been initialized. As a result comparing the
// credentials we will use to the provider's CurrentUser can
// tell us whether or not the provider has been initialized for
// that user.
initialized = (user.Equals(_rmProvider.CurrentUser));
}
return foundCredentials;
}
/// <summary>
/// Retrieves the use license for the document and returns the rights granted to
/// the current user. Returns null if the user does not have rights to the
/// document.
/// </summary>
/// <returns>The rights granted to the user.</returns>
private RightsManagementLicense GetUseLicense()
{
if (_rmProvider.IsProtected &&
_rmProvider.CurrentUseLicense == null)
{
Invariant.Assert(
_rmProvider.CurrentUser != null,
"No user for whom to get a license.");
try
{
if (_rmProvider.LoadUseLicense())
{
_rmProvider.BindUseLicense();
}
else if (_rmProvider.AcquireUseLicense())
{
// We have just acquired a new use license; this needs to be stored back
// into the package.
_rmProvider.BindUseLicense();
SaveUseLicense();
}
else
{
// No use license was found, which means that the
// current user has no rights to the document. We
// return null and let the caller decide what to do.
}
}
catch (RightsManagementException exception)
{
// Whether or not the exception was handled is irrelevant,
// since if the use license wasn't bound, the function will
// return null
RightsManagementErrorHandler.HandleOrRethrowException(
RightsManagementOperation.Decrypt,
exception);
}
}
return _rmProvider.CurrentUseLicense;
}
/// <summary>
/// Checks if the grants listed in the licenses passed in as an argument are
/// different from the ones already granted on the document.
/// </summary>
/// <param name="newGrants">A list of new grants to compare with the existing
/// set</param>
/// <exception cref="System.Security.RightsManagement.RightsManagementException">
/// Can be thrown by the RM APIs when there is an error when getting
/// all existing access rights on the document</exception>
/// <returns>Whether or not the new grants are different</returns>
private bool HasPublishLicenseChanged(IList<RightsManagementLicense> newGrants)
{
// If the desired state is that the document will not be protected (i.e. no
// licenses are specified), we check if the document was already not
// protected.
if (newGrants == null || newGrants.Count == 0)
{
return _rmProvider.IsProtected;
}
// Check if we are adding protection to an unprotected document
if (!_rmProvider.IsProtected)
{
return true;
}
// This might throw, but it will be caught in the caller
IDictionary<RightsManagementUser, RightsManagementLicense> allRights =
_rmProvider.GetAllAccessRights();
// Check if the number of licenses is different
if (newGrants.Count != allRights.Count)
{
return true;
}
// Look through each license to see if it is different from the old ones.
// We already know that the number of licenses is the same in both lists of
// licenses, so any other differences will be detected here.
foreach (RightsManagementLicense newLicense in newGrants)
{
if (!allRights.ContainsKey(newLicense.LicensedUser))
{
return true;
}
RightsManagementLicense existingLicense = allRights[newLicense.LicensedUser];
// Check if both licenses grant the same permissions
if (newLicense.LicensePermissions !=
existingLicense.LicensePermissions)
{
return true;
}
if (DateTime.Compare(
newLicense.ValidUntil,
existingLicense.ValidUntil) != 0)
{
return true;
}
if (!Uri.Equals(
newLicense.ReferralInfoUri,
existingLicense.ReferralInfoUri))
{
return true;
}
}
return false;
}
/// <summary>
/// The thread operation that enrolls a new Rights Management user.
/// </summary>
private void RMEnrollThreadProc(Object stateInfo)
{
Trace.SafeWrite(Trace.Rights, "Enrollment thread started.");
//Get Thread Info
RightsManagementEnrollThreadInfo rmEnrollThreadInfo = (RightsManagementEnrollThreadInfo)stateInfo;
//Enroll
try
{
_rmProvider.InitializeEnvironment(rmEnrollThreadInfo.AccountType);
//Enrollment is done. Let's close the Progress dialog.
rmEnrollThreadInfo.ProgressForm.Invoke(new MethodInvoker(rmEnrollThreadInfo.ProgressForm.Close));
}
catch (RightsManagementException exception)
{
rmEnrollThreadInfo.ProgressForm.Invoke(new MethodInvoker(rmEnrollThreadInfo.ProgressForm.Close));
RightsManagementOperation operation =
RightsManagementOperation.Other;
if (rmEnrollThreadInfo.AccountType == EnrollmentAccountType.NET)
{
operation = RightsManagementOperation.PassportActivation;
}
RightsManagementErrorHandler.HandleOrRethrowException(
operation,
exception);
}
// Close the progress dialog in the event of any exception. This prevents the
// dialog from remaining open when the error page appears. We cannot use a finally
// block here because in the case of an uncaught exception the unhandled exception
// handler will get the exception (and show the error page) before the block runs.
catch
{
rmEnrollThreadInfo.ProgressForm.Invoke(new MethodInvoker(rmEnrollThreadInfo.ProgressForm.Close));
throw;
}
Trace.SafeWrite(Trace.Rights, "Enrollment thread ended.");
}
/// <summary>
/// Creates an appropriate EventArgs and sends the RMStatusChange event.
/// </summary>
/// <param name="newStatus">The RM status to put in the EventArgs
/// </param>
private void OnRMStatusChange(RightsManagementStatus newStatus)
{
Trace.SafeWrite(Trace.Rights, "RightsManagementStatus changed.");
RightsManagementStatusEventArgs args = new RightsManagementStatusEventArgs(
newStatus);
RaiseRMStatusChange(args);
}
/// <summary>
/// Raises an RMStatusChange event with the given arguments.
/// </summary>
/// <param name="args">The event arguments</param>
private void RaiseRMStatusChange(RightsManagementStatusEventArgs args)
{
if (RMStatusChange != null)
{
RMStatusChange(this, args);
}
}
/// <summary>
/// Creates an appropriate EventArgs and sends the RMPolicyChange event.
/// </summary>
/// <param name="newPolicy">The RM policy to put in the EventArgs
/// </param>
private void OnRMPolicyChange(RightsManagementPolicy newPolicy)
{
Trace.SafeWrite(Trace.Rights, "RightsManagementPolicy changed.");
RightsManagementPolicyEventArgs args = new RightsManagementPolicyEventArgs(newPolicy);
RaiseRMPolicyChange(args);
}
/// <summary>
/// Raises an RMPolicyChange event with the given arguments.
/// </summary>
/// <param name="args">The event arguments</param>
private void RaiseRMPolicyChange(RightsManagementPolicyEventArgs args)
{
if (RMPolicyChange != null)
{
RMPolicyChange(this, args);
}
}
/// <summary>
/// Fires the event that indicates that the publish license has changed.
/// </summary>
private void OnPublishLicenseChange()
{
Trace.SafeWrite(Trace.Rights, "Publish License changed.");
RaisePublishLicenseChange(null);
}
/// <summary>
/// Raises the PublishLicenseChange with the given arguments.
/// </summary>
/// <param name="args">The event arguments</param>
private void RaisePublishLicenseChange(EventArgs args)
{
if (PublishLicenseChange != null)
{
PublishLicenseChange(this, args);
}
}
/// <summary>
/// Using the provided list of licenses, attempt to setup a signed
/// publish license for the document.
/// </summary>
/// <param name="licenses">The licenses to process.</param>
/// <param name="exitDialog">True if an error has occurred that should
/// exit the RMPublishing dialog</param>
/// <param name="publishLicenseChanged">True if the grants passed in
/// are different from the ones in the document</param>
/// <returns>True on success, false on failure.</returns>
private bool ProcessRMLicenses(
IList<RightsManagementLicense> licenses,
out bool exitDialog,
out bool publishLicenseChanged)
{
// By default exitDialog is true so that on unhandled errors the publish
// dialog will not reload.
exitDialog = true;
publishLicenseChanged = true;
try
{
publishLicenseChanged = HasPublishLicenseChanged(licenses);
}
catch (RightsManagementException exception)
{
bool handled =
RightsManagementErrorHandler.HandleOrRethrowException(
RightsManagementOperation.Other,
exception);
// If there was an exception (that could not be
// handled) that occurred in detecting whether the
// publish license changed, bail out
if (!handled)
{
return false;
}
}
// If the new publish license is unchanged don't bother
// doing anything else
if (publishLicenseChanged)
{
if (licenses != null && licenses.Count > 0)
{
try
{
_rmProvider.GenerateUnsignedPublishLicense(licenses);
_rmProvider.SignPublishLicense();
}
catch (RightsManagementException exception)
{
bool handled =
RightsManagementErrorHandler.HandleOrRethrowException(
RightsManagementOperation.Other,
exception);
// If the exception was handled, don't exit the dialog
if (handled)
{
exitDialog = false;
}
// Stop processing and post dialog.
return false;
}
}
else
{
this.PublishLicense = null;
}
}
// If we made it this far then it is considered success
exitDialog = false;
return true;
}
/// <summary>
/// Using the provided template filename, attempt to load the template from
/// disk and setup a signed publish license from it.
/// </summary>
/// <param name="templateFilename">The template file to load.</param>
/// <param name="exitDialog">Used to determine if the RMPublishing
/// dialog should exit.</param>
/// <returns>True on success, false on failure.</returns>
private bool ProcessRMTemplate(Uri templateFilename, out bool exitDialog)
{
string template = string.Empty;
// With the current template system we should never bail on the dialog
// as we will inform the user and allow them to select again.
exitDialog = false;
try
{
// Load the template from file.
template = GetTemplateFromFile(templateFilename);
}
// This generic catch allows all failures of loading the file to be
// directed to our central error handler which will show the
// appropriate error message to the user. Since this operation is
// classified as a critical operation, the error handler will re-throw
// the exception to allow it to be handled higher on the stack if it
// is fatal (which includes all exceptions not specifically handled
// by the error handler).
#pragma warning suppress 56500 // suppress PreSharp Warning 56500: Avoid `swallowing errors by catching non-specific exceptions..
catch (Exception exception)
{
// This exception will be thrown if there is a problem
// reading the template from the file.
// We need to handle the method and determine the
// appropriate action.
RightsManagementErrorHandler.HandleOrRethrowException(
RightsManagementOperation.TemplateAccess,
exception);
// Since any exception means the file was not loaded, bail out.
return false;
}
// Ensure that template was actually loaded.
if (!string.IsNullOrEmpty(template))
{
try
{
// Generate the license from the template.
_rmProvider.GenerateUnsignedPublishLicense(template);
_rmProvider.SignPublishLicense();
}
catch (RightsManagementException exception)
{
// This exception will be thrown if there is a problem
// either parsing/loading the template, or signing it.
// In either case a message should be displayed informing
// the user about the invalid template.
RightsManagementErrorHandler.HandleOrRethrowException(
RightsManagementOperation.TemplateAccess,
exception);
// Since any exception means the file was not loaded, bail out.
return false;
}
}
else
{
// The template could not be loaded correctly from disk
// (could be that it was removed, permissions are incorrect,
// network down, etc), so display an error message.
RightsManagementErrorHandler.HandleOrRethrowException(
RightsManagementOperation.TemplateAccess,
new FileFormatException());
// Since any exception means the file was not loaded, bail out.
return false;
}
// Set exitDialog to false to continue with the signing process.
exitDialog = false;
return true;
}
/// <summary>
/// Open a template file and attempt to load it into _template.
/// </summary>
/// <param name="templateFilename">The filename (with path) to load.</param>
/// <returns>The text string read from the file if successful, otherwise
/// string.Empty</returns>
private string GetTemplateFromFile(Uri templateFilename)
{
// Set the default value, this value will remain if any of the file
// loading fails.
string template = string.Empty;
// If a template exists on the publishing dialog then user has selected
// a template rather than individual settings.
if (templateFilename != null)
{
// Save the path local to this method to guarantee it does not
// change between the assert and the file access
string templateLocalPath = templateFilename.LocalPath;
// The RM client requires that templates are in UTF16
// (Encoding.Unicode) format.
template = File.ReadAllText(templateLocalPath, Encoding.Unicode);
}
return template;
}
#endregion Private Methods
#region Private Properties
//------------------------------------------------------
// Private Properties
//------------------------------------------------------
/// <summary>
/// Setup as a clr property to transparently use CriticalDataForSet.
/// </summary>
private IRightsManagementProvider _rmProvider
{
get
{
return _rmProviderCache;
}
}
#endregion Private Properties
#region Private Fields
//------------------------------------------------------
// Private Fields
//------------------------------------------------------
private static DocumentRightsManagementManager _currentManager;
private IRightsManagementProvider _rmProviderCache;
/// <summary>
/// A handle to a currently open instance of the credential manager dialog
/// </summary>
private CredentialManagerDialog _credManagerDialog;
/// <summary>
/// True if we have yet determined whether the RM client is installed.
/// We cache this to keep us from checking if the RM Client is installed
/// over and over.
/// </summary>
private bool _determinedRMInstallState = false;
/// <summary>
/// True if the RM client is installed.
/// </summary>
private bool _isRMInstalled = false;
/// <summary>
/// False if the SecureEnvironment is not in a reliable state (ie need to reload).
/// </summary>
private bool _isSecureEnvironmentReliable = true;
/// <summary>
/// A dictionary mapping user names to RightsManagementUser objects.
/// </summary>
private IDictionary<string, RightsManagementUser> _userMap;
#endregion Private Fields
#region Nested Class
//------------------------------------------------------
// Nested Class
//------------------------------------------------------
/// <summary>
/// RMStatusEventArgs, object used when firing RMStatus change.
/// </summary>
public class RightsManagementStatusEventArgs : EventArgs
{
#region Constructors
//------------------------------------------------------
// Constructors
//------------------------------------------------------
/// <summary>
/// The constructor
/// </summary>
/// <param name="rmStatus">Rights Management status</param>
/// <param name="statusResources">Resources containing other information
/// relevant to the document RM status</param>
public RightsManagementStatusEventArgs(
RightsManagementStatus rmStatus)
{
_rmStatus = rmStatus;
_statusResourcesLoaded = false;
}
#endregion Constructors
#region Public Properties
//------------------------------------------------------
// Public Properties
//------------------------------------------------------
/// <summary>
/// The Rights Management status
/// </summary>
public RightsManagementStatus RMStatus
{
get { return _rmStatus; }
}
/// <summary>
/// Property to get the StatusResources. To improve performance the resources are not loaded
/// until the first time they are accessed.
/// </summary>
public DocumentStatusResources StatusResources
{
get
{
if (!_statusResourcesLoaded)
{
_statusResources = RightsManagementResourceHelper.GetDocumentLevelResources(_rmStatus);
_statusResourcesLoaded = true;
}
return _statusResources;
}
}
#endregion Public Properties
#region Private Fields
//------------------------------------------------------
// Private Fields
//------------------------------------------------------
private RightsManagementStatus _rmStatus;
private DocumentStatusResources _statusResources;
private bool _statusResourcesLoaded;
#endregion Private Fields
}
/// <summary>
/// RMPolicyEventArgs, object used when firing RMPolicy change.
/// </summary>
public class RightsManagementPolicyEventArgs : EventArgs
{
#region Constructors
//------------------------------------------------------
// Constructors
//------------------------------------------------------
/// <summary>
/// The constructor
/// </summary>
internal RightsManagementPolicyEventArgs(RightsManagementPolicy rmPolicy)
{
_rmPolicy = rmPolicy;
}
#endregion Constructors
#region Public Properties
//------------------------------------------------------
// Public Properties
//------------------------------------------------------
/// <summary>
/// Property to get/set the RMPolicy
/// </summary>
public RightsManagementPolicy RMPolicy
{
get { return _rmPolicy; }
}
#endregion Public Properties
#region Private Fields
//------------------------------------------------------
// Private Fields
//------------------------------------------------------
private RightsManagementPolicy _rmPolicy;
#endregion Private Fields
}
/// <summary>
/// The data passed to the Rights Management enrollment thread.
/// </summary>
private struct RightsManagementEnrollThreadInfo
{
/// <summary>
/// The account type to enroll
/// </summary>
public EnrollmentAccountType AccountType;
/// <summary>
/// A handle to the form that displays enrollment progress
/// </summary>
public Form ProgressForm;
}
#endregion Nested Class
}
}
|