File: Base\System\Security\RightsManagement\SecureEnvironment.cs
Project: wpf\src\WindowsBase.csproj (WindowsBase)
//-----------------------------------------------------------------------------
//
// <copyright file="SecureEnvironment.cs" company="Microsoft">
//    Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// Description:  Secure Environment class is a starting point for Managed RM APIs 
//   It provides basic services of enumerating User Certificates, Initializing Environment 
//
// History:
//  06/01/2005: IgorBel :   Initial Implementation 
//
//-----------------------------------------------------------------------------
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using MS.Internal.Security.RightsManagement;
using SecurityHelper=MS.Internal.WindowsBase.SecurityHelper; 
using MS.Internal;
using System.Security.Permissions;           
 
using SR=MS.Internal.WindowsBase.SR;
using SRID=MS.Internal.WindowsBase.SRID;
 
 
namespace System.Security.RightsManagement 
{
    /// <summary>
    /// This class represent a client session, which used in activation, binding  and other function calls.
    /// </summary>
    /// <SecurityNote>
    ///     Critical:    This class expose access to methods that eventually do one or more of the the following
    ///             1. call into unmanaged code 
    ///             2. affects state/data that will eventually cross over unmanaged code boundary
    ///             3. Return some RM related information which is considered private 
    ///
    ///     TreatAsSafe: This attrbiute automatically applied to all public entry points. All the public entry points have
    ///     Demands for RightsManagementPermission at entry to counter the possible attacks that do 
    ///     not lead to the unamanged code directly(which is protected by another Demand there) but rather leave 
    ///     some status/data behind which eventually might cross the unamanaged boundary. 
    /// </SecurityNote>
    [SecurityCritical(SecurityCriticalScope.Everything)]    
    public class SecureEnvironment : IDisposable
    {
        /// <summary>
        /// This static Method builds a new instance of a SecureEnvironment  for a given user that is already 
        /// activated. If this method called with a user that isn't activated, and exception will be thrown. 
        /// The user that is passed into the function must have a well defined authentication type 
        /// AuthenticationType.Windows or AuthenticationType.Passport, all other Authentication 
        /// types(AuthenticationType.WindowsPassport or AuthenticationType.Internal) are not allowed.
        /// </summary>
        public static SecureEnvironment Create(string applicationManifest,
                                               ContentUser user)
        {
            SecurityHelper.DemandRightsManagementPermission();
    
            return CriticalCreate(applicationManifest, user);
        }
 
 
        /// <summary>
        /// This static method activates a user and creates a new instance of SecureEnvironment.
        /// The authentication type determines the type of user identity that will be activated. 
        /// If Permanent Windows activation is requested then the default currently logged on 
        /// Windows Account identity will be activated. If Temporary Windows activation requested 
        /// then user will be prompted for Windows Domain credentials through a dialog, and the 
        /// user identified through those credentials will be activated. 
        /// In case of Passport authentication, a Passport authentication dialog will always
        /// appear regardless of temporary or permanent activation mode. The user that authenticatd 
        /// through that Passport Authentication dialog will be activated.
        /// Regardless of Windows or Passport Authentication, all Temporary created activation will be 
        /// destroyed when SecureEnvironment instance is Disposed or Finalized.  
        /// </summary>   
        public static SecureEnvironment Create(string applicationManifest, 
                                                                                        AuthenticationType authentication, 
                                                                                        UserActivationMode userActivationMode)
        {
            SecurityHelper.DemandRightsManagementPermission();
 
            return CriticalCreate(applicationManifest, 
                                            authentication,
                                            userActivationMode);
        }
        
        /// <summary>
        /// This property verifies whether the current machine was prepared for consuming and producing RM protected content. 
        /// If property returns true it could be used as an indication that Init function call will not result in a network transaction.
        /// </summary>
        public static bool IsUserActivated(ContentUser user)
        {
            SecurityHelper.DemandRightsManagementPermission();
        
            if (user == null)
            {
                throw new ArgumentNullException("user");
            }
 
            // we only let specifically identified users to be used here  
            if ((user.AuthenticationType != AuthenticationType.Windows) && 
                 (user.AuthenticationType != AuthenticationType.Passport))
            {
                throw new ArgumentOutOfRangeException("user", SR.Get(SRID.OnlyPassportOrWindowsAuthenticatedUsersAreAllowed));
            }
            
            using (ClientSession userClientSession = new ClientSession(user))
            {
                // if machine activation is not present we can return false right away             
                return (userClientSession.IsMachineActivated() && userClientSession.IsUserActivated());
            }
        }
 
        /// <summary>
        /// Removes activation for a given user. User must have Windows or Passport authnetication 
        /// </summary>
        public static void RemoveActivatedUser(ContentUser user)
        {
            SecurityHelper.DemandRightsManagementPermission();
            
            if (user == null)
            {
                throw new ArgumentNullException("user");
            }
 
            // we only let specifically identifyed users to be used here  
            if ((user.AuthenticationType != AuthenticationType.Windows) && 
                 (user.AuthenticationType != AuthenticationType.Passport))
            {
                throw new ArgumentOutOfRangeException("user", SR.Get(SRID.OnlyPassportOrWindowsAuthenticatedUsersAreAllowed));
            }
 
            // Generic client session to enumerate user certificates 
            using (ClientSession userClientSession = new ClientSession(user))
            {          
                // Remove Licensor certificastes first 
                List<string> userClientLicensorCertificateIds = 
                                userClientSession.EnumerateUsersCertificateIds(user, EnumerateLicenseFlags.ClientLicensor);
 
                // and now we can remove certificates that have been enumerated 
                foreach(string licenseId in userClientLicensorCertificateIds)
                {
                    userClientSession.DeleteLicense(licenseId); 
                }                
                        
                // Remove User's identity certificastes second 
                List<string> userGroupIdentityCertificateIds = 
                                userClientSession.EnumerateUsersCertificateIds(user, EnumerateLicenseFlags.GroupIdentity);
 
                // and now we can remove certificates that have been enumerated 
                foreach(string licenseId in userGroupIdentityCertificateIds)
                {
                    userClientSession.DeleteLicense(licenseId); 
                }                
            }
        }
 
        /// <summary>
        /// This function returns a read only collection of the activated users.
        /// </summary>
        static public  ReadOnlyCollection<ContentUser>  GetActivatedUsers()
        {
            SecurityHelper.DemandRightsManagementPermission();
            
            //build user with the default authentication type and a default name 
            // neither name not authentication type is important in this case 
            //ContentUser tempUser = new ContentUser(_defaultUserName, AuthenticationType.Windows);
        
            // Generic client session to enumerate user certificates 
            using(ClientSession genericClientSession = 
                ClientSession.DefaultUserClientSession(AuthenticationType.Windows))
            {
                List<ContentUser> userList = new List<ContentUser>(); 
 
                // if machine activation is not present we can return empty list right away             
                if (genericClientSession.IsMachineActivated())
                {
                    int index =0; 
                    while(true)
                    {
                        // we get a string which can be parsed to get the ID and type 
                        string userCertificate = genericClientSession.EnumerateLicense(EnumerateLicenseFlags.GroupIdentity, index);
 
                        if (userCertificate == null)
                            break;
 
                        // we need to parse the information out of the string 
                        ContentUser user = ClientSession.ExtractUserFromCertificateChain(userCertificate);
 
                        // User specific client session to check it's status 
                        using(ClientSession userClientSession = new ClientSession(user))
                        {
                            if (userClientSession.IsUserActivated()) 
                            {
                                userList.Add(user);
                            }
                        }
 
                        index ++;
                    }
                }
                
                return new ReadOnlyCollection<ContentUser>(userList);
            }
        }
 
        /// <summary>
        /// This method is responsible for tearing down secure environment that was built as a result of Init call.
        /// </summary>
        public void Dispose()
        {              
            SecurityHelper.DemandRightsManagementPermission();        
            
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        /// <summary>
        /// Read only property which returns the User provided in the constructor. 
        /// </summary>
        public ContentUser User
        {
            get
            {
                SecurityHelper.DemandRightsManagementPermission();
            
                CheckDisposed();
                return _user;
            }
        }
 
        /// <summary>
        /// Read only property which returns the Application Manifest provided in the constructor. 
        /// </summary>
        public string ApplicationManifest        
        {
            get
            {
                SecurityHelper.DemandRightsManagementPermission();
            
                CheckDisposed();
                return _applicationManifest;
            }
        }
 
        /// <summary>
        /// Dispose(bool)
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            try
            {
                if (disposing && (_clientSession != null))
                {
                    _clientSession.Dispose();
                }
            }
            finally
            {
                _clientSession = null;
            }
        }
 
        internal ClientSession ClientSession 
        {
            get
            {
                Invariant.Assert(_clientSession != null);
            
                return _clientSession;
            }
        }
 
        /// <summary>
        /// This static Method builds a new instance of a secure environment for a given user that is assumed to be already activated. 
        /// client Application can use GetActivatedUsers property to enumerate Activated users.
        /// </summary>
        private static SecureEnvironment CriticalCreate(string applicationManifest, ContentUser user)
        {
            if (applicationManifest == null)
            {
                throw new ArgumentNullException("applicationManifest");
            }
 
            if (user == null)
            {
                throw new  ArgumentNullException("user");
            }
 
            // we only let specifically identifyed users to be used here  
            if ((user.AuthenticationType != AuthenticationType.Windows) && 
                 (user.AuthenticationType != AuthenticationType.Passport))
            {
                throw new ArgumentOutOfRangeException("user");
            }
 
            if (!IsUserActivated(user))
            {
                throw new RightsManagementException(RightsManagementFailureCode.NeedsGroupIdentityActivation);
            }
            
            ClientSession clientSession = new ClientSession(user);
 
            try
            {
                clientSession.BuildSecureEnvironment(applicationManifest);
 
                return new SecureEnvironment(applicationManifest, user, clientSession);
            }
            catch
            {
                clientSession.Dispose();
                throw;
            }
        }
 
        private static SecureEnvironment CriticalCreate(
            string applicationManifest, 
            AuthenticationType authentication,
            UserActivationMode userActivationMode)
        {
            if (applicationManifest == null)
            {
                throw new ArgumentNullException("applicationManifest");
            }
 
            if ((authentication != AuthenticationType.Windows) && 
                 (authentication != AuthenticationType.Passport))
            {
                throw new ArgumentOutOfRangeException("authentication");
            }
 
            if ((userActivationMode != UserActivationMode.Permanent) &&
                 (userActivationMode != UserActivationMode.Temporary))
            {
                throw new ArgumentOutOfRangeException("userActivationMode");            
            }
 
            //build user with the given authnetication type and a default name 
            // only authentication type is critical in this case 
            ContentUser user; 
            
            using (ClientSession tempClientSession =
                ClientSession.DefaultUserClientSession(authentication))
            {
                //Activate Machine if neccessary
                if (!tempClientSession.IsMachineActivated())
                {
                    // activate Machine
                    tempClientSession.ActivateMachine(authentication);
                }
 
                //Activate User (we will force start activation at this point)
                // at this point we should have a real user name 
                user = tempClientSession.ActivateUser(authentication, userActivationMode);
            }
 
            Debug.Assert(IsUserActivated(user));
 
            ClientSession clientSession = new ClientSession(user, userActivationMode);
 
            try
            {
                try
                {
                    // make sure we have a Client Licensor Certificate 
                    clientSession.AcquireClientLicensorCertificate();
                }
                catch (RightsManagementException)
                {
                    // In case of the RightsMnaagement exception we are willing to proceed
                    // as ClientLicensorCertificate only required for publishing not for consumption 
                    // and therefore it is optional to have one.
                }
            
                clientSession.BuildSecureEnvironment(applicationManifest);
 
                return new SecureEnvironment(applicationManifest, user, clientSession);
            }
            catch
            {
                clientSession.Dispose();
                throw;
            }
        }
 
        /// <summary>
        /// Private Constructor for the SecureEnvironment. 
        /// </summary>
        private SecureEnvironment(string applicationManifest,
                                                         ContentUser user, 
                                                         ClientSession clientSession) 
        {
            Invariant.Assert(applicationManifest != null);
            Invariant.Assert(user != null);
            Invariant.Assert(clientSession != null);
 
            _user = user;
            _applicationManifest = applicationManifest;
            _clientSession = clientSession;
        }
 
        /// <summary>
        /// Call this before accepting any API call 
        /// </summary>
        private void CheckDisposed()
        {
            if (_clientSession == null)
                throw new ObjectDisposedException("SecureEnvironment");
        }
 
        private ContentUser _user;
        private string _applicationManifest;
        private ClientSession _clientSession;       // if null we are disposed
    }
}