/// <summary> /// Initializes a new instance of the <Typ>LearningStore</Typ> class. /// </summary> /// <param name="connectionString">The connection used to access the SQL server /// database. The string is the same format as a connection string passed to /// <Typ>/System.Data.SqlClient.SqlConnection</Typ></param> /// <param name="userKey">The unique key of the user accessing the database.</param> /// <param name="impersonationBehavior">Identifies which <c>WindowsIdentity</c> is used to /// access the database when impersonation is involved.</param> /// <param name="disableSecurityChecks">True if security checks should be skipped when performing operations /// in the database.</param> /// <exception cref="ArgumentNullException"><paramref name="connectionString"/> or <paramref name="userKey"/> /// is a null reference.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="impersonationBehavior"/> /// has an invalid value</exception> /// <example>The following code opens the store located in the "Mls" /// database of the "Learning" server with the user key "Bob". The original (non-impersonated) /// identity is used when accessing the database: /// <code language="C#"> /// WindowsIdentity applicationIdentity = ... /// LearningStore store = new LearningStore("Server=Learning;Database=Mls;Integrated Security=true", "Bob", ImpersonationBehavior.UseOriginalIdentity, false); /// </code> /// </example> public LearningStore(string connectionString, string userKey, ImpersonationBehavior impersonationBehavior, bool disableSecurityChecks) { // Check the input parameters if (connectionString == null) { throw new ArgumentNullException("connectionString"); } if (userKey == null) { throw new ArgumentNullException("userKey"); } if ((impersonationBehavior != ImpersonationBehavior.UseImpersonatedIdentity) && (impersonationBehavior != ImpersonationBehavior.UseOriginalIdentity)) { throw new ArgumentOutOfRangeException("impersonationBehavior"); } // Save the passed-in values m_connectionString = connectionString; m_userKey = userKey; m_impersonationBehavior = impersonationBehavior; m_disableSecurityChecks = disableSecurityChecks; // Default locale is the current thread locale m_locale = CultureInfo.CurrentCulture; }
/// <summary> /// This method throws an exception of the user does not have the right /// to switch to the identity. This must be Disposed of /// when we are finished with it. If the identity is null, this method has no effect. /// </summary> public ImpersonateIdentity(ImpersonationBehavior impersonationBehavior) { if (impersonationBehavior == ImpersonationBehavior.UseOriginalIdentity) { m_context = WindowsIdentity.Impersonate(IntPtr.Zero); } }
/// <summary> /// Constructor that creates a FileSystemPackageStore. /// </summary> /// <param name="learningStore">The <c>LearningStore</c> where packages in this store /// are kept.</param> /// <param name="basePath">The base path to which files should be 'stored'. That is, the /// Microsoft Learning Components /// core schema defines a base path for all packages. When packages are imported /// into LearningStore, they are copied to a subdirectory of this basePath directory.Every /// package is given a unique directory for its files when the package is added to the system. /// The application should not modify files in this directory. The directory must exist /// before calling this constructor. /// </param> /// <param name="impersonationBehavior">A user that has read/write access to the <P>basePath</P> /// location. In some cases, the identity must also have permission to remove files from the <P>basePath</P>. /// The constructor does not verify all the permissions are available. If the identity does not have the /// appropriate permissions, an exception may be thrown from other methods as those permissions are required. /// </param> /// <exception cref="ArgumentNullException">Thrown if any of the arguments is null.</exception> /// <exception cref="DirectoryNotFoundException">Thrown if <P>basePath</P> is not an absolute path /// to an existing directory.</exception> /// <exception cref="UnauthorizedAccessException">Thrown if <P>impersonationBehavior</P> does not have FileSystemRights.Read to the /// <P>basePath</P> directory.</exception> public FileSystemPackageStore(LearningStore learningStore, string basePath, ImpersonationBehavior impersonationBehavior) : base(learningStore) { Utilities.ValidateParameterNonNull("basePath", basePath); Utilities.ValidateParameterNonNull("learningStore", learningStore); basePath = basePath.Trim(); Utilities.ValidateParameterNotEmpty("basePath", basePath); // The outer try/catch block is there for security reasons. Search MSDN for // "WrapVulnerableFinallyClausesInOuterTry" to see details. try { using (ImpersonateIdentity id = new ImpersonateIdentity(impersonationBehavior)) { if (!Directory.Exists(basePath)) { throw new DirectoryNotFoundException(String.Format(CultureInfo.CurrentCulture, PackageResources.FSPS_BasePathDirNotFound, basePath)); } // Test that the identity has read access to the directory. This will throw security exception if it // does not. Directory.GetFiles(basePath); } } catch { throw; } m_basePath = basePath; m_impersonationBehavior = impersonationBehavior; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")] // the writer is disposed for all code paths internal static void CopyStream(Stream fromStream, ImpersonationBehavior readImpersonationBehavior, Stream toStream, ImpersonationBehavior writeImpersonationBehavior) { // An addition revert during write operations is required if the setting for reading and writing // is not the same bool requiresWriteRevert = (readImpersonationBehavior != writeImpersonationBehavior); using (ImpersonateIdentity readId = new ImpersonateIdentity(readImpersonationBehavior)) { byte[] bytesIn = new byte[65536]; int bytesRead; while ((bytesRead = fromStream.Read(bytesIn, 0, bytesIn.Length)) != 0) { // If we have to impersonate to write, then do it. Otherwise skip it. if (requiresWriteRevert) { using (ImpersonateIdentity id = new ImpersonateIdentity(writeImpersonationBehavior)) { toStream.Write(bytesIn, 0, bytesRead); } } else { toStream.Write(bytesIn, 0, bytesRead); } } } }
/// <summary> /// Create settings for a class that manages a cache of SharePoint files. /// </summary> /// <param name="cachePath">The path to the folder containing the cached files.</param> /// <param name="expirationTime">The maximum amount of time that packages are stored in the cache. After this time has passed, /// the package will be removed from the cache if it has not been accessed. If not provided, files will never be removed from the /// cache.</param> /// <param name="impersonationBehavior">The identity used to access the cache. </param> /// <param name="cacheInvalidPackageAsFile">If true, a package that is invalid will be cached as a file. While it /// cannot be read as a package in this case, setting this to true may improve performance.</param> /// <remarks> /// This class does not verify that the <paramref name="cachePath"/> /// directory exists, however, when the settings are passed to SharePointPackageReader or SharePointPackageStore, those /// classes will require the directory to exist.</remarks> public SharePointCacheSettings(string cachePath, TimeSpan?expirationTime, ImpersonationBehavior impersonationBehavior, bool cacheInvalidPackageAsFile) { Utilities.ValidateParameterNotEmpty("cachePath", cachePath); m_cachePath = cachePath; m_expirationTime = expirationTime; m_impersonationBehavior = impersonationBehavior; m_cacheInvalidPackageAsFile = cacheInvalidPackageAsFile; }
/// <summary> /// Gets the directory that the package (specified by packageLocation) /// would be cached into. /// </summary> /// <param name="cachePath">The folder to use for a cache of all SharePoint files.</param> /// <param name="impersonationBehavior">The impersonation behaviour to use.</param> /// <param name="packageLocation">The package loacation in LearningStore format.</param> /// <returns>The directory path to the cached package.</returns> internal static string GetCacheDirectory(string cachePath, ImpersonationBehavior impersonationBehavior, string packageLocation) { SharePointFileLocation location; if (!SharePointFileLocation.TryParse(packageLocation, out location)) { throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, Resources.SPFormatInvalid, packageLocation)); } return(SharePointPackageReader.GetCacheDirectory(cachePath, impersonationBehavior, location)); }
FileSystemPackageReader m_fsPackageReader; // the PackageReader to read resource files /// <summary> /// Constructor. /// </summary> /// <param name="packageBasePath">The absolute path to the location where the packages /// are located in the filesystem.</param> /// <param name="packageStore">The PackageStore that contains information about the package.</param> /// <param name="packageId">The package id of the package to load.</param> /// <param name="packageLocation">The location of the package, as defined in /// LearningStore PackageItem.Location column. This cannot be null.</param> /// <param name="impersonationBehavior">The user who has access to the file system related to the store.</param> /// <remarks></remarks> internal FileSystemPackageStoreReader(string packageBasePath, FileSystemPackageStore packageStore, PackageItemIdentifier packageId, string packageLocation, ImpersonationBehavior impersonationBehavior) { m_store = packageStore; m_packageId = packageId; m_impersonationBehavior = impersonationBehavior; m_packageBasePath = PackageReader.SafePathCombine(packageBasePath, packageLocation); // This does not verify that m_packageBasePath actually exists m_fsPackageReader = new FileSystemPackageReader(m_packageBasePath, m_impersonationBehavior); }
/// <summary> /// Shared helper function to read a file from the package location and wrap the resulting exceptions /// to be consistent within MLC. /// </summary> /// <param name="packagePath">The path to the root folder of the package.</param> /// <param name="filePath">The package-relative path of the file within the package.</param> /// <param name="impersonationBehavior">Indicates which identity to use when reading the file.</param> /// <returns>A stream containing the file.</returns> internal static Stream ReadFile(string packagePath, string filePath, ImpersonationBehavior impersonationBehavior) { Stream stream = null; try { using (ImpersonateIdentity id = new ImpersonateIdentity(impersonationBehavior)) { stream = File.OpenRead(SafePathCombine(packagePath, filePath)); } } catch (UnauthorizedAccessException) { // don't wrap the inner exception to obfuscate any system file info that might exist throw new UnauthorizedAccessException(String.Format(CultureInfo.InvariantCulture, Resources.UnauthorizedAccess, filePath)); } catch (FileNotFoundException) { // don't wrap the inner exception to obfuscate any system file info that might exist throw new FileNotFoundException(Resources.PackageFileNotFound, filePath); } catch (DirectoryNotFoundException) { // don't wrap the inner exception to obfuscate any system file info that might exist throw new DirectoryNotFoundException(String.Format(CultureInfo.InvariantCulture, Resources.DirectoryNotFound, filePath)); } return stream; }
/// <summary> /// Initializes a new instance of the <c>FileSystemPackageReader</c> class with the /// package that is stored in the specified file system path. Uses the specified identity /// to access files in the package. /// </summary> /// /// <param name="packageBasePath">The path to the directory that is the base of the package, and /// contains the manifest for the package.</param> /// <param name="impersonationBehavior">The identity which has read access to the files in the located in /// <paramref name="packageBasePath"/>. </param> /// /// <exception cref="ArgumentException"><paramref name="packageBasePath"/> contains invalid characters /// such as ", <, >, or |.</exception> /// <exception cref="SecurityException">The caller does not have the required permission.</exception> /// <exception cref="PathTooLongException">The specified path exceeds the /// system-defined maximum length. For example, on Windows-based platforms, paths must be less /// than 248 characters, and file names must be less than 260 characters. The specified path /// is too long.</exception> /// <exception cref="ArgumentNullException"><paramref name="packageBasePath"/> is a null /// reference.</exception> /// /// <remarks> /// The package should be exploded (not in zip format) on the file system. Zipped packages can be /// read with the <Typ>ZipPackageReader</Typ>. Learning Resource packages can be read with the /// <Typ>LrmPackageReader</Typ>. /// <para> /// This constructor does not check if a directory exists. This constructor is a placeholder /// for a string that is used to access the disk in subsequent operations. /// </para> /// <para> /// The <paramref name="packageBasePath"/> parameter can be a directory on a network file share accessible /// via a mapped drive letter or UNC path. /// </para> /// </remarks> /// public FileSystemPackageReader(string packageBasePath, ImpersonationBehavior impersonationBehavior) { // Modeling this after the DirectoryInfo constructor. The DirectoryInfo constructor throws // the exceptions noted above. The overhead of creating and destroying the DirectoryInfo // structure is made up for by the robustness of using an established .NET object for // checking for these exceptions. m_packageBasePath = new DirectoryInfo(packageBasePath).FullName; m_impersonationBehavior = impersonationBehavior; }
/// <summary> /// Recursively copy from one folder to another by reading the files into memory. /// </summary> /// <param name="fromDir">The folder to copy from.</param> /// <param name="fromImpersonationBehavior">The identity that has rights to read the <paramref name="fromDir"/>.</param> /// <param name="toDir">The folder to write to. Writing is not done in an impersonation block.</param> private static void RecursiveCopyStreams(DirectoryInfo fromDir, ImpersonationBehavior fromImpersonationBehavior, DirectoryInfo toDir) { FileInfo[] files; using (ImpersonateIdentity id = new ImpersonateIdentity(fromImpersonationBehavior)) { files = fromDir.GetFiles(); } foreach (FileInfo file in files) { using(Disposer disposer = new Disposer()) { FileStream fromStream; using (ImpersonateIdentity id = new ImpersonateIdentity(fromImpersonationBehavior)) { fromStream = file.OpenRead(); disposer.Push(fromStream); } string toPath = PackageReader.SafePathCombine(toDir.FullName, file.Name); FileInfo toFileInfo = new FileInfo(toPath); FileStream toStream = toFileInfo.OpenWrite(); disposer.Push(toStream); Utilities.CopyStream(fromStream, fromImpersonationBehavior, toStream, ImpersonationBehavior.UseImpersonatedIdentity); } } foreach (DirectoryInfo sub in fromDir.GetDirectories()) { DirectoryInfo newSub = Directory.CreateDirectory(Path.Combine(toDir.FullName, sub.Name)); RecursiveCopy(sub, newSub); } }
/// <summary> /// Recursively copy all package files from one directory to another. /// </summary> /// <param name="from">Directory to copy from.</param> /// <param name="to">Directory to copy to.</param> /// <param name="impersonationBehavior">The identity that can read the files in the <paramref name="from"/> folder. /// If this identity can also write to the <paramref name="to"/> folder, the method performs faster /// than if it does not have write permissions.</param> internal static void RecursiveCopy(DirectoryInfo from, DirectoryInfo to, ImpersonationBehavior impersonationBehavior) { bool copySucceeded = false; try { using (ImpersonateIdentity id = new ImpersonateIdentity(impersonationBehavior)) { try { to.Create(); } catch (IOException ex) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.PackageDirectoryAlreadyExists, to.FullName), ex); } RecursiveCopy(from, to); copySucceeded = true; } } catch (UnauthorizedAccessException) { // This means the identity probably did not have write privileges to the 'to' folder. // So, try continue and try loading files into memory before writing them to the output folder. } if (!copySucceeded) { // Load files into memory before writing them to the output RecursiveCopyStreams(from, impersonationBehavior, to); } }
/// <summary> /// Initializes a new instance of the <c>ZipPackageReader</c> class with the specified zipped package. Uses the /// specified user's credentials to access package files. /// </summary> /// /// <param name="filePath">The path to the zipped file containing the package, e.g. "C:\Foo\Bar.zip".</param> /// <param name="impersonationBehavior">The identity to use when accessing package files. </param> /// /// <remarks> /// <para> /// This constructor does not check if the file exists. This constructor is a placeholder /// for a string that is used to access the file in subsequent operations. /// </para> /// <para> /// The <paramref name="filePath"/> parameter can be a directory on a network file share accessible /// via a mapped drive letter or UNC path. /// </para> /// </remarks> /// <exception cref="ArgumentNullException"><paramref name="filePath"/> is a null reference.</exception> /// <exception cref="SecurityException">The caller does not have the required permission.</exception> /// <exception cref="ArgumentException">The file name is empty, contains only white spaces, or contains invalid characters.</exception> /// <exception cref="PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.</exception> /// <exception cref="NotSupportedException"><paramref name="filePath"/> contains a colon (:) in the middle of the string.</exception> public ZipPackageReader(string filePath, ImpersonationBehavior impersonationBehavior) { Utilities.ValidateParameterNonNull("filePath", filePath); Utilities.ValidateParameterNotEmpty("filePath", filePath); m_zip = new FileInfo(filePath); m_impersonationBehavior = impersonationBehavior; }
/// <summary> /// Gets the directory that the package (specified by site, web, file, etc) /// would be cached into. /// </summary> /// <param name="cachePath">The folder to use for a cache of all SharePoint files.</param> /// <param name="impersonationBehavior">The impersonation behaviour to use.</param> /// <param name="packageLocation">The location of the package in SharePoint.</param> /// <returns>The directory path to the cached package.</returns> internal static string GetCacheDirectory(string cachePath, ImpersonationBehavior impersonationBehavior, SharePointFileLocation packageLocation) { return(CachedPackage.GetCacheDirectory(cachePath, packageLocation, impersonationBehavior)); }
/// <summary> /// Initializes a new instance of the <Typ>LearningStore</Typ> class. /// </summary> /// <param name="connectionString">The connection used to access the SQL server /// database. The string is the same format as a connection string passed to /// <Typ>/System.Data.SqlClient.SqlConnection</Typ></param> /// <param name="userKey">The unique key of the user accessing the database.</param> /// <param name="impersonationBehavior">Identifies which <c>WindowsIdentity</c> is used to /// access the database when impersonation is involved.</param> /// <exception cref="ArgumentNullException"><paramref name="connectionString"/> or <paramref name="userKey"/> /// is a null reference.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="impersonationBehavior"/> /// has an invalid value</exception> /// <example>The following code opens the store located in the "Mls" /// database of the "Learning" server with the user key "Bob". The original (non-impersonated) /// identity is used when accessing the database: /// <code language="C#"> /// LearningStore store = new LearningStore("Server=Learning;Database=Mls;Integrated Security=true", "Bob", ImpersonationBehavior.UseOriginalIdentity); /// </code> /// </example> public LearningStore(string connectionString, string userKey, ImpersonationBehavior impersonationBehavior) : this(connectionString, userKey, impersonationBehavior, false) { }
/// <summary>Initializes the reader with the cache directory.</summary> protected void Initialize(DirectoryInfo cacheDirectory, ImpersonationBehavior impersonationBehavior) { CacheDirectory = cacheDirectory; this.impersonationBehavior = impersonationBehavior; }
/// <summary> /// Get the schema information for this store from the cache or from the database. /// </summary> /// <param name="connectionString">Connection string used to access the store.</param> /// <param name="impersonationBehavior">Identifies which <c>WindowsIdentity</c> is used to /// access the database when impersonation is involved.</param> /// <param name="debugLog">Location to which the debug log should be written, or /// null if there isn't a debug log.</param> /// <returns>The schema information.</returns> private static LearningStoreSchema GetSchemaInformationFromCache(string connectionString, ImpersonationBehavior impersonationBehavior, TextWriter debugLog) { // Try to find the connection in the cache lock (s_allSchemasLock) { LearningStoreSchema schema; if (s_allSchemas.TryGetValue(connectionString, out schema)) { return(schema); } } // Not found in the cache -- so go get it from the database // This try/catch block is here for security reasons. Search MSDN for // "WrapVulnerableFinallyClausesInOuterTry" to see details. try { WindowsImpersonationContext impersonationContext = null; try { // Impersonate if necessary if (impersonationBehavior == ImpersonationBehavior.UseOriginalIdentity) { // Not adding it to the disposer, since that could fail (e.g., OutOfMemoryException), // which could cause a security hole. Instead, we'll clean it up manually later. impersonationContext = WindowsIdentity.Impersonate(IntPtr.Zero); } using (Microsoft.LearningComponents.Disposer disposer = new Microsoft.LearningComponents.Disposer()) { // Create a connection SqlConnection connection = new SqlConnection(connectionString); disposer.Push(connection); connection.Open(); // Create a command to retrieve information from the configuration table LogableSqlCommand command = new LogableSqlCommand(connection, debugLog); disposer.Push(command); // Execute command.Execute( "SELECT EngineVersion,\r\n" + " SchemaDefinition\r\n" + "FROM Configuration\r\n"); // Read return values from the database if (!command.Read()) { throw new LearningComponentsInternalException("LSTR1500"); } if (command.GetFieldCount() != 2) { throw new LearningComponentsInternalException("LSTR1510"); } int engineVersion = command.GetInt32(0); SqlXml schemaXml = command.GetSqlXml(1); XPathDocument schemaDoc; using (XmlReader reader = schemaXml.CreateReader()) { schemaDoc = new XPathDocument(reader); } LearningStoreSchema newSchema = LearningStoreSchema.CreateSchema(schemaDoc); if (command.Read()) { throw new LearningComponentsInternalException("LSTR1520"); } if (command.NextResult()) { throw new LearningComponentsInternalException("LSTR1530"); } // Fail if a different engine created this if (engineVersion != LearningStore.EngineVersion) { throw new InvalidOperationException(LearningStoreStrings.IncompatibleEngineVersion); } // Save it in the cache lock (s_allSchemasLock) { LearningStoreSchema schema; if (s_allSchemas.TryGetValue(connectionString, out schema)) { return(schema); } s_allSchemas.Add(connectionString, newSchema); } return(newSchema); } } finally { if (impersonationContext != null) { impersonationContext.Dispose(); } } } catch { throw; } }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")] // the writer is disposed for all code paths internal static void CopyStream(Stream fromStream, ImpersonationBehavior readImpersonationBehavior, Stream toStream, ImpersonationBehavior writeImpersonationBehavior) { using (Disposer disposer = new Disposer()) { DetachableStream dsToStream = new DetachableStream(toStream); disposer.Push(dsToStream); BinaryWriter writer = new BinaryWriter(dsToStream); disposer.Push(writer); // An addition revert during write operations is required if the setting for reading and writing // is not the same bool requiresWriteRevert = (readImpersonationBehavior != writeImpersonationBehavior); using (ImpersonateIdentity readId = new ImpersonateIdentity(readImpersonationBehavior)) { byte[] bytesIn = new byte[65536]; int bytesRead; while ((bytesRead = fromStream.Read(bytesIn, 0, bytesIn.Length)) != 0) { // If we have to impersonate to write, then do it. Otherwise skip it. if (requiresWriteRevert) { using (ImpersonateIdentity id = new ImpersonateIdentity(writeImpersonationBehavior)) { writer.Write(bytesIn, 0, bytesRead); } } else { writer.Write(bytesIn, 0, bytesRead); } } } dsToStream.Detach(); } }
/// <summary> /// This method throws an exception of the user does not have the right /// to switch to the identity. This must be Disposed of /// when we are finished with it. If the identity is null, this method has no effect. /// </summary> public ImpersonateIdentity(ImpersonationBehavior impersonationBehavior) { if (impersonationBehavior == ImpersonationBehavior.UseOriginalIdentity) m_context = WindowsIdentity.Impersonate(IntPtr.Zero); }