#17976: Preventing incompatible packages to get installed

Work Item: 17976

--HG--
branch : 1.x
This commit is contained in:
Sebastien Ros
2011-11-29 18:05:52 -08:00
parent 52ea7ce9b8
commit f5810823fb
10 changed files with 127 additions and 45 deletions
@@ -64,6 +64,9 @@ namespace Orchard.Packaging.Commands {
try { try {
_packageManager.Install(packageId, Version, Path.GetFullPath(location), ApplicationPath); _packageManager.Install(packageId, Version, Path.GetFullPath(location), ApplicationPath);
} }
catch (OrchardException e) {
Context.Output.WriteLine(T("Could not install the package: {0}", e.Message));
}
catch(Exception e) { catch(Exception e) {
// Exceptions area thrown by NuGet as error messages // Exceptions area thrown by NuGet as error messages
Context.Output.WriteLine(HttpUtility.HtmlDecode(T("Could not install the package: {0}", e.Message).Text)); Context.Output.WriteLine(HttpUtility.HtmlDecode(T("Could not install the package: {0}", e.Message).Text));
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Web;
using System.Web.Hosting; using System.Web.Hosting;
using System.Web.Mvc; using System.Web.Mvc;
using NuGet; using NuGet;
@@ -12,6 +11,7 @@ using Orchard.FileSystems.AppData;
using Orchard.Localization; using Orchard.Localization;
using Orchard.Modules.Services; using Orchard.Modules.Services;
using Orchard.Mvc.Extensions; using Orchard.Mvc.Extensions;
using Orchard.Packaging.Extensions;
using Orchard.Packaging.Services; using Orchard.Packaging.Services;
using Orchard.Packaging.ViewModels; using Orchard.Packaging.ViewModels;
using Orchard.Recipes.Models; using Orchard.Recipes.Models;
@@ -20,7 +20,6 @@ using Orchard.Security;
using Orchard.Themes; using Orchard.Themes;
using Orchard.UI.Admin; using Orchard.UI.Admin;
using Orchard.UI.Notify; using Orchard.UI.Notify;
using Orchard.Utility.Extensions;
using IPackageManager = Orchard.Packaging.Services.IPackageManager; using IPackageManager = Orchard.Packaging.Services.IPackageManager;
using PackageBuilder = Orchard.Packaging.Services.PackageBuilder; using PackageBuilder = Orchard.Packaging.Services.PackageBuilder;
@@ -32,7 +31,6 @@ namespace Orchard.Packaging.Controllers {
private readonly IPackageManager _packageManager; private readonly IPackageManager _packageManager;
private readonly IPackagingSourceManager _packagingSourceManager; private readonly IPackagingSourceManager _packagingSourceManager;
private readonly IAppDataFolderRoot _appDataFolderRoot; private readonly IAppDataFolderRoot _appDataFolderRoot;
private readonly INotifier _notifier;
private readonly IModuleService _moduleService; private readonly IModuleService _moduleService;
private readonly IRecipeHarvester _recipeHarvester; private readonly IRecipeHarvester _recipeHarvester;
private readonly IRecipeManager _recipeManager; private readonly IRecipeManager _recipeManager;
@@ -41,18 +39,16 @@ namespace Orchard.Packaging.Controllers {
ShellSettings shellSettings, ShellSettings shellSettings,
IPackageManager packageManager, IPackageManager packageManager,
IPackagingSourceManager packagingSourceManager, IPackagingSourceManager packagingSourceManager,
INotifier notifier,
IAppDataFolderRoot appDataFolderRoot, IAppDataFolderRoot appDataFolderRoot,
IOrchardServices services, IOrchardServices services,
IModuleService moduleService) IModuleService moduleService)
: this(shellSettings, packageManager, packagingSourceManager, notifier, appDataFolderRoot, services, moduleService, null, null) { : this(shellSettings, packageManager, packagingSourceManager, appDataFolderRoot, services, moduleService, null, null) {
} }
public PackagingServicesController( public PackagingServicesController(
ShellSettings shellSettings, ShellSettings shellSettings,
IPackageManager packageManager, IPackageManager packageManager,
IPackagingSourceManager packagingSourceManager, IPackagingSourceManager packagingSourceManager,
INotifier notifier,
IAppDataFolderRoot appDataFolderRoot, IAppDataFolderRoot appDataFolderRoot,
IOrchardServices services, IOrchardServices services,
IModuleService moduleService, IModuleService moduleService,
@@ -61,7 +57,6 @@ namespace Orchard.Packaging.Controllers {
_shellSettings = shellSettings; _shellSettings = shellSettings;
_packageManager = packageManager; _packageManager = packageManager;
_notifier = notifier;
_appDataFolderRoot = appDataFolderRoot; _appDataFolderRoot = appDataFolderRoot;
_moduleService = moduleService; _moduleService = moduleService;
_recipeHarvester = recipeHarvester; _recipeHarvester = recipeHarvester;
@@ -103,7 +98,7 @@ namespace Orchard.Packaging.Controllers {
if (_shellSettings.Name != ShellSettings.DefaultName || !Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to add sources"))) if (_shellSettings.Name != ShellSettings.DefaultName || !Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to add sources")))
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
var source = _packagingSourceManager.GetSources().Where(s => s.Id == sourceId).FirstOrDefault(); var source = _packagingSourceManager.GetSources().FirstOrDefault(s => s.Id == sourceId);
if (source == null) { if (source == null) {
return HttpNotFound(); return HttpNotFound();
} }
@@ -119,47 +114,58 @@ namespace Orchard.Packaging.Controllers {
IPackageRepository packageRepository = PackageRepositoryFactory.Default.CreateRepository(new PackageSource(source.FeedUrl, "Default")); IPackageRepository packageRepository = PackageRepositoryFactory.Default.CreateRepository(new PackageSource(source.FeedUrl, "Default"));
IPackage package = packageRepository.FindPackage(packageId); IPackage package = packageRepository.FindPackage(packageId);
ExtensionDescriptor extensionDescriptor = _packageManager.GetExtensionDescriptor(package, packageInfo.ExtensionType); ExtensionDescriptor extensionDescriptor = package.GetExtensionDescriptor(packageInfo.ExtensionType);
return InstallPackageDetails(extensionDescriptor, redirectUrl); return InstallPackageDetails(extensionDescriptor, redirectUrl);
} }
} }
catch (Exception) { catch (OrchardException e) {
Services.Notifier.Error(T("Package installation failed.")); Services.Notifier.Error(T("Package installation failed: {0}", e.Message));
} return View("InstallPackageFailed");
}
catch (Exception) {
Services.Notifier.Error(T("Package installation failed."));
return View("InstallPackageFailed");
}
return Redirect(redirectUrl); return Redirect(redirectUrl);
} }
public ActionResult InstallLocally(string redirectUrl) { public ActionResult InstallLocally(string redirectUrl) {
if (_shellSettings.Name != ShellSettings.DefaultName || !Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to install packages"))) if (_shellSettings.Name != ShellSettings.DefaultName || !Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to install packages")))
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
if (Request.Files == null || var httpPostedFileBase = Request.Files.Get(0);
Request.Files.Count == 0 || if (httpPostedFileBase == null
string.IsNullOrWhiteSpace(Request.Files.Get(0).FileName)) { || Request.Files.Count == 0
|| string.IsNullOrWhiteSpace(httpPostedFileBase.FileName)) {
throw new OrchardException(T("Select a file to upload.")); throw new OrchardException(T("Select a file to upload."));
} }
try { try {
HttpPostedFileBase file = Request.Files.Get(0); string fullFileName = Path.Combine(_appDataFolderRoot.RootFolder, Path.GetFileName(httpPostedFileBase.FileName)).Replace(Path.DirectorySeparatorChar, '/');
string fullFileName = Path.Combine(_appDataFolderRoot.RootFolder, Path.GetFileName(file.FileName)).Replace(Path.DirectorySeparatorChar, '/'); httpPostedFileBase.SaveAs(fullFileName);
file.SaveAs(fullFileName); var package = new ZipPackage(fullFileName);
ZipPackage package = new ZipPackage(fullFileName);
PackageInfo packageInfo = _packageManager.Install(package, _appDataFolderRoot.RootFolder, HostingEnvironment.MapPath("~/")); PackageInfo packageInfo = _packageManager.Install(package, _appDataFolderRoot.RootFolder, HostingEnvironment.MapPath("~/"));
ExtensionDescriptor extensionDescriptor = _packageManager.GetExtensionDescriptor(package, packageInfo.ExtensionType); ExtensionDescriptor extensionDescriptor = package.GetExtensionDescriptor(packageInfo.ExtensionType);
System.IO.File.Delete(fullFileName); System.IO.File.Delete(fullFileName);
if (DefaultExtensionTypes.IsTheme(extensionDescriptor.ExtensionType)) { if (DefaultExtensionTypes.IsTheme(extensionDescriptor.ExtensionType)) {
Services.Notifier.Information(T("The theme has been successfully installed. It can be enabled in the \"Themes\" page accessible from the menu.")); Services.Notifier.Information(T("The theme has been successfully installed. It can be enabled in the \"Themes\" page accessible from the menu."));
} else if (DefaultExtensionTypes.IsModule(extensionDescriptor.ExtensionType)) { }
else if (DefaultExtensionTypes.IsModule(extensionDescriptor.ExtensionType)) {
Services.Notifier.Information(T("The module has been successfully installed.")); Services.Notifier.Information(T("The module has been successfully installed."));
return InstallPackageDetails(extensionDescriptor, redirectUrl); return InstallPackageDetails(extensionDescriptor, redirectUrl);
} }
} }
catch (OrchardException e) {
Services.Notifier.Error(T("Package uploading and installation failed: ", e.Message));
return View("InstallPackageFailed");
}
catch (Exception) { catch (Exception) {
Services.Notifier.Error(T("Package uploading and installation failed.")); Services.Notifier.Error(T("Package uploading and installation failed."));
return View("InstallPackageFailed");
} }
return Redirect(redirectUrl); return Redirect(redirectUrl);
@@ -1,4 +1,8 @@
using NuGet; using System;
using System.IO;
using System.Linq;
using NuGet;
using Orchard.Environment.Extensions.Folders;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.Packaging.Models; using Orchard.Packaging.Models;
@@ -41,5 +45,27 @@ namespace Orchard.Packaging.Extensions {
packageId.Substring(Services.PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Theme).Length) : packageId.Substring(Services.PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Theme).Length) :
packageId.Substring(Services.PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Module).Length); packageId.Substring(Services.PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Module).Length);
} }
public static ExtensionDescriptor GetExtensionDescriptor(this IPackage package, string extensionType) {
IPackageFile packageFile = package.GetFiles().FirstOrDefault(file => {
var fileName = Path.GetFileName(file.Path);
return fileName != null && fileName.Equals(
DefaultExtensionTypes.IsModule(extensionType) ? "module.txt" : "theme.txt",
StringComparison.OrdinalIgnoreCase);
});
if (packageFile != null) {
var directoryName = Path.GetDirectoryName(packageFile.Path);
if (directoryName != null) {
string extensionId = Path.GetFileName(directoryName.TrimEnd('/', '\\'));
using (var streamReader = new StreamReader(packageFile.GetStream())) {
return ExtensionHarvester.GetDescriptorForExtension("", extensionId, extensionType, streamReader.ReadToEnd());
}
}
}
return null;
}
} }
} }
@@ -51,6 +51,7 @@
<RequiredTargetFramework>3.5</RequiredTargetFramework> <RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference> </Reference>
<Reference Include="System.ServiceModel" /> <Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <Reference Include="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll</HintPath> <HintPath>..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll</HintPath>
@@ -173,7 +174,9 @@
<ItemGroup> <ItemGroup>
<Content Include="Views\PackagingServices\InstallModuleDetails.cshtml" /> <Content Include="Views\PackagingServices\InstallModuleDetails.cshtml" />
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup>
<Content Include="Views\PackagingServices\InstallPackageFailed.cshtml" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
@@ -8,6 +8,7 @@ using Orchard.Logging;
namespace Orchard.Packaging.Services { namespace Orchard.Packaging.Services {
public interface IFolderUpdater : IDependency { public interface IFolderUpdater : IDependency {
void Backup(DirectoryInfo existingFolder, DirectoryInfo backupfolder); void Backup(DirectoryInfo existingFolder, DirectoryInfo backupfolder);
void Restore(DirectoryInfo backupfolder, DirectoryInfo existingFolder);
} }
[OrchardFeature("PackagingServices")] [OrchardFeature("PackagingServices")]
@@ -29,6 +30,10 @@ namespace Orchard.Packaging.Services {
CopyFolder(GetFolderContent(existingFolder), backupfolder); CopyFolder(GetFolderContent(existingFolder), backupfolder);
} }
public void Restore(DirectoryInfo backupfolder, DirectoryInfo existingFolder) {
CopyFolder(GetFolderContent(backupfolder), existingFolder);
}
private void CopyFolder(FolderContent source, DirectoryInfo dest) { private void CopyFolder(FolderContent source, DirectoryInfo dest) {
foreach (var file in source.Files) { foreach (var file in source.Files) {
CopyFile(source.Folder, file, dest); CopyFile(source.Folder, file, dest);
@@ -8,7 +8,5 @@ namespace Orchard.Packaging.Services {
PackageInfo Install(IPackage package, string location, string applicationPath); PackageInfo Install(IPackage package, string location, string applicationPath);
PackageInfo Install(string packageId, string version, string location, string applicationPath); PackageInfo Install(string packageId, string version, string location, string applicationPath);
void Uninstall(string packageId, string applicationPath); void Uninstall(string packageId, string applicationPath);
ExtensionDescriptor GetExtensionDescriptor(IPackage package, string extensionType);
} }
} }
@@ -1,12 +1,14 @@
using System; using System;
using System.IO; using System.IO;
using NuGet; using NuGet;
using Orchard.ContentManagement;
using Orchard.Environment.Extensions; using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.VirtualPath; using Orchard.FileSystems.VirtualPath;
using Orchard.Localization; using Orchard.Localization;
using Orchard.Packaging.Extensions; using Orchard.Packaging.Extensions;
using Orchard.Packaging.Models; using Orchard.Packaging.Models;
using Orchard.UI;
using Orchard.UI.Notify; using Orchard.UI.Notify;
using NuGetPackageManager = NuGet.PackageManager; using NuGetPackageManager = NuGet.PackageManager;
@@ -78,7 +80,26 @@ namespace Orchard.Packaging.Services {
} }
} }
return ExecuteInstall(package, packageRepository, location, applicationPath); var packageInfo = ExecuteInstall(package, packageRepository, location, applicationPath);
// check the new package is compatible with current Orchard version
var descriptor = package.GetExtensionDescriptor(packageInfo.ExtensionType);
if(descriptor != null) {
if(new FlatPositionComparer().Compare(descriptor.OrchardVersion, typeof(ContentItem).Assembly.GetName().Version.ToString()) >= 0) {
if (previousInstalled) {
// restore the previous version
RestoreExtensionFolder(package.ExtensionFolder(), package.ExtensionId());
}
else {
// just uninstall the new package
Uninstall(package.Id, _virtualPathProvider.MapPath("~\\"));
}
throw new OrchardException(T("The package is compatible with version {0} and above. Please update Orchard or install another version of this package.", descriptor.OrchardVersion));
}
}
return packageInfo;
} }
/// <summary> /// <summary>
@@ -218,6 +239,35 @@ namespace Orchard.Packaging.Services {
} }
} }
private bool RestoreExtensionFolder(string extensionFolder, string extensionId) {
var source = new DirectoryInfo(_virtualPathProvider.MapPath(_virtualPathProvider.Combine("~", extensionFolder, extensionId)));
if (source.Exists) {
var tempPath = _virtualPathProvider.Combine("~", extensionFolder, "_Backup", extensionId);
string localTempPath = null;
for (int i = 0; i < 1000; i++) {
localTempPath = _virtualPathProvider.MapPath(tempPath) + (i == 0 ? "" : "." + i.ToString());
if (!Directory.Exists(localTempPath)) {
Directory.CreateDirectory(localTempPath);
break;
}
localTempPath = null;
}
if (localTempPath == null) {
throw new OrchardException(T("Backup folder {0} has too many backups subfolder (limit is 1,000)", tempPath));
}
var backupFolder = new DirectoryInfo(localTempPath);
_folderUpdater.Restore(backupFolder, source);
_notifier.Information(T("Successfully restored local package to local folder \"{0}\"", source));
return true;
}
return false;
}
private bool BackupExtensionFolder(string extensionFolder, string extensionId) { private bool BackupExtensionFolder(string extensionFolder, string extensionId) {
var source = new DirectoryInfo(_virtualPathProvider.MapPath(_virtualPathProvider.Combine("~", extensionFolder, extensionId))); var source = new DirectoryInfo(_virtualPathProvider.MapPath(_virtualPathProvider.Combine("~", extensionFolder, extensionId)));
@@ -32,6 +32,9 @@ namespace Orchard.Packaging.Services {
try { try {
return installer(); return installer();
} }
catch (OrchardException) {
throw;
}
catch (Exception exception) { catch (Exception exception) {
var message = T( var message = T(
"There was an error installing the requested package. " + "There was an error installing the requested package. " +
@@ -69,22 +72,6 @@ namespace Orchard.Packaging.Services {
_packageExpander.Uninstall(packageId, applicationPath); _packageExpander.Uninstall(packageId, applicationPath);
} }
public ExtensionDescriptor GetExtensionDescriptor(IPackage package, string extensionType) {
IPackageFile packageFile = package.GetFiles().FirstOrDefault(file =>
Path.GetFileName(file.Path).Equals(
DefaultExtensionTypes.IsModule(extensionType) ? "module.txt" : "theme.txt",
StringComparison.OrdinalIgnoreCase));
if (packageFile != null) {
string extensionId = Path.GetFileName(Path.GetDirectoryName(packageFile.Path).TrimEnd('/', '\\'));
using (StreamReader streamReader = new StreamReader(packageFile.GetStream())) {
return ExtensionHarvester.GetDescriptorForExtension("", extensionId, extensionType, streamReader.ReadToEnd());
}
}
return null;
}
#endregion #endregion
} }
} }
@@ -36,7 +36,7 @@
<button type="submit">@T("Search")</button> <button type="submit">@T("Search")</button>
</fieldset> </fieldset>
if (Model.Extensions.Count() > 0) { if (Model.Extensions.Any()) {
<ul class="contentItems"> <ul class="contentItems">
@foreach (var item in Model.Extensions) { @foreach (var item in Model.Extensions) {
<li> <li>
@@ -75,7 +75,7 @@
</div> </div>
<div class="properties"> <div class="properties">
<p>@(item.Description == null ? T("(No description").Text : item.Description)</p> <p>@(item.Description ?? T("(No description").Text)</p>
<ul class="pageStatus group"> <ul class="pageStatus group">
<li>@T("Last Updated: {0}", item.LastUpdated)</li> <li>@T("Last Updated: {0}", item.LastUpdated)</li>
<li>&nbsp;&#124;&nbsp;@T("Author: {0}", !string.IsNullOrEmpty(item.Authors) ? item.Authors : T("Unknown").ToString())</li> <li>&nbsp;&#124;&nbsp;@T("Author: {0}", !string.IsNullOrEmpty(item.Authors) ? item.Authors : T("Unknown").ToString())</li>
@@ -0,0 +1,4 @@
@* Used as Notification don't durvive a Redirection *@
@{
Layout.Title = T("Install package failed");
}