| File: MS\Internal\Documents\Application\RightsController.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationUI\PresentationUI.csproj (PresentationUI) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // Description: Responsible for the lifecycle of the RightsDocument and the actions that can // be performed on it. using System; using System.IO; using System.IO.Packaging; using System.Security.RightsManagement; namespace MS.Internal.Documents.Application { /// <summary> /// Responsible for the lifecycle of the RightsDocument and the actions that can /// be performed on it. /// <see cref="MS.Internal.Documents.Application.IDocumentController"/> /// </summary> /// <remarks> /// All IDocumentController methods are expected to throw if provided /// a document that is not a RightsDocument. Users of the IDocumentController /// interface are expected to use the IChainOfResponsibiltyNode method before /// calling into the IDocumentController methods to avoid runtime errors. /// </remarks> internal class RightsController : IDocumentController, IDisposable { #region IDocumentController Members //-------------------------------------------------------------------------- // IDocumentController Members //-------------------------------------------------------------------------- /// <summary> /// <see cref="MS.Internal.Documents.Application.IDocumentController"/> /// </summary> bool IDocumentController.EnableEdit(Document document) { RightsDocument doc = (RightsDocument)document; // see class remarks on why this is ok Stream ciphered = doc.Dependency.Workspace; Stream clear = ciphered; bool canEdit = DocumentRightsManagementManager.Current.HasPermissionToEdit; EncryptedPackageEnvelope encryptedPackage = null; // If editing the document is allowed (i.e. the document is either // not RM protected, or the user has permission to edit it), create // a temporary editing file if (canEdit) { try { encryptedPackage = _provider.EncryptPackage(ciphered); if (encryptedPackage != null) { clear = DecryptEnvelopeAndSuppressStream( encryptedPackage, canEdit); doc.WorkspacePackage = encryptedPackage; } } catch (RightsManagementException exception) { RightsManagementErrorHandler.HandleOrRethrowException( RightsManagementOperation.Other, exception); // Bail out return true; } if (encryptedPackage != null) { Trace.SafeWrite( Trace.Rights, "Editing package is RM protected."); } else { Trace.SafeWrite( Trace.Rights, "Editing package is unprotected."); } doc.WorkspaceProxy = new StreamProxy(clear); } else { Trace.SafeWrite( Trace.Rights, "Did not create editing package because user does not have permission to edit."); } return canEdit; } /// <summary> /// <see cref="MS.Internal.Documents.Application.IDocumentController"/> /// </summary> bool IDocumentController.Open(Document document) { RightsDocument doc = (RightsDocument)document; // see class remarks on why this is ok Stream ciphered = doc.Dependency.Source; Stream clear = ciphered; bool isSourceProtected = doc.IsSourceProtected(); if (isSourceProtected) { // Do not catch exceptions here - there can be no mitigation EncryptedPackageEnvelope envelope = OpenEnvelopeOnStream(ciphered); PackageProperties properties = new SuppressedProperties(envelope); doc.SourcePackage = envelope; DocumentProperties.Current.SetRightsManagedProperties(properties); } RightsManagementProvider provider = new RightsManagementProvider(doc.SourcePackage); _provider = provider; try { DocumentRightsManagementManager.Initialize(provider); DocumentRightsManagementManager.Current.PublishLicenseChange += new EventHandler(delegate(object sender, EventArgs args) { Trace.SafeWrite( Trace.Rights, "Disabling file copy for current document."); doc.IsFileCopySafe = false; DocumentManager.CreateDefault().EnableEdit(null); }); if (isSourceProtected) { clear = DocumentRightsManagementManager.Current.DecryptPackage(); if (clear != null) { clear = new RightsManagementSuppressedStream( clear, DocumentRightsManagementManager.Current.HasPermissionToEdit) { // Reset the position of the stream since GetPackageStream will // create a package and move the stream pointer somewhere else Position = 0 }; } else { Trace.SafeWrite( Trace.Rights, "You do not have rights for the current document."); return false; } } } catch { // If anything failed here, we cannot use the provider any longer, // so we can dispose it provider.Dispose(); _provider = null; throw; } if (clear != null) { doc.SourceProxy = new StreamProxy(clear); } else { // If decryption failed, we can no longer do anything with the // provider instance or the current RM manager provider.Dispose(); _provider = null; } return true; } /// <summary> /// <see cref="MS.Internal.Documents.Application.IDocumentController"/> /// </summary> bool IDocumentController.Rebind(Document document) { RightsDocument doc = (RightsDocument)document; // see class remarks on why this is ok Stream ciphered = doc.Dependency.Source; Stream clear = ciphered; if (doc.IsRebindNeeded) { if (doc.SourcePackage != null) { CloseEnvelope(doc.SourcePackage); doc.SourcePackage = null; } EncryptedPackageEnvelope envelope = null; PackageProperties properties = null; bool isSourceProtected = doc.IsSourceProtected(); if (isSourceProtected) { envelope = OpenEnvelopeOnStream(ciphered); doc.SourcePackage = envelope; properties = new SuppressedProperties(envelope); } DocumentProperties.Current.SetRightsManagedProperties(properties); DocumentRightsManagementManager.Current.SetEncryptedPackage(envelope); if (isSourceProtected) { clear = DocumentRightsManagementManager.Current.DecryptPackage(); if (clear != null) { clear = new RightsManagementSuppressedStream( clear, DocumentRightsManagementManager.Current.HasPermissionToEdit) { // Reset the position of the stream since GetPackageStream will // create a package and move the stream pointer somewhere else Position = 0 }; } else { Trace.SafeWrite( Trace.Rights, "You do not have rights for the current document."); return false; } } doc.SourceProxy.Target = clear; } return true; } /// <summary> /// <see cref="MS.Internal.Documents.Application.IDocumentController"/> /// </summary> bool IDocumentController.SaveAsPreperation(Document document) { if (!DocumentRightsManagementManager.Current.HasPermissionToSave) { Trace.SafeWrite(Trace.Rights, "Can not save a package when we do not have permission."); return false; } return ((IDocumentController)this).SavePreperation(document); } /// <summary> /// <see cref="MS.Internal.Documents.Application.IDocumentController"/> /// </summary> bool IDocumentController.SaveCommit(Document document) { RightsDocument doc = (RightsDocument)document; // see class remarks on why this is ok if (doc.DestinationPackage != null) { CloseEnvelope(doc.DestinationPackage); doc.DestinationPackage = null; Trace.SafeWrite(Trace.File, "Destination EncryptedPackageEnvelope closed."); } return true; } /// <summary> /// <see cref="MS.Internal.Documents.Application.IDocumentController"/> /// </summary> bool IDocumentController.SavePreperation(Document document) { bool handled = false; RightsDocument doc = (RightsDocument)document; // see class remarks on why this is ok Stream ciphered = doc.Dependency.Destination; Stream clear = ciphered; // We protect the actual decrypted content (if necessary) using the // RightsManagementSuppressedStream that prevents callers from writing // to it. This still allows us to save an acquired use license back to // the package even if the user does not have the permissions to modify // the document itself. It also means that there is no need to check if // the user has permission to save before starting the operation. // If we are encrypted and the destination stream is identical to the // source stream, there is no need to create a new envelope. Instead we // can simply open the envelope on the destination stream, which // already contains the complete encrypted envelope that was the source // file. // // It doesn't actually matter to this class why the flag was set, but // we know the destination is identical to the source in two situations: // 1) The source and the destination are the same file, and since the // publish license didn't change there is no need to write to a // temporary file first // 2) The destination file was copied directly from the source file. if (doc.IsDestinationProtected() && doc.IsDestinationIdenticalToSource) { // we can't reuse protections because EncryptedPackage caches the // original permissions for the stream (if we re-open the package r/w // the Encrypted package will still be r/o) doc.DestinationPackage = OpenEnvelopeOnStream( doc.Dependency.Destination); doc.DestinationPackage.RightsManagementInformation.CryptoProvider = doc.SourcePackage.RightsManagementInformation.CryptoProvider; clear = DecryptEnvelopeAndSuppressStream( doc.DestinationPackage, DocumentRightsManagementManager.Current.HasPermissionToEdit); doc.DestinationProxy = new StreamProxy(clear); // save the use license in case the user acquired one _provider.SaveUseLicense(doc.DestinationPackage); handled = true; Trace.SafeWrite( Trace.Rights, "Reused CryptoProvider as underlying stream is the same."); } else // we are not protected and/or the RM protections have changed { bool canEdit = DocumentRightsManagementManager.Current.HasPermissionToEdit; // canEdit should always be true here - either the document is not // protected, or the protections have been changed. In the latter // case, the user has to have Owner permissions to change the // protections, and that of course includes Edit permissions. Invariant.Assert( canEdit, "Cannot save with changes if Edit permission was not granted."); EncryptedPackageEnvelope encryptedPackage = _provider.EncryptPackage(ciphered); // the destination is intended to be encrypted when a non-null // value is returned if (encryptedPackage != null) { clear = DecryptEnvelopeAndSuppressStream( encryptedPackage, canEdit); doc.DestinationPackage = encryptedPackage; } Trace.SafeWriteIf( encryptedPackage == null, Trace.Rights, "Destination package is unprotected."); doc.DestinationProxy = new StreamProxy(clear); // If the destination file is not identical to the source file, we // need to copy the (possibly decrypted) source stream to the // destination here. if (!doc.IsDestinationIdenticalToSource) { StreamHelper.CopyStream(doc.Source, doc.Destination); doc.DestinationProxy.Flush(); Trace.SafeWrite( Trace.Rights, "Copied Source contents to Destination."); } handled = true; } return handled; } #endregion IDocumentController Members #region IChainOfResponsibiltyNode<Document> Members //-------------------------------------------------------------------------- // IChainOfResponsibiltyNode<Document> Members //-------------------------------------------------------------------------- /// <summary> /// <see cref="MS.Internal.Documents.Application.IChainOfResponsibiltyNode<T>"/> /// </summary> bool IChainOfResponsibiltyNode<Document>.IsResponsible(Document subject) { return subject is RightsDocument; } #endregion IChainOfResponsibiltyNode<Document> Members #region IDisposable Members //-------------------------------------------------------------------------- // IDisposable Members //-------------------------------------------------------------------------- /// <summary> /// Dispose /// </summary> void IDisposable.Dispose() { IDisposable provider = _provider as IDisposable; provider?.Dispose(); _provider = null; GC.SuppressFinalize(this); } #endregion IDisposable Members #region Private Methods //-------------------------------------------------------------------------- // Private Methods //-------------------------------------------------------------------------- /// <summary> /// Opens an EncryptedPackageEnvelope on a given stream. /// </summary> /// <param name="ciphered">The encrypted stream</param> /// <returns>An EncryptedPackageEnvelope on the stream</returns> /// <remarks> /// This function exists to centralize the asserts needed to use encrypted /// package envelopes. /// </remarks> private static EncryptedPackageEnvelope OpenEnvelopeOnStream(Stream ciphered) { return EncryptedPackageEnvelope.Open(ciphered); } /// <summary> /// Retrieves and creates a rights management suppressed stream around the /// decrypted stream from a given encrypted package envelope. The stream /// returned should not be handed out to untrusted code. /// </summary> /// <param name="envelope">The envelope to decrypt and suppress</param> /// <param name="allowWrite">True if editing the suppressed stream should /// be allowed</param> /// <returns>The new demand-suppressed stream</returns> /// <remarks> /// This function exists to centralize the asserts needed to use encrypted /// package envelopes. /// </remarks> private static Stream DecryptEnvelopeAndSuppressStream( EncryptedPackageEnvelope envelope, bool allowWrite) { Stream clear = null; clear = envelope.GetPackageStream(); clear = new RightsManagementSuppressedStream(clear, allowWrite) { // Reset the position of the stream since GetPackageStream will // create a package and move the stream pointer somewhere else Position = 0 }; return clear; } /// <summary> /// Close the EncryptedPackageEnvelope passed in as an argument. /// </summary> /// <param name="envelope">The envelope to close</param> /// <remarks> /// This function exists to centralize the asserts needed to use encrypted /// package envelopes. /// </remarks> private static void CloseEnvelope(EncryptedPackageEnvelope envelope) { envelope.Close(); } #endregion Private Methods #region Private Fields //-------------------------------------------------------------------------- // Private Fields //-------------------------------------------------------------------------- private static IRightsManagementProvider _provider; #endregion Private Fields } }