Compare commits

..

8 Commits

Author SHA1 Message Date
jorge.agraz@onestop.com
6862637b69 Stuff Jorge commented out to get the build working. 2016-07-20 10:28:01 -07:00
jorge.agraz@onestop.com
f519bc96ed Merge branch 'dev' into feature/deployments
# Conflicts:
#	src/Orchard/ContentManagement/DefaultContentManager.cs
2016-07-01 19:34:07 -07:00
Sipke Schoorstra
48ccada654 Merge branch 'dev' into feature/deployments 2016-04-26 21:12:25 +02:00
jorge.agraz@onestop.com
cf8f4d5388 Merge branch 'dev' into feature/deployments
# Conflicts:
#	src/Orchard.Web/Modules/Orchard.ImportExport/Orchard.ImportExport.csproj
2016-04-15 18:21:04 -07:00
jorge.agraz@onestop.com
835ff95664 Import / Export changes and additions for Deployments, Feature, Admin screens 2016-04-15 18:16:59 -07:00
jorge.agraz@onestop.com
3df81980b3 Additions to error handling for file IO errors 2016-04-15 18:16:10 -07:00
jorge.agraz@onestop.com
c0ac53078c Changes to Orchard.Recipes 2016-04-15 18:14:45 -07:00
jorge.agraz@onestop.com
162be51ad9 Changes to Content Manager 2016-04-15 18:14:13 -07:00
207 changed files with 5931 additions and 2188 deletions

View File

@@ -6,7 +6,7 @@ Orchard is a free, open source, community-focused Content Management System buil
You can try it for free on [DotNest.com](https://dotnest.com) or on Microsoft Azure by clicking on this button.
[![Deploy to Azure](https://azuredeploy.net/deploybutton.png)](https://portal.azure.com/#create/OutercurveFoundation.OrchardCMS)
[![Deploy to Azure](https://azuredeploy.net/deploybutton.png)](https://portal.azure.com/#create/OutercurveFoundation.OrchardCMS.1.0.4)
## About The Orchard Project
@@ -22,16 +22,16 @@ Our mission is to empower our users and foster a dedicated and diverse community
## Project Status
Orchard is currently in version **[1.10.1](https://github.com/OrchardCMS/Orchard/releases/tag/1.10.1)** and **[1.9.3](https://github.com/OrchardCMS/Orchard/releases/tag/1.9.3)**:
Orchard is currently in version **1.10** and **1.9.3**:
- *1.10.1* is the latest minor version that contains bugfixes and the more impactful changes and new features added in the latest major version (*1.10*). **If you're new to Orchard, you should use this version.**
- *1.10* is the latest major version which introduces more impactful changes and new features as well. If you're new to Orchard, you should start with this version.
- *1.9.3* contains further bugfixes in addition to *1.9.2* and these versions are based on the feature set of Orchard *1.9*.
We invite participation by the developer community in shaping the projects direction, so that we can publicly validate our designs and development approach.
All our releases are available on our [Releases](https://github.com/OrchardCMS/Orchard/releases) page, and it's easy to [Install Orchard using the Web Platform Installer](http://docs.orchardproject.net/Documentation/Installing-Orchard) as well. We encourage interested developers to check out the source code on the Orchard GitHub site and get involved with the project.
* [Download the latest release](https://github.com/OrchardCMS/Orchard/releases)
* [Feature roadmap](http://docs.orchardproject.net/Documentation/Feature-roadmap)
* [Feature roadmap](http://docs.orchardproject.net/Documentation/feature-roadmap)
* [Docs and designs/specs](http://www.orchardproject.net/docs)
* [About us](http://www.orchardproject.net/about)
* [Contact us](mailto:ofeedbk@microsoft.com)

View File

@@ -10,8 +10,8 @@ var fs = require("fs"),
plumber = require("gulp-plumber"),
sourcemaps = require("gulp-sourcemaps"),
less = require("gulp-less"),
sass = require("gulp-sass"),
cssnano = require("gulp-cssnano"),
sass = require("gulp-sass"),
typescript = require("gulp-typescript"),
uglify = require("gulp-uglify"),
rename = require("gulp-rename"),
@@ -136,12 +136,8 @@ function buildCssPipeline(assetGroup, doConcat, doRebuild) {
throw "Input file '" + inputPath + "' is not of a valid type for output file '" + assetGroup.outputPath + "'.";
});
var generateSourceMaps = assetGroup.hasOwnProperty("generateSourceMaps") ? assetGroup.generateSourceMaps : true;
var containsLessOrScss = assetGroup.inputPaths.some(function (inputPath) {
var ext = path.extname(inputPath).toLowerCase();
return ext === ".less" || ext === ".scss";
});
// Source maps are useless if neither concatenating nor transforming.
if ((!doConcat || assetGroup.inputPaths.length < 2) && !containsLessOrScss)
if ((!doConcat || assetGroup.inputPaths.length < 2) && !assetGroup.inputPaths.some(function (inputPath) { return path.extname(inputPath).toLowerCase() === ".less"; }))
generateSourceMaps = false;
var minifiedStream = gulp.src(assetGroup.inputPaths) // Minified output, source mapping completely disabled.
.pipe(gulpif(!doRebuild,
@@ -154,7 +150,7 @@ function buildCssPipeline(assetGroup, doConcat, doRebuild) {
.pipe(plumber())
.pipe(gulpif("*.less", less()))
.pipe(gulpif("*.scss", sass({
precision: 10
precision: 10
})))
.pipe(gulpif(doConcat, concat(assetGroup.outputFileName)))
.pipe(cssnano({
@@ -182,7 +178,7 @@ function buildCssPipeline(assetGroup, doConcat, doRebuild) {
.pipe(gulpif(generateSourceMaps, sourcemaps.init()))
.pipe(gulpif("*.less", less()))
.pipe(gulpif("*.scss", sass({
precision: 10
precision: 10
})))
.pipe(gulpif(doConcat, concat(assetGroup.outputFileName)))
.pipe(header(

View File

@@ -27,10 +27,6 @@
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -201,7 +201,7 @@ namespace Orchard.Tests.ContentManagement {
private void Import(XElement element) {
var importContentSession = new ImportContentSession(_contentManager);
_contentManager.Import(element, importContentSession);
_contentManager.Import(new ImportContentContext(element, null, importContentSession));
_contentManager.CompleteImport(element, importContentSession);
}

View File

@@ -18,10 +18,6 @@
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -1,6 +1,4 @@
@{
var propertyName = Model.PropertyName != null ? (string)Model.PropertyName : "Text";
var htmlAttributes = new Dictionary<string, object> {
{"class", "text large"}
};
@@ -14,4 +12,4 @@
}
}
@Html.TextBox(propertyName, (string)Model.Text, htmlAttributes)
@Html.TextBox("Text", (string)Model.Text, htmlAttributes)

View File

@@ -1,6 +1,4 @@
@{
var propertyName = Model.PropertyName != null ? (string)Model.PropertyName : "Text";
var htmlAttributes = new Dictionary<string, object> {
{"class", "text small"}
};
@@ -14,4 +12,4 @@
}
}
@Html.TextBox(propertyName, (string)Model.Text, htmlAttributes)
@Html.TextBox("Text", (string)Model.Text, htmlAttributes)

View File

@@ -1,6 +1,4 @@
@{
var propertyName = Model.PropertyName != null ? (string)Model.PropertyName : "Text";
var htmlAttributes = new Dictionary<string, object>();
if (Model.Required == true) {
@@ -16,4 +14,4 @@
}
}
@Html.TextArea(propertyName, (string)Model.Text, 25, 80, htmlAttributes)
@Html.TextArea("Text", (string)Model.Text, 25, 80, htmlAttributes)

View File

@@ -1,6 +1,4 @@
@{
var propertyName = Model.PropertyName != null ? (string)Model.PropertyName : "Text";
var htmlAttributes = new Dictionary<string, object> {
{"class", "text medium"}
};
@@ -14,4 +12,4 @@
}
}
@Html.TextBox(propertyName, (string)Model.Text, htmlAttributes)
@Html.TextBox("Text", (string)Model.Text, htmlAttributes)

View File

@@ -1,7 +1,5 @@
@using Orchard.Utility.Extensions;
@{
var propertyName = Model.PropertyName != null ? (string)Model.PropertyName : "Text";
string editorFlavor = Model.EditorFlavor;
var htmlAttributes = new Dictionary<string, object> {
@@ -21,4 +19,4 @@
}
}
@Html.TextArea(propertyName, (string)Model.Text, 25, 80, htmlAttributes)
@Html.TextArea("Text", (string)Model.Text, 25, 80, htmlAttributes)

View File

@@ -22,7 +22,7 @@
<fieldset>
@Html.LabelFor(m => m.EnablePositioning, @T("Enable drag and drop"))
@Html.EditorFor(m => m.EnablePositioning)
<span class="hint">@T("Check this option to enable manual repositioning of items using drag and drop. If not set, this option will be configurable on the list content item itself.")</span>
<span class="hint">@T("Check this option to enable manual repositioning of items using. If not set, this option will be configurable on the list content item itself.")</span>
</fieldset>
<fieldset>
@Html.LabelFor(m => m.AdminListViewName, @T("List View"))
@@ -45,4 +45,4 @@
<span class="hint">@T("Select zero or more content types that this content type supports. Selecting zero content types means the list can contain any content type. Only types with the Containable part can be contained in a list.")</span>
</div>
</fieldset>
}
}

View File

@@ -54,7 +54,7 @@
@Html.SelectOption((ContentsOrder)Model.Options.OrderBy, ContentsOrder.Modified, T("recently modified").ToString())
@Html.SelectOption((ContentsOrder)Model.Options.OrderBy, ContentsOrder.Published, T("recently published").ToString())
</select>
<label for="contentResults" class="bulk-filter">@T("Filter by")</label>
<label for="contentResults" class="bulk-order">@T("Filter by")</label>
<select id="contentResults" name="Options.ContentsStatus">
@Html.SelectOption((ContentsStatus)Model.Options.ContentsStatus, ContentsStatus.Owner, T("owned by me").ToString())
@Html.SelectOption((ContentsStatus)Model.Options.ContentsStatus, ContentsStatus.Latest, T("latest").ToString())

View File

@@ -1,8 +1,7 @@
using System;
using System;
using Orchard.Commands;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Common.Models;
using Orchard.Core.Navigation.Models;
using Orchard.Core.Navigation.Services;
using Orchard.Security;
@@ -16,7 +15,7 @@ namespace Orchard.Core.Navigation.Commands {
private readonly IMembershipService _membershipService;
public MenuCommands(
IContentManager contentManager,
IContentManager contentManager,
IMenuService menuService,
ISiteService siteService,
IMembershipService membershipService) {
@@ -28,10 +27,7 @@ namespace Orchard.Core.Navigation.Commands {
[OrchardSwitch]
public string MenuPosition { get; set; }
[OrchardSwitch]
public string Identity { get; set; }
[OrchardSwitch]
public string Owner { get; set; }
@@ -45,7 +41,7 @@ namespace Orchard.Core.Navigation.Commands {
public string MenuName { get; set; }
[CommandName("menuitem create")]
[CommandHelp("menuitem create /MenuPosition:<position> /MenuText:<text> /Url:<url> /MenuName:<name> [/Owner:<username>]\r\n\t" + "Creates a new menu item")]
[CommandHelp("menuitem create /MenuPosition:<position> /MenuText:<text> /Url:<url> /MenuName:<name> [/Owner:<username>] \r\n\t" + "Creates a new menu item")]
[OrchardSwitches("MenuPosition,MenuText,Url,MenuName,Owner")]
public void Create() {
// flushes before doing a query in case a previous command created the menu
@@ -79,20 +75,17 @@ namespace Orchard.Core.Navigation.Commands {
}
[CommandName("menu create")]
[CommandHelp("menu create /MenuName:<name> [/Identity:<identity>] \r\n\t" + "Creates a new menu")]
[OrchardSwitches("MenuName,Identity")]
[CommandHelp("menu create /MenuName:<name>\r\n\t" + "Creates a new menu")]
[OrchardSwitches("MenuName")]
public void CreateMenu() {
if (string.IsNullOrWhiteSpace(MenuName)) {
Context.Output.WriteLine(T("Menu name can't be empty.").Text);
return;
}
var menuItem = _menuService.Create(MenuName);
if (menuItem.Has<IdentityPart>() && !String.IsNullOrEmpty(Identity)) {
menuItem.As<IdentityPart>().Identifier = Identity;
}
_menuService.Create(MenuName);
Context.Output.WriteLine(T("Menu created successfully.").Text);
}
}
}
}

View File

@@ -177,13 +177,5 @@ namespace Orchard.Core.Navigation {
return 6;
}
public int UpdateFrom6() {
ContentDefinitionManager.AlterTypeDefinition("ShapeMenuItem", cfg => cfg
.WithIdentity()
);
return 7;
}
}
}
}

View File

@@ -25,27 +25,27 @@
</fieldset>
<fieldset>
@Html.EditorFor(m => m.Breadcrumb)
@Html.EditorFor(m => m.Breadcrumb)
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.Breadcrumb)">@T("Display as Breadcrumb")</label>
<span class="hint">@T("Check to render the path to the current content item.")</span>
</fieldset>
<div data-controllerid="@Html.FieldIdFor(m => m.Breadcrumb)">
<fieldset>
@Html.EditorFor(m => m.AddHomePage)
@Html.EditorFor(m => m.AddHomePage)
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.AddHomePage)">@T("Add the home page as the first element")</label>
<span class="hint">@T("Check to render the home page as the first element of the breadcrumb.")</span>
</fieldset>
<fieldset>
@Html.EditorFor(m => m.AddCurrentPage)
@Html.EditorFor(m => m.AddCurrentPage)
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.AddCurrentPage)">@T("Add the current content item as the last element")</label>
<span class="hint">@T("Check to render the current content item as the last element.")</span>
</fieldset>
</div>
<fieldset>
@Html.EditorFor(m => m.ShowFullMenu)
@Html.EditorFor(m => m.ShowFullMenu)
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.ShowFullMenu)">@T("No filter on selected page")</label>
<span class="hint">@T("Check for the menu to be displayed without filtering the selected current page.")</span>
</fieldset>
<span class="hint">@T("Check for the menu to be display without filtering the selected current page.")</span>
</fieldset>

View File

@@ -1,8 +1,6 @@
@using Orchard.Environment.Descriptor.Models
@{
var propertyName = Model.PropertyName != null ? (string)Model.PropertyName : "Text";
Script.Require("OrchardMarkdown");
Style.Require("OrchardMarkdown");
@@ -37,7 +35,7 @@
<div class="wmd-innerbox">
<div class="wmd-editor-box">
<div id="wmd-button-bar-@idPostfix" class="wmd-button-bar"></div>
@Html.TextArea(propertyName, (string)Model.Text, 25, 80, textAreaAttributes)
@Html.TextArea("Text", (string)Model.Text, 25, 80, textAreaAttributes)
</div>
<div class="wmd-preview-box">
<div id="wmd-preview-@idPostfix" class="wmd-panel wmd-preview"></div>

View File

@@ -41,14 +41,14 @@ namespace Orchard.Alias.Navigation {
var urlLevel = endsWithSlash ? requestUrl.Count(l => l == '/') - 1 : requestUrl.Count(l => l == '/');
var menuLevel = menuPosition.Count(l => l == '.');
// Checking the menu item whether it's the leaf element or it's an intermediate element
// Checking menu item if it's the leaf element or it's an intermediate element
// or it's an unneccessary element according to the url.
RouteValueDictionary contentRoute;
if (menuLevel == urlLevel) {
contentRoute = request.RequestContext.RouteData.Values;
}
else {
// If menuLevel doesn't equal to urlLevel it can mean that this menu item is
// If menuLevel doesn't equal with urlLevel it can mean that this menu item is
// an intermediate element (the difference is a positive value) or this menu
// item is lower in the navigation hierarchy according to the url (negative
// value). If the value is negative, removing the menu item, if the value

View File

@@ -1,6 +0,0 @@
[
{
"inputs": [ "Assets/Styles.less" ],
"output": "Styles/Styles.css"
}
]

View File

@@ -1,13 +0,0 @@
namespace Orchard.Azure.Authentication.Constants {
public class DefaultAzureSettings {
public static readonly string Tenant = "mydirectory.onmicrosoft.com";
public static readonly string ClientId = "MyClientId";
public static readonly string ADInstance = "https://login.microsoftonline.com/{0}";
public static readonly string LogoutRedirectUri = "http://localhost:30321/OrchardLocal/";
public static readonly bool BearerAuthEnabled = false;
public static readonly bool SSLEnabled = false;
public static readonly bool AzureWebSiteProtectionEnabled = false;
public static readonly string GraphiApiUri = "https://graph.windows.net";
public static readonly string GraphApiKey = "XSpN3xxGE7KgjLF/3lk2PBz98eAQpIMwUQUvoB/bZXs=";
}
}

View File

@@ -1,63 +0,0 @@
using System;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Orchard.Environment.Extensions;
using Orchard.Security;
using Orchard.Themes;
using System.Web;
using System.Web.Configuration;
using System.Web.Mvc;
using Orchard.Azure.Authentication.Services;
using Orchard.Logging;
namespace Orchard.Azure.Authentication.Controllers {
[Themed]
[OrchardSuppressDependency("Orchard.Users.Controllers.AccountController")]
public class AccountController : Controller {
public ILogger Logger { get; set; }
private readonly IAzureGraphiApiService _graphiApiService;
private readonly IAzureRolesPersistence _azureRolesPersistence;
public AccountController(IAzureGraphiApiService graphiApiService, IAzureRolesPersistence azureRolesPersistence) {
Logger = NullLogger.Instance;
_graphiApiService = graphiApiService;
_azureRolesPersistence = azureRolesPersistence;
}
public void LogOn() {
if (Request.IsAuthenticated) {
return; //TODO: redirect to home if we can?
}
var redirectUri = Url.Content("~/users/account/logoncallback");
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties {RedirectUri = redirectUri},
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
public void LogOff() {
HttpContext.GetOwinContext().Authentication.SignOut(
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType); //OpenID Connect sign-out request.
}
public ActionResult LogonCallback() {
var userName = HttpContext.GetOwinContext().Authentication.User.Identity.Name.Trim();
try {
var groups = _graphiApiService.GetUserGroups(userName);
_azureRolesPersistence.SyncAzureGroupsToOrchardRoles(userName, groups);
}
catch (Exception ex) {
Logger.Error(ex.Message, ex);
}
return Redirect(Url.Content("~/"));
}
[AlwaysAccessible]
public ActionResult AccessDenied() {
return View();
}
}
}

View File

@@ -1,21 +0,0 @@
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.Azure.Authentication.Models;
namespace Orchard.Azure.Authentication.Drivers {
public class AzureSettingsPartDriver : ContentPartDriver<AzureSettingsPart> {
protected override string Prefix {
get { return "AzureSettings"; }
}
protected override DriverResult Editor(AzureSettingsPart part, dynamic shapeHelper) {
return ContentShape("Parts_AzureSettings_Edit", () =>
shapeHelper.EditorTemplate(TemplateName: "Parts/AzureSettings", Model: part, Prefix: Prefix)).OnGroup("Azure");
}
protected override DriverResult Editor(AzureSettingsPart part, IUpdateModel updater, dynamic shapeHelper) {
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
}

View File

@@ -1,26 +0,0 @@
using Orchard.ContentManagement.Handlers;
using Orchard.ContentManagement;
using Orchard.Data;
using Orchard.Localization;
using Orchard.Azure.Authentication.Models;
namespace Orchard.Azure.Authentication.Handlers {
public class AzureSettingsPartHandler : ContentHandler {
public Localizer T { get; set; }
public AzureSettingsPartHandler() {
T= NullLocalizer.Instance;
Filters.Add(new ActivatingFilter<AzureSettingsPart>("Site"));
Filters.Add(new TemplateFilterForPart<AzureSettingsPart>("AzureSettings", "Parts/AzureSettings", "Azure Authentication"));
}
protected override void GetItemMetadata(GetContentItemMetadataContext context) {
if (context.ContentItem.ContentType != "Site") {
return;
}
base.GetItemMetadata(context);
context.Metadata.EditorGroupInfo.Add(new GroupInfo(T("Azure Authentication")));
}
}
}

View File

@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015 Radio Systems Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,55 +0,0 @@
using Orchard.ContentManagement;
namespace Orchard.Azure.Authentication.Models {
public class AzureSettingsPart : ContentPart{
public string Tenant {
get { return this.Retrieve(x => x.Tenant); }
set { this.Store(x => x.Tenant, value); }
}
public string ADInstance {
get { return this.Retrieve(x => x.ADInstance); }
set { this.Store(x => x.ADInstance, value); }
}
public string ClientId {
get { return this.Retrieve(x => x.ClientId); }
set { this.Store(x => x.ClientId, value); }
}
public string AppName {
get { return this.Retrieve(x => x.AppName); }
set { this.Store(x => x.AppName, value); }
}
public string LogoutRedirectUri {
get { return this.Retrieve(x => x.LogoutRedirectUri); }
set { this.Store(x => x.LogoutRedirectUri, value); }
}
public bool BearerAuthEnabled {
get { return this.Retrieve(x => x.BearerAuthEnabled); }
set { this.Store(x => x.BearerAuthEnabled, value); }
}
public bool SSLEnabled {
get { return this.Retrieve(x => x.SSLEnabled); }
set { this.Store(x => x.SSLEnabled, value); }
}
public bool AzureWebSiteProtectionEnabled {
get { return this.Retrieve(x => x.AzureWebSiteProtectionEnabled); }
set { this.Store(x => x.AzureWebSiteProtectionEnabled, value); }
}
public string GraphApiUrl {
get { return this.Retrieve(x => x.GraphApiUrl); }
set { this.Store(x => x.GraphApiUrl, value); }
}
public bool UseAzureGraphApi {
get { return this.Retrieve(x => x.UseAzureGraphApi); }
set { this.Store(x => x.UseAzureGraphApi, value); }
}
}
}

View File

@@ -1,12 +0,0 @@
Name: Orchard.Azure.Authentication
AntiForgery: enabled
Author: Radio Systems Corp.
Website: http://orchardproject.net
Version: 1.0
OrchardVersion: 1.9.2
Description: Description for the module
Features:
Orchard.Azure.Authentication:
Description: Provides Azure Active Directory authentication.
Category: Authentication

View File

@@ -1,286 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{60D63D66-E88E-4D07-82EE-C1561903A73E}</ProjectGuid>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Orchard.Azure.Authentication</RootNamespace>
<AssemblyName>Orchard.Azure.Authentication</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<MvcBuildViews>false</MvcBuildViews>
<FileUpgradeFlags>
</FileUpgradeFlags>
<OldToolsVersion>4.0</OldToolsVersion>
<UpgradeBackupLocation />
<TargetFrameworkProfile />
<UseIISExpress>false</UseIISExpress>
<IISExpressSSLPort />
<IISExpressAnonymousAuthentication />
<IISExpressWindowsAuthentication />
<IISExpressUseClassicPipelineMode />
<UseGlobalApplicationHostFile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>..\..\..\OrchardBasicCorrectness.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="Iesi.Collections, Version=4.0.0.0, Culture=neutral, PublicKeyToken=aa95f207798dfdb4, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Iesi.Collections.4.0.1.4000\lib\net40\Iesi.Collections.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Azure.ActiveDirectory.GraphClient, Version=2.1.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Azure.ActiveDirectory.GraphClient.2.1.0\lib\portable-net4+sl5+win+wpa+wp8\Microsoft.Azure.ActiveDirectory.GraphClient.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Data.Edm, Version=5.6.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Data.Edm.5.6.4\lib\net40\Microsoft.Data.Edm.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Data.OData, Version=5.6.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Data.OData.5.6.4\lib\net40\Microsoft.Data.OData.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Data.Services.Client, Version=5.6.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Data.Services.Client.5.6.4\lib\net40\Microsoft.Data.Services.Client.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.IdentityModel.Clients.ActiveDirectory, Version=2.19.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.19.208020213\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms, Version=2.19.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.19.208020213\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.IdentityModel.Logging, Version=1.0.0.127, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.IdentityModel.Protocol.Extensions, Version=1.0.2.33, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.2.206221351\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.IdentityModel.Tokens, Version=5.0.0.127, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Host.SystemWeb, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Security, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Security.ActiveDirectory, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Owin.Security.ActiveDirectory.3.0.1\lib\net45\Microsoft.Owin.Security.ActiveDirectory.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Security.Cookies, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Owin.Security.Cookies.3.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Security.Jwt, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Owin.Security.Jwt.3.0.1\lib\net45\Microsoft.Owin.Security.Jwt.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Security.OAuth, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Owin.Security.OAuth.3.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Security.OpenIdConnect, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Owin.Security.OpenIdConnect.3.0.1\lib\net45\Microsoft.Owin.Security.OpenIdConnect.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NHibernate, Version=4.0.0.4000, Culture=neutral, PublicKeyToken=aa95f207798dfdb4, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\NHibernate.4.0.1.4000\lib\net40\NHibernate.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.ComponentModel.DataAnnotations">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.IdentityModel" />
<Reference Include="System.IdentityModel.Tokens.Jwt, Version=5.0.0.127, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\System.IdentityModel.Tokens.Jwt.5.0.0\lib\net451\System.IdentityModel.Tokens.Jwt.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Spatial, Version=5.6.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\System.Spatial.5.6.4\lib\net40\System.Spatial.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.ApplicationServices" />
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Entity" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Web.Helpers, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web" />
<Reference Include="System.Web.Abstractions" />
<Reference Include="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Routing" />
<Reference Include="System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.WebPages.Deployment, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets.json" />
<Content Include="Web.config" />
<Content Include="Scripts\Web.config" />
<Content Include="Module.txt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Orchard\Orchard.Framework.csproj">
<Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project>
<Name>Orchard.Framework</Name>
</ProjectReference>
<ProjectReference Include="..\..\Core\Orchard.Core.csproj">
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
<Name>Orchard.Core</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Roles\Orchard.Roles.csproj">
<Project>{d10ad48f-407d-4db5-a328-173ec7cb010f}</Project>
<Name>Orchard.Roles</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Users\Orchard.Users.csproj">
<Project>{79aed36e-abd0-4747-93d3-8722b042454b}</Project>
<Name>Orchard.Users</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="Constants\DefaultAzureSettings.cs" />
<Compile Include="Controllers\AccountController.cs" />
<Compile Include="Drivers\AzureSettingsPartDriver.cs" />
<Compile Include="Handlers\AzureSettingsPartHandler.cs" />
<Compile Include="Models\AzureSettingsPart.cs" />
<Compile Include="OwinMiddlewares.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Routes.cs" />
<Compile Include="Security\MachineKeyDataProtector.cs" />
<Compile Include="Services\AzureActiveDirectoryService.cs" />
<Compile Include="Services\AzureAuthenticationService.cs" />
<Compile Include="Services\AzureGraphiApiService.cs" />
<Compile Include="Services\AzureRolesPersistence.cs" />
<Compile Include="Services\IAzureGraphiApiService.cs" />
<Compile Include="Services\IAzureRolesPersistence.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="placement.info" />
</ItemGroup>
<ItemGroup>
<Content Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\EditorTemplates\Parts\AzureSettings.cshtml" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target> -->
<Target Name="AfterBuild" DependsOnTargets="AfterBuildCompiler">
<PropertyGroup>
<AreasManifestDir>$(ProjectDir)\..\Manifests</AreasManifestDir>
</PropertyGroup>
<!-- If this is an area child project, uncomment the following line:
<CreateAreaManifest AreaName="$(AssemblyName)" AreaType="Child" AreaPath="$(ProjectDir)" ManifestPath="$(AreasManifestDir)" ContentFiles="@(Content)" />
-->
<!-- If this is an area parent project, uncomment the following lines:
<CreateAreaManifest AreaName="$(AssemblyName)" AreaType="Parent" AreaPath="$(ProjectDir)" ManifestPath="$(AreasManifestDir)" ContentFiles="@(Content)" />
<CopyAreaManifests ManifestPath="$(AreasManifestDir)" CrossCopy="false" RenameViews="true" />
-->
</Target>
<Target Name="AfterBuildCompiler" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(ProjectDir)\..\$(ProjectName)" />
</Target>
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>45979</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>
</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>True</UseCustomServer>
<CustomServerUrl>http://orchard.codeplex.com</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
</Project>

View File

@@ -1,134 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Security.Claims;
using System.Web.Helpers;
using System.Web.WebPages;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.DataProtection;
using Microsoft.Owin.Security.OpenIdConnect;
using Orchard.ContentManagement;
using Orchard.Logging;
using Orchard.Owin;
using Orchard.Settings;
using Owin;
using Orchard.Azure.Authentication.Constants;
using Orchard.Azure.Authentication.Models;
using Orchard.Azure.Authentication.Security;
using Orchard.Azure.Authentication.Services;
using AuthenticationContext = Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext;
using LogLevel = Orchard.Logging.LogLevel;
namespace Orchard.Azure.Authentication {
public class OwinMiddlewares : IOwinMiddlewareProvider {
public ILogger Logger { get; set; }
private readonly string _azureClientId = DefaultAzureSettings.ClientId;
private readonly string _azureTenant = DefaultAzureSettings.Tenant;
private readonly string _logoutRedirectUri = DefaultAzureSettings.LogoutRedirectUri;
private readonly string _azureAdInstance = DefaultAzureSettings.ADInstance;
private readonly bool _azureWebSiteProtectionEnabled = DefaultAzureSettings.AzureWebSiteProtectionEnabled;
private readonly string _azureGraphiApiUri = DefaultAzureSettings.GraphiApiUri;
private readonly string _azureGraphApiKey = DefaultAzureSettings.GraphApiKey;
public OwinMiddlewares(ISiteService siteService) {
Logger = NullLogger.Instance;
try {
var settings = siteService.GetSiteSettings().As<AzureSettingsPart>();
if (settings == null) {
return;
}
_azureClientId = string.IsNullOrEmpty(settings.ClientId) ? _azureClientId : settings.ClientId;
_azureTenant = string.IsNullOrEmpty(settings.Tenant) ? _azureTenant : settings.Tenant;
_azureAdInstance = string.IsNullOrEmpty(settings.ADInstance) ? _azureAdInstance : settings.ADInstance;
_logoutRedirectUri = string.IsNullOrEmpty(settings.LogoutRedirectUri) ? _logoutRedirectUri : settings.LogoutRedirectUri;
_azureWebSiteProtectionEnabled = settings.AzureWebSiteProtectionEnabled;
_azureGraphiApiUri = string.IsNullOrEmpty(settings.GraphApiUrl) ? _azureGraphiApiUri : settings.GraphApiUrl;
}
catch (Exception ex) {
Logger.Log(LogLevel.Debug, ex, "An error occured while accessing azure settings: {0}");
}
}
public IEnumerable<OwinMiddlewareRegistration> GetOwinMiddlewares() {
var middlewares = new List<OwinMiddlewareRegistration>();
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
var openIdOptions = new OpenIdConnectAuthenticationOptions {
ClientId = _azureClientId,
Authority = string.Format(CultureInfo.InvariantCulture, _azureAdInstance, _azureTenant), // e.g. "https://login.windows.net/azurefridays.onmicrosoft.com/"
PostLogoutRedirectUri = _logoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
};
var cookieOptions = new CookieAuthenticationOptions();
if (_azureWebSiteProtectionEnabled) {
middlewares.Add(new OwinMiddlewareRegistration {
Priority = "9",
Configure = app => { app.SetDataProtectionProvider(new MachineKeyProtectionProvider()); }
});
}
middlewares.Add(new OwinMiddlewareRegistration {
Priority = "10",
Configure = app => {
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(cookieOptions);
app.UseOpenIdConnectAuthentication(openIdOptions);
}
});
middlewares.Add(new OwinMiddlewareRegistration {
Priority = "11",
Configure = app => app.Use(async (context, next) => {
try {
if (AzureActiveDirectoryService.token == null && AzureActiveDirectoryService.token.IsEmpty()) {
RegenerateAzureGraphApiToken();
}
else {
if (DateTimeOffset.Compare(DateTimeOffset.UtcNow, AzureActiveDirectoryService.tokenExpiresOn) > 0) {
RegenerateAzureGraphApiToken();
}
}
}
catch (Exception ex) {
Logger.Log(LogLevel.Error, ex, "An error occured generating azure api credential {0}", ex.Message);
}
await next.Invoke();
})
});
return middlewares;
}
private void RegenerateAzureGraphApiToken() {
var result = GetAuthContext().AcquireToken(_azureGraphiApiUri, GetClientCredential());
AzureActiveDirectoryService.tokenExpiresOn = result.ExpiresOn;
AzureActiveDirectoryService.token = result.AccessToken;
AzureActiveDirectoryService.azureGraphApiUri = _azureGraphiApiUri;
AzureActiveDirectoryService.azureTenant = _azureTenant;
}
private ClientCredential GetClientCredential() {
return new ClientCredential(_azureClientId, _azureGraphApiKey);
}
private AuthenticationContext GetAuthContext() {
var authority = string.Format(CultureInfo.InvariantCulture, _azureAdInstance, _azureTenant);
return new AuthenticationContext(authority, false);
}
}
}

View File

@@ -1,33 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Orchard.Azure.Authentication")]
[assembly: AssemblyDescription("Orchard module that enables authentication with Windows Azure Active Directory")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Orchard.Azure.Authentication")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ff308bcd-8cf0-4103-b8aa-f7444985ca56")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -1,15 +0,0 @@
# Orchard Azure Authentication Module
Orchard Azure Authentication is a module that enables Azure Authentication to Orchard using Azure Active Directory and OpenID connect. The module overrides the default login and integrates into the standard pipeline for Orchard.
## How to use
1. If you don't have an Azure Active Directory, get more info [here](https://azure.microsoft.com/en-us/documentation/articles/active-directory-whatis/). Create an application in Azure that will correspond to your Orchard instance. The settings you will need to configure Orchard Azure Active Directory Authentication will be on the configure tab of the created application.
2. Before enabling the module, your must add an admin user to Orchard via the Orchard admin. Ensure that the user name matches the Azure Active Directory user name for the user you wish to have admin privileges.
3. After enabling the module, before navigating away from the admin, go to site settings under the **Azure Authentication** group. Fill in the information from your Azure Active Directory. Tenant, AppName, and ClientId must be set. Other fields can be left as defaults.
4. You will have to restart your Orchard site for the new settings to take effect.
5. Navigate to the Orchard home screen. Clear site cookies, and watch Azure Authentication take over!
## License
This module is licensed under the Apache 2.0 license, a copy is included in the repository.

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="7.00:00:00" />
</staticContent>
<handlers accessPolicy="Script,Read">
<!--
iis7 - for any request to a file exists on disk, return it via native http module.
accessPolicy 'Script' is to allow for a managed 404 page.
-->
<add name="StaticFile" path="*" verb="*" modules="StaticFileModule" preCondition="integratedMode" resourceType="File" requireAccess="Read" />
</handlers>
</system.webServer>
</configuration>

View File

@@ -1,26 +0,0 @@
using System.Web.Security;
using Microsoft.Owin.Security.DataProtection;
namespace Orchard.Azure.Authentication.Security {
public class MachineKeyProtectionProvider : IDataProtectionProvider {
public IDataProtector Create(params string[] purposes) {
return new MachineKeyDataProtector(purposes);
}
}
public class MachineKeyDataProtector : IDataProtector {
private readonly string[] _purposes;
public MachineKeyDataProtector(string[] purposes) {
_purposes = purposes;
}
public byte[] Protect(byte[] userData) {
return MachineKey.Protect(userData, _purposes);
}
public byte[] Unprotect(byte[] protectedData) {
return MachineKey.Unprotect(protectedData, _purposes);
}
}
}

View File

@@ -1,30 +0,0 @@
using System;
using System.Threading.Tasks;
using System.Web.WebPages;
using Microsoft.Azure.ActiveDirectory.GraphClient;
using Orchard.Azure.Authentication.Constants;
namespace Orchard.Azure.Authentication.Services {
public class AzureActiveDirectoryService {
public static string token;
public static DateTimeOffset tokenExpiresOn;
public static string azureGraphApiUri = DefaultAzureSettings.GraphiApiUri;
public static string azureTenant = DefaultAzureSettings.Tenant;
public static async Task<string> AcquireTokenAsync() {
if (token == null || token.IsEmpty()) {
throw new Exception("Authorization Required.");
}
return token;
}
public static ActiveDirectoryClient GetActiveDirectoryClient() {
var baseServiceUri = new Uri("https://graph.windows.net/");
var activeDirectoryClient = new ActiveDirectoryClient(new Uri(baseServiceUri, azureTenant),
async () => await AcquireTokenAsync());
return activeDirectoryClient;
}
}
}

View File

@@ -1,45 +0,0 @@
using System.Web;
using System.Web.Security;
using Orchard.Mvc;
using Orchard.Security;
namespace Orchard.Azure.Authentication.Services {
public class AzureAuthenticationService : IAuthenticationService {
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IMembershipService _membershipService;
private IUser _localAuthenticationUser;
public AzureAuthenticationService(IHttpContextAccessor httpContextAccessor, IMembershipService membershipService) {
_httpContextAccessor = httpContextAccessor;
_membershipService = membershipService;
}
public void SignIn(IUser user, bool createPersistentCookie) {}
public void SignOut() {}
public void SetAuthenticatedUserForRequest(IUser user) { }
public IUser GetAuthenticatedUser() {
var azureUser = _httpContextAccessor.Current().GetOwinContext().Authentication.User;
if (!azureUser.Identity.IsAuthenticated) {
return null;
}
// In memory caching of sorts since this method gets called many times per request
if (_localAuthenticationUser != null) {
return _localAuthenticationUser;
}
var userName = azureUser.Identity.Name.Trim();
//Get the local user, if local user account doesn't exist, create it
var localUser = _membershipService.GetUser(userName) ?? _membershipService.CreateUser(new CreateUserParams(
userName, Membership.GeneratePassword(16, 1), userName, string.Empty, string.Empty, true
));
return _localAuthenticationUser = localUser;
}
}
}

View File

@@ -1,29 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Azure.ActiveDirectory.GraphClient;
namespace Orchard.Azure.Authentication.Services {
public class AzureGraphiApiService : IAzureGraphiApiService {
private readonly ActiveDirectoryClient _client;
public AzureGraphiApiService() {
_client = AzureActiveDirectoryService.GetActiveDirectoryClient();
}
public IUser GetUser(string userName) {
var azureUser = _client.Users.Where(x => x.UserPrincipalName == userName)
.ExecuteAsync().Result.CurrentPage.FirstOrDefault();
return azureUser;
}
public IList<Group> GetUserGroups(string userName) {
var user = GetUser(userName);
var userFetcher = (IUserFetcher) user;
var groups = userFetcher.MemberOf.ExecuteAsync()
.Result.CurrentPage.Select(x => x as Group).ToList();
groups.RemoveAll(x => x == null);
return groups;
}
}
}

View File

@@ -1,42 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Azure.ActiveDirectory.GraphClient;
using NHibernate;
using Orchard.Data;
using Orchard.Roles.Models;
using Orchard.Roles.Services;
using Orchard.Security;
namespace Orchard.Azure.Authentication.Services {
public class AzureRolesPersistence : IAzureRolesPersistence {
private readonly IRoleService _roleService;
private readonly IMembershipService _membershipService;
private readonly ISession _session;
public AzureRolesPersistence(IRoleService roleService, IMembershipService membershipService,
ITransactionManager transactionManager) {
_roleService = roleService;
_membershipService = membershipService;
_session = transactionManager.GetSession();
}
public void SyncAzureGroupsToOrchardRoles(string userName, IList<Group> azureGroups) {
var user = _membershipService.GetUser(userName);
var matchingOrchardRoles = azureGroups.Select(@group => _roleService.GetRoleByName(@group.DisplayName))
.Where(orchardRole => orchardRole != null).ToList();
foreach (var role in matchingOrchardRoles) {
var existingUserRole = _session.QueryOver<UserRolesPartRecord>()
.Where(x => x.UserId == user.Id).And(x => x.Role.Id == role.Id).List().FirstOrDefault();
if (existingUserRole == null) {
_session.SaveOrUpdate(new UserRolesPartRecord {
Role = role,
UserId = user.Id
});
}
}
}
}
}

View File

@@ -1,9 +0,0 @@
using System.Collections.Generic;
using Microsoft.Azure.ActiveDirectory.GraphClient;
namespace Orchard.Azure.Authentication.Services {
public interface IAzureGraphiApiService : IDependency {
IUser GetUser(string userName);
IList<Group> GetUserGroups(string userObjectId);
}
}

View File

@@ -1,8 +0,0 @@
using System.Collections.Generic;
using Microsoft.Azure.ActiveDirectory.GraphClient;
namespace Orchard.Azure.Authentication.Services {
public interface IAzureRolesPersistence : IDependency {
void SyncAzureGroupsToOrchardRoles(string userName, IList<Group> azureGroups);
}
}

View File

@@ -1,49 +0,0 @@
@model Orchard.Azure.Authentication.Models.AzureSettingsPart
<fieldset>
<legend>Azure Active Directory Settings</legend>
@Html.LabelFor(m => m.Tenant, T("Tenant"))
@Html.TextBoxFor(m => m.Tenant)
<span class="hint">@T("Azure Active Directory tenant (e.g. mysite.onmicrosoft.com)")</span>
</fieldset>
<fieldset>
@Html.LabelFor(m => m.ADInstance, T("Active Directory Instance"))
@Html.TextBoxFor(m => m.ADInstance)
<span class="hint">@T("Default instance is https://login.microsoftonline.com/{your-tenant-name}")</span>
</fieldset>
<fieldset>
@Html.LabelFor(m => m.ClientId)
@Html.TextBoxFor(m => m.ClientId)
</fieldset>
<fieldset>
@Html.LabelFor(m => m.AppName)
@Html.TextBoxFor(m => m.AppName)
<span class="hint">@T("Application name you wish to give active directly login rights to")</span>
</fieldset>
<fieldset>
@Html.LabelFor(m => m.LogoutRedirectUri, T("Logout Redirect"))
@Html.TextBoxFor(m => m.LogoutRedirectUri)
<span class="hint">@T("Redirect url after azure logout, default is http://localhost:30321/OrchardLocal/")</span>
</fieldset>
<fieldset>
@Html.LabelFor(m => m.BearerAuthEnabled, T("Enable Bearer Token Authentication"))
@Html.CheckBoxFor(m => m.BearerAuthEnabled)
</fieldset>
<fieldset>
@Html.LabelFor(m => m.SSLEnabled, T("Use SSL Protocol for valid audience"))
@Html.CheckBoxFor(m => m.SSLEnabled)
</fieldset>
<fieldset>
@Html.LabelFor(m => m.AzureWebSiteProtectionEnabled, T("Enable Machine Key Data Protection for Azure Web Site"))
@Html.CheckBoxFor(m => m.AzureWebSiteProtectionEnabled)
</fieldset>
<fieldset>
@Html.LabelFor(m => m.GraphApiUrl)
@Html.TextBoxFor(m => m.GraphApiUrl)
<span class="hint">@T("Typically https://graph.windows.net")</span>
</fieldset>
<fieldset>
@Html.LabelFor(m => m.UseAzureGraphApi)
@Html.CheckBoxFor(m => m.UseAzureGraphApi)
<span class="hint">@T("Check this box to enable syncing Orchard Role membership to Azure Graph API Group Membership. This module will not create new Orchard Roles for you, but it will sync up user membership of existing Orchard Roles with AD Group membership for Role names that match a group name")</span>
</fieldset>

View File

@@ -1,93 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<remove name="host" />
<remove name="pages" />
<section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
</sectionGroup>
</configSections>
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.3, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<pages pageBaseType="Orchard.Mvc.ViewEngines.Razor.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="System.Web.WebPages" />
<add namespace="System.Linq" />
<add namespace="System.Collections.Generic" />
<add namespace="Orchard.Mvc.Html" />
</namespaces>
</pages>
</system.web.webPages.razor>
<!--
For a description of web.config changes see http://go.microsoft.com/fwlink/?LinkId=235367.
The following attributes can be set on the <httpRuntime> tag.
<system.Web>
<httpRuntime targetFramework="4.5.2" />
</system.Web>
-->
<system.web>
<compilation targetFramework="4.5.2" debug="true">
<assemblies>
<add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add assembly="System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<add assembly="System.Web.Mvc, Version=5.2.3, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add assembly="System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</assemblies>
</compilation>
</system.web>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Autofac" publicKeyToken="17863af14b0044da" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.5.0.0" newVersion="3.5.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="NHibernate" publicKeyToken="aa95f207798dfdb4" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.4000" newVersion="4.0.0.4000" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Iesi.Collections" publicKeyToken="aa95f207798dfdb4" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.0.0.127" newVersion="5.0.0.127" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.IdentityModel.Protocol.Extensions" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.0.2.33" newVersion="1.0.2.33" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Iesi.Collections" version="4.0.1.4000" targetFramework="net452" />
<package id="Microsoft.AspNet.Mvc" version="5.2.3" targetFramework="net451" />
<package id="Microsoft.AspNet.Razor" version="3.2.3" targetFramework="net451" />
<package id="Microsoft.AspNet.WebPages" version="3.2.3" targetFramework="net451" />
<package id="Microsoft.Azure.ActiveDirectory.GraphClient" version="2.1.0" targetFramework="net452" />
<package id="Microsoft.Data.Edm" version="5.6.4" targetFramework="net452" />
<package id="Microsoft.Data.OData" version="5.6.4" targetFramework="net452" />
<package id="Microsoft.Data.Services.Client" version="5.6.4" targetFramework="net452" />
<package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="2.19.208020213" targetFramework="net452" />
<package id="Microsoft.IdentityModel.Logging" version="1.0.0" targetFramework="net451" />
<package id="Microsoft.IdentityModel.Protocol.Extensions" version="1.0.2.206221351" targetFramework="net451" />
<package id="Microsoft.IdentityModel.Tokens" version="5.0.0" targetFramework="net451" />
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net451" />
<package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net451" />
<package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net451" />
<package id="Microsoft.Owin.Security.ActiveDirectory" version="3.0.1" targetFramework="net451" />
<package id="Microsoft.Owin.Security.Cookies" version="3.0.1" targetFramework="net451" />
<package id="Microsoft.Owin.Security.Jwt" version="3.0.1" targetFramework="net451" />
<package id="Microsoft.Owin.Security.OAuth" version="3.0.1" targetFramework="net451" />
<package id="Microsoft.Owin.Security.OpenIdConnect" version="3.0.1" targetFramework="net451" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net451" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net451" />
<package id="NHibernate" version="4.0.1.4000" targetFramework="net452" />
<package id="Owin" version="1.0" targetFramework="net451" />
<package id="System.IdentityModel.Tokens.Jwt" version="5.0.0" targetFramework="net451" />
<package id="System.Spatial" version="5.6.4" targetFramework="net452" />
</packages>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Placement>
<Place Parts_AzureSettings_Edit="Content:1"/>
</Placement>

View File

@@ -147,7 +147,7 @@ namespace Orchard.Azure.MediaServices.Controllers {
_transactionManager.Cancel();
Logger.Error(ex, "Error while deleting asset with ID {0}.", id);
_notifier.Error(T("An error occurred while deleting the asset '{0}':\n{1}", asset.Name, ex.Message));
_notifier.Error(T("Ar error occurred while deleting the asset '{0}':\n{1}", asset.Name, ex.Message));
}
return Redirect(Url.ItemEditUrl(cloudVideoPart));
@@ -182,4 +182,4 @@ namespace Orchard.Azure.MediaServices.Controllers {
ModelState.AddModelError(key, errorMessage.ToString());
}
}
}
}

View File

@@ -164,7 +164,7 @@ namespace Orchard.Azure.MediaServices.Controllers {
_transactionManager.Cancel();
Logger.Error(ex, "Error while creating job with task of type {0} on cloud video item with ID {1}.", task, id);
_notifier.Error(T("An error occurred while creating the job:\n{0}", ex.Message));
_notifier.Error(T("Ar error occurred while creating the job:\n{0}", ex.Message));
}
}
@@ -253,4 +253,4 @@ namespace Orchard.Azure.MediaServices.Controllers {
ModelState.AddModelError(key, errorMessage.ToString());
}
}
}
}

View File

@@ -29,8 +29,8 @@ namespace Orchard.Azure.MediaServices.Controllers {
private readonly IAuthorizer _authorizer;
public MediaController(
IOrchardServices services,
IAssetManager assetManager,
IOrchardServices services,
IAssetManager assetManager,
ITransactionManager transactionManager) {
_contentManager = services.ContentManager;
@@ -124,7 +124,7 @@ namespace Orchard.Azure.MediaServices.Controllers {
Logger.Debug("User requested to save cloud video item with ID {0}.", part.Id);
var editorShape = _contentManager.UpdateEditor(part, this);
if (!ModelState.IsValid) {
_transactionManager.Cancel();
@@ -153,8 +153,8 @@ namespace Orchard.Azure.MediaServices.Controllers {
catch (Exception ex) {
_transactionManager.Cancel();
Logger.Error(ex, "Error while saving cloud video item with ID {0}.", part.Id);
_notifier.Error(T("An error occurred while saving the cloud video item:\n{1}", ex.Message));
Logger.Error(ex, "Error while saving cloud video item with ID {0}.", part.Id);
_notifier.Error(T("Ar error occurred while saving the cloud video item:\n{1}", ex.Message));
}
return RedirectToAction("Edit", new { id = part.Id });
@@ -168,4 +168,4 @@ namespace Orchard.Azure.MediaServices.Controllers {
ModelState.AddModelError(key, errorMessage.ToString());
}
}
}
}

View File

@@ -107,7 +107,7 @@ namespace Orchard.ImportExport.Commands {
Context.Output.WriteLine(T("Export starting..."));
var exportContext = new ExportActionContext();
_importExportService.Export(exportContext, actions);
var exportFilePath = _importExportService.WriteExportFile(exportContext.RecipeDocument);
var exportFilePath = _importExportService.WriteExportFile(exportContext);
if (!String.IsNullOrEmpty(Filename)) {
var directory = Path.GetDirectoryName(Filename);

View File

@@ -8,6 +8,7 @@ using Orchard.ImportExport.Services;
using Orchard.ImportExport.ViewModels;
using Orchard.Localization;
using Orchard.Recipes.Services;
using Orchard.Recipes.Providers.Builders;
namespace Orchard.ImportExport.Controllers {
public class AdminController : Controller, IUpdateModel {
@@ -102,16 +103,17 @@ namespace Orchard.ImportExport.Controllers {
var actions = _exportActions.OrderByDescending(x => x.Priority).ToList();
foreach (var action in actions) {
foreach (var action in actions)
{
action.UpdateEditor(Services.New, this);
}
var exportActionContext = new ExportActionContext();
_importExportService.Export(exportActionContext, actions);
var recipeDocument = exportActionContext.RecipeDocument;
var exportFilePath = _importExportService.WriteExportFile(recipeDocument);
var recipe = _recipeParser.ParseRecipe(recipeDocument);
//var recipeDocument = exportActionContext.RecipeDocument;
var exportFilePath = _importExportService.WriteExportFile(exportActionContext);
var recipe = _recipeParser.ParseRecipe(exportActionContext.RecipeDocument);
var exportFileName = recipe.GetExportFileName();
return File(exportFilePath, "text/xml", exportFileName);

View File

@@ -0,0 +1,82 @@
using System.Collections.Specialized;
using System.IO;
using System.Web;
using System.Web.Mvc;
using Orchard.ContentManagement;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
using Orchard.ImportExport.Services;
using Orchard.Security;
using Orchard.Services;
namespace Orchard.ImportExport.Controllers {
[OrchardFeature("Orchard.Deployment")]
public abstract class BaseApiController : Controller {
protected readonly ISigningService _signingService;
protected readonly IAuthenticationService _authenticationService;
protected readonly IClock _clock;
protected BaseApiController(
ISigningService signingService,
IAuthenticationService authenticationService,
IClock clock
) {
_signingService = signingService;
_authenticationService = authenticationService;
_clock = clock;
}
protected ActionResult CreateSignedResponse(string content) {
if (string.IsNullOrWhiteSpace(content)) return Content("");
var user = _authenticationService.GetAuthenticatedUser();
var timestamp = _clock.UtcNow.ToString(_signingService.TimestampFormat);
Response.Headers.Add(_signingService.TimestampHeaderName, timestamp);
Response.Headers.Add(_signingService.ContentHashHeaderName,
_signingService.SignContent(content, timestamp, user.As<DeploymentUserPart>().PrivateApiKey));
return Content(content);
}
protected FilePathResult CreateSignedResponse(FilePathResult result) {
var user = _authenticationService.GetAuthenticatedUser();
var timestamp = _clock.UtcNow.ToString(_signingService.TimestampFormat);
Response.Headers.Add(_signingService.TimestampHeaderName, timestamp);
var package = System.IO.File.ReadAllBytes(result.FileName);
Response.Headers.Add(_signingService.ContentHashHeaderName,
_signingService.SignContent(package, timestamp, user.As<DeploymentUserPart>().PrivateApiKey));
return result;
}
protected bool ValidateContent(string content, NameValueCollection headers) {
if (string.IsNullOrWhiteSpace(content)) return true;
var timestamp = GetHttpRequestHeader(headers, _signingService.TimestampHeaderName);
var contentHash = HttpUtility.UrlDecode(GetHttpRequestHeader(headers, _signingService.ContentHashHeaderName));
var user = _authenticationService.GetAuthenticatedUser();
if (user != null && user.Is<DeploymentUserPart>()) {
return _signingService.ValidateContent(content, timestamp, user.As<DeploymentUserPart>().PrivateApiKey, contentHash);
}
return false;
}
protected bool ValidateContent(Stream content, NameValueCollection headers) {
if (content == null) return true;
var timestamp = GetHttpRequestHeader(headers, _signingService.TimestampHeaderName);
var contentHash = HttpUtility.UrlDecode(GetHttpRequestHeader(headers, _signingService.ContentHashHeaderName));
var user = _authenticationService.GetAuthenticatedUser();
if (user != null && user.Is<DeploymentUserPart>()) {
return _signingService.ValidateContent(content, timestamp, user.As<DeploymentUserPart>().PrivateApiKey, contentHash);
}
return false;
}
protected static string GetHttpRequestHeader(NameValueCollection headers, string headerName) {
return headers[headerName] ?? "";
}
}
}

View File

@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Mvc;
using Orchard.ContentManagement;
using Orchard.DisplayManagement;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Deployment;
using Orchard.ImportExport.Services;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.UI.Admin;
using Orchard.UI.Navigation;
using Orchard.UI.Notify;
namespace Orchard.ImportExport.Controllers {
[Admin]
[OrchardFeature("Orchard.Deployment")]
public class DeploymentConfigurationController : Controller, IUpdateModel {
private readonly IDeploymentService _deploymentService;
public DeploymentConfigurationController(IOrchardServices services,
IDeploymentService deploymentService,
IShapeFactory shapeFactory) {
_deploymentService = deploymentService;
Services = services;
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
Shape = shapeFactory;
}
public IOrchardServices Services { get; private set; }
public Localizer T { get; set; }
public ILogger Logger { get; set; }
private dynamic Shape { get; set; }
public ActionResult Index(PagerParameters pagerParameters) {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ConfigureDeployments, T("Not allowed to configure deployments.")))
return new HttpUnauthorizedResult();
var pager = new Pager(Services.WorkContext.CurrentSite, pagerParameters);
var sourceTargetTypes = _deploymentService.GetDeploymentConfigurationContentTypes().Select(c => c.Name).ToArray();
var configs = sourceTargetTypes.Any() ?
Services.ContentManager.Query(sourceTargetTypes).List<IContent>().ToList() : new List<IContent>();
var pagerShape = Shape.Pager(pager).TotalItemCount(configs.Count());
configs = configs
.Skip(pager.GetStartIndex())
.Take(pager.PageSize)
.ToList();
dynamic viewModel = Shape.ViewModel()
.ContentItems(configs)
.Pager(pagerShape);
return View(viewModel);
}
public ActionResult Create() {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ConfigureDeployments, T("Not allowed to configure deployments.")))
return new HttpUnauthorizedResult();
var sourceAndTargetTypes = _deploymentService.GetDeploymentConfigurationContentTypes().ToArray();
dynamic viewModel = Shape.ViewModel(ContentTypes: sourceAndTargetTypes);
// Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
return View("CreatableTypeList", (object) viewModel);
}
public ActionResult TestConnection(int id) {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ConfigureDeployments, T("Not allowed to configure deployments.")))
return new HttpUnauthorizedResult();
try {
var deploymentSource = _deploymentService.GetDeploymentSource(Services.ContentManager.Get(id));
if (deploymentSource != null) {
deploymentSource.GetContentTypes();
Services.Notifier.Add(NotifyType.Information, T("Successfully tested import from remote target."));
}
}
catch (WebException ex) {
Services.Notifier.Add(NotifyType.Warning,T
("Unable to import from deployment source. Review configuration and ensure that features are enabled, deployment source is configured with required permissions for deployment export of content, and that both servers have the same API key.<br/>{0}", ex.Message));
Logger.Information(ex, "Deployment import connection test failed.");
}
try {
var deploymentTarget = _deploymentService.GetDeploymentTarget(Services.ContentManager.Get(id));
if (deploymentTarget != null) {
deploymentTarget.PushRecipe(Guid.NewGuid().ToString("n"), @"<Orchard><Recipe></Recipe></Orchard>");
Services.Notifier.Add(NotifyType.Information, T("Successfully tested deployment to remote target."));
}
}
catch (WebException ex) {
Services.Notifier.Add(NotifyType.Warning,
T("Unable to deploy content to target. Review configuration and ensure that features are enabled, deployment target is configured with required permissions for deployment import of content, and that both servers have the same API key.<br/>{0}", ex.Message));
Logger.Information(ex, "Deployment export connection test failed.");
}
return RedirectToAction("Index");
}
bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) {
return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
}
public void AddModelError(string key, LocalizedString errorMessage) {
ModelState.AddModelError(key, errorMessage.ToString());
}
}
}

View File

@@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web.Mvc;
using Orchard.ContentManagement;
using Orchard.DisplayManagement;
using Orchard.Environment.Extensions;
using Orchard.FileSystems.AppData;
using Orchard.FileSystems.Media;
using Orchard.ImportExport.Models;
using Orchard.ImportExport.Deployment;
using Orchard.ImportExport.Services;
using Orchard.ImportExport.ViewModels;
using Orchard.Localization;
using Orchard.Recipes.Services;
using Orchard.UI.Admin;
using Orchard.UI.Navigation;
namespace Orchard.ImportExport.Controllers {
[Admin]
[OrchardFeature("Orchard.Deployment")]
public class DeploymentController : Controller {
private const string RecipeJournalFolder = "RecipeJournal";
private readonly IRecurringScheduledTaskManager _taskManager;
private readonly IDeploymentService _deploymentService;
private readonly IStorageProvider _storageProvider;
private readonly IAppDataFolder _appDataFolder;
private readonly IRecipeResultAccessor _recipeResultAccessor;
public DeploymentController(
IOrchardServices services,
IRecurringScheduledTaskManager taskManager,
IDeploymentService deploymentService,
IStorageProvider storageProvider,
IAppDataFolder appDataFolder,
IShapeFactory shapeFactory,
IRecipeResultAccessor recipeResultAccessor) {
_taskManager = taskManager;
_deploymentService = deploymentService;
_storageProvider = storageProvider;
_appDataFolder = appDataFolder;
Services = services;
Shape = shapeFactory;
_recipeResultAccessor = recipeResultAccessor;
T = NullLocalizer.Instance;
}
public IOrchardServices Services { get; private set; }
public Localizer T { get; set; }
private dynamic Shape { get; set; }
public ActionResult Index(PagerParameters pagerParameters) {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ViewDeploymentHistory, T("Not allowed to view deployment history."))) {
return new HttpUnauthorizedResult();
}
var historyItems = GetDeploymentHistory(20);
dynamic viewModel = Shape.ViewModel()
.HistoryItems(historyItems);
return View(viewModel);
}
public ActionResult DeployContent(int id, int targetId, string returnUrl = null, string version = "Published") {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ExportToDeploymentTargets, T("Not allowed to deploy content."))) {
return new HttpUnauthorizedResult();
}
if (version != "Draft" && version != "Published") return HttpNotFound();
var content = Services.ContentManager.Get(id, version == "Draft" ? VersionOptions.Draft : VersionOptions.Published);
if (version == "Draft" && content == null) {
content = Services.ContentManager.Get(id, VersionOptions.Latest);
}
var target = Services.ContentManager.Get(targetId);
if (content == null || target == null) {
return HttpNotFound();
}
_deploymentService.DeployContentToTarget(content, target, version == "Draft");
if (!string.IsNullOrEmpty(returnUrl)) {
return new RedirectResult(returnUrl);
}
return Index(null);
}
public ActionResult QueueContent(int id, int targetId, string returnUrl = null) {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ExportToDeploymentTargets, T("Not allowed to deploy content."))) {
return new HttpUnauthorizedResult();
}
var content = Services.ContentManager.Get(id, VersionOptions.Latest);
var target = Services.ContentManager.Get(targetId);
if (content == null || target == null) {
return HttpNotFound();
}
_deploymentService.QueueContentForDeployment(content, target);
if (!string.IsNullOrEmpty(returnUrl)) {
return new RedirectResult(returnUrl);
}
return Index(null);
}
public ActionResult DownloadRecipe(string executionId) {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ViewDeploymentHistory, T("Not allowed to view recipe."))) {
return new HttpUnauthorizedResult();
}
var path = GetRecipePath(executionId);
if (string.IsNullOrEmpty(path))
return HttpNotFound();
return File(_appDataFolder.MapPath(path), "text/xml", "subscription.xml");
}
private List<DeploymentHistoryViewModel> GetDeploymentHistory(int limit) {
var journals = new List<IStorageFile>().ToDictionary(f=>f.GetName());
//_storageProvider.ListFiles(RecipeJournalFolder)
//.Where(f => !f.GetName().EndsWith(".config", StringComparison.OrdinalIgnoreCase))
//.OrderByDescending(j => j.GetLastUpdated())
//.Take(limit)
//.ToDictionary(f => f.GetName());
var subscriptionRunHistory = _taskManager.GetHistory(null, limit);
var deploymentHistoryItems = new List<DeploymentHistoryViewModel>();
foreach (var subscriptionRun in subscriptionRunHistory) {
DeploymentHistoryViewModel historyItem;
if (!string.IsNullOrEmpty(subscriptionRun.ExecutionId) && journals.ContainsKey(subscriptionRun.ExecutionId)) {
historyItem = CreateHistoryFromJournal(subscriptionRun.ExecutionId, journals[subscriptionRun.ExecutionId]);
journals.Remove(subscriptionRun.ExecutionId);
}
else {
historyItem = new DeploymentHistoryViewModel();
}
historyItem.ExecutionId = subscriptionRun.ExecutionId;
historyItem.SubscriptionId = subscriptionRun.ContentItemRecord != null ? (int?) subscriptionRun.ContentItemRecord.Id : null;
historyItem.RunStatus = subscriptionRun.RunStatus;
historyItem.RunStarted = subscriptionRun.RunStartUtc;
historyItem.RunCompleted = subscriptionRun.RunCompletedUtc;
deploymentHistoryItems.Add(historyItem);
}
//Add any remaining journals that did not have scheduled task runs associated
deploymentHistoryItems.AddRange(journals.Select(j => CreateHistoryFromJournal(j.Key, j.Value)));
return deploymentHistoryItems.OrderByDescending(c => c.RunStarted).ToList();
}
private DeploymentHistoryViewModel CreateHistoryFromJournal(string executionId, IStorageFile journalFile) {
var recipeResult = _recipeResultAccessor.GetResult(executionId);
var metadata = recipeResult.Steps
.Select(m => DeploymentMetadata.FromDisplayString(m.ErrorMessage))
.Where(m => m != null).ToList();
DeploymentType deploymentType;
Enum.TryParse(FindMetaItem(metadata, "DeploymentType"), out deploymentType);
DateTime runStarted;
if (!DateTime.TryParseExact(FindMetaItem(metadata, "StartedUtc"), "u", null, DateTimeStyles.AssumeUniversal, out runStarted)) {
runStarted = journalFile.GetLastUpdated().ToUniversalTime();
}
runStarted = runStarted.ToUniversalTime();
return new DeploymentHistoryViewModel {
ExecutionId = journalFile.GetName(),
RecipeStatus = T(recipeResult.IsSuccessful ? "Successful" : "Failed").ToString(),
DeploymentType = deploymentType,
Source = FindMetaItem(metadata, "Source") ?? "Local",
Target = FindMetaItem(metadata, "Target") ?? "Local",
RunStarted = runStarted,
RecipeFileAvailable = GetRecipePath(executionId) != null
};
}
private string GetRecipePath(string executionId) {
var path = string.Format("{0}/{1}.xml", _deploymentService.DeploymentStoragePath, executionId);
return _appDataFolder.FileExists(path) ? path : null;
}
private string FindMetaItem(IEnumerable<DeploymentMetadata> metadata, string key) {
if (metadata == null)
return null;
var item = metadata.FirstOrDefault(m => m.Key == key);
return item != null ? item.Value : null;
}
}
}

View File

@@ -0,0 +1,102 @@
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Newtonsoft.Json;
using Orchard.ContentManagement;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Handlers;
using Orchard.ImportExport.Models;
using Orchard.ImportExport.Deployment;
using Orchard.ImportExport.Security;
using Orchard.ImportExport.Services;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Mvc.AntiForgery;
using Orchard.Projections.Models;
using Orchard.Security;
using Orchard.Services;
namespace Orchard.ImportExport.Controllers {
[OrchardFeature("Orchard.Deployment.ExportApi")]
public class ExportController : BaseApiController {
private readonly IImportExportService _importExportService;
private readonly IDeploymentService _deploymentService;
public ExportController(
IOrchardServices services,
IImportExportService importExportService,
IDeploymentService deploymentService,
ISigningService signingService,
IAuthenticationService authenticationService,
IClock clock
) : base(signingService, authenticationService, clock) {
_importExportService = importExportService;
_deploymentService = deploymentService;
Services = services;
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
}
public IOrchardServices Services { get; private set; }
public Localizer T { get; set; }
public ILogger Logger { get; set; }
[AuthenticateApi]
[HttpPost]
[ValidateAntiForgeryTokenOrchard(false)]
public ActionResult Recipe(RecipeRequest request) {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ExportToDeploymentTargets, T("Not allowed to export")))
return new HttpUnauthorizedResult();
var exportingItems = _deploymentService.GetContentForExport(request);
var exportSteps = request.DeploymentMetadata != null ?
request.DeploymentMetadata.Select(m => m.ToExportStep()).ToList() : new List<string>();
var packagePath = "";
//var packagePath = _importExportService.Export(request.ContentTypes, exportingItems, new ExportOptions {
// ExportData = exportingItems.Any(),
// ExportMetadata = request.IncludeMetadata,
// ExportFiles = request.IncludeFiles,
// VersionHistoryOptions = request.VersionHistoryOption,
// CustomSteps = exportSteps
//});
return CreateSignedResponse(packagePath);
}
[AuthenticateApi]
[HttpGet]
public ActionResult Queries() {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ExportToDeploymentTargets, T("Not allowed to export")))
return new HttpUnauthorizedResult();
var queries = Services.ContentManager.Query<QueryPart, QueryPartRecord>()
.ForType(new[] {"Query"}).List()
.Select(q =>
new DeploymentQuery {
Name = q.Name,
Identity = Services.ContentManager.GetItemMetadata(q.ContentItem).Identity.ToString()
}).ToList();
var content = JsonConvert.SerializeObject(queries);
return CreateSignedResponse(content);
}
[AuthenticateApi]
[HttpGet]
public ActionResult ContentTypes() {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ExportToDeploymentTargets, T("Not allowed to export")))
return new HttpUnauthorizedResult();
var contentTypes = Services.ContentManager.GetContentTypeDefinitions()
.Select(c => new DeploymentContentType {Name = c.Name, DisplayName = c.DisplayName}).ToList();
var content = JsonConvert.SerializeObject(contentTypes);
return CreateSignedResponse(content);
}
}
}

View File

@@ -0,0 +1,121 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Web.Mvc;
using Orchard.Environment.Extensions;
using Orchard.FileSystems.AppData;
using Orchard.ImportExport.Deployment;
using Orchard.ImportExport.Security;
using Orchard.ImportExport.Services;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Mvc.AntiForgery;
using Orchard.Recipes.Services;
using Orchard.Security;
using Orchard.Services;
namespace Orchard.ImportExport.Controllers {
[OrchardFeature("Orchard.Deployment.ImportApi")]
public class ImportController : BaseApiController {
private readonly IImportExportService _importExportService;
private readonly IAppDataFolder _appData;
private readonly IRecipeResultAccessor _recipeResultAccessor;
public ImportController(
IOrchardServices services,
IImportExportService importExportService,
IAppDataFolder appData,
ISigningService signingService,
IAuthenticationService authenticationService,
IClock clock,
IRecipeResultAccessor recipeResultAccessor)
: base(signingService, authenticationService, clock) {
_importExportService = importExportService;
_appData = appData;
_recipeResultAccessor = recipeResultAccessor;
Services = services;
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
}
public IOrchardServices Services { get; private set; }
public Localizer T { get; set; }
public ILogger Logger { get; set; }
[AuthenticateApi]
[HttpPost]
[ValidateAntiForgeryTokenOrchard(false)]
public ActionResult Recipe(string executionId) {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ImportFromDeploymentSources, T("Not allowed to import")))
return new HttpUnauthorizedResult();
string content;
using (var reader = new StreamReader(Request.InputStream)) {
content = reader.ReadToEndAsync().Result;
}
if (!ValidateContent(content, Request.Headers)) {
return new HttpUnauthorizedResult(T("Invalid recipe").Text);
}
//_importExportService.ImportRecipe(content, null, executionId);
_importExportService.Import(content);
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
[AuthenticateApi]
[HttpGet]
public ActionResult RecipeJournal(string executionId)
{
if (!Services.Authorizer.Authorize(DeploymentPermissions.ImportFromDeploymentSources, T("Not allowed to import")))
return new HttpUnauthorizedResult();
var recipeResult = _recipeResultAccessor.GetResult(executionId);
if (!recipeResult.Steps.Any()) {
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError, T("Unable to locate recipe journal").Text);
}
return CreateSignedResponse(executionId);
}
[AuthenticateApi]
[HttpPost]
[ValidateAntiForgeryTokenOrchard(false)]
public ActionResult DeployContent(string executionId = null) {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ImportFromDeploymentSources, T("Not allowed to import")))
return new HttpUnauthorizedResult();
executionId = executionId ?? Guid.NewGuid().ToString("n");
var files = Request.Files;
if (files.Count > 0) {
var file = files[0];
if (!_appData.DirectoryExists("Deployments"))
{
_appData.CreateDirectory("Deployments");
}
var packagePath = _appData.Combine("Deployments", executionId + ".nupkg");
using (var packageWriteStream = _appData.CreateFile(packagePath))
{
file.InputStream.CopyTo(packageWriteStream);
}
using (var packageStream = _appData.OpenFile(packagePath))
{
if (!ValidateContent(packageStream, Request.Headers))
{
return new HttpUnauthorizedResult();
}
packageStream.Seek(0, SeekOrigin.Begin);
// _importExportService.Import(packageStream, executionId + ".nupkg");
}
}
else {
return Recipe(executionId);
}
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
}
}

View File

@@ -0,0 +1,233 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web.Mvc;
using Orchard.ContentManagement;
using Orchard.DisplayManagement;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
using Orchard.ImportExport.Deployment;
using Orchard.ImportExport.Services;
using Orchard.ImportExport.ViewModels;
using Orchard.Localization;
using Orchard.Recipes.Services;
using Orchard.UI.Admin;
using Orchard.UI.Navigation;
using Orchard.UI.Notify;
namespace Orchard.ImportExport.Controllers {
[Admin]
[OrchardFeature("Orchard.Deployment")]
public class SubscriptionController : Controller, IUpdateModel {
private readonly IOrchardServices _orchardServices;
private readonly ISubscriptionService _subscriptionService;
private readonly IRecurringScheduledTaskManager _recurringScheduledTaskManager;
private readonly IDeploymentService _deploymentService;
private readonly IRecipeResultAccessor _recipeJournal;
public SubscriptionController(
IOrchardServices services,
ISubscriptionService subscriptionService,
IRecurringScheduledTaskManager recurringScheduledTaskManager,
IDeploymentService deploymentService,
IRecipeResultAccessor recipeJournal,
IShapeFactory shapeFactory
) {
_orchardServices = services;
_subscriptionService = subscriptionService;
_recurringScheduledTaskManager = recurringScheduledTaskManager;
_deploymentService = deploymentService;
_recipeJournal = recipeJournal;
Services = services;
T = NullLocalizer.Instance;
Shape = shapeFactory;
}
public IOrchardServices Services { get; private set; }
public Localizer T { get; set; }
private dynamic Shape { get; set; }
public ActionResult Index(PagerParameters pagerParameters) {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ConfigureDeployments, T("Not allowed to configure deployments.")))
return new HttpUnauthorizedResult();
var pager = new Pager(Services.WorkContext.CurrentSite, pagerParameters);
var subscriptions = Services.ContentManager
.Query<DeploymentSubscriptionPart, DeploymentSubscriptionPartRecord>(VersionOptions.Latest)
.List().ToList();
var pagerShape = Shape.Pager(pager).TotalItemCount(subscriptions.Count());
return View(new SubscriptionsViewModel {
Subscriptions = subscriptions
.Skip(pager.GetStartIndex())
.Take(pager.PageSize)
.Select(PopulateSubscriptionSummary)
.ToList(),
Pager = pagerShape
});
}
public ActionResult Create() {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ConfigureDeployments, T("Not allowed to configure deployments.")))
return new HttpUnauthorizedResult();
var model = new CreateSubscriptionViewModel {
Sources = _deploymentService.GetDeploymentSourceConfigurations(),
Targets = _deploymentService.GetDeploymentTargetConfigurations(),
SubscriptionTypes = new List<string> {DeploymentType.Export.ToString(), DeploymentType.Import.ToString()}
};
return View(model);
}
[HttpPost, ActionName("Create")]
public ActionResult CreatePost() {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ConfigureDeployments, T("Not allowed to configure deployments.")))
return new HttpUnauthorizedResult();
var model = new CreateSubscriptionViewModel();
if (!TryUpdateModel(model) || !ModelState.IsValid) {
model.Sources = _deploymentService.GetDeploymentSourceConfigurations();
model.Targets = _deploymentService.GetDeploymentTargetConfigurations();
model.SubscriptionTypes = new List<string> {DeploymentType.Import.ToString(), DeploymentType.Export.ToString()};
return View(model);
}
var subscription = _orchardServices.ContentManager.New("DeploymentSubscription").As<DeploymentSubscriptionPart>();
_orchardServices.ContentManager.Create(subscription);
subscription.Title = model.Title;
DeploymentType deploymentType;
subscription.DeploymentType = Enum.TryParse(model.SelectedDeploymentType, out deploymentType) ? deploymentType : deploymentType;
subscription.DeploymentConfiguration = subscription.DeploymentType == DeploymentType.Import ?
Services.ContentManager.Get(model.SelectedDeploymentSourceId) : Services.ContentManager.Get(model.SelectedDeploymentTargetId);
_orchardServices.ContentManager.Create(subscription);
Services.Notifier.Information(T("Subscription {0} created successfully", subscription.Title));
return RedirectToAction("Edit", new {id = subscription.Id});
}
public ActionResult Edit(int id) {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ConfigureDeployments, T("Not allowed to configure deployments.")))
return new HttpUnauthorizedResult();
var subscription = _orchardServices.ContentManager.Get<DeploymentSubscriptionPart>(id);
if (subscription == null)
return HttpNotFound();
dynamic model = Services.ContentManager.BuildEditor(subscription);
return View((object) model);
}
[HttpPost, ActionName("Edit")]
public ActionResult EditPost(int id) {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ConfigureDeployments, T("Not allowed to configure deployments.")))
return new HttpUnauthorizedResult();
var subscription = _orchardServices.ContentManager.Get<DeploymentSubscriptionPart>(id);
dynamic model = Services.ContentManager.UpdateEditor(subscription, this);
if (!ModelState.IsValid) {
Services.TransactionManager.Cancel();
return View((object) model);
}
Services.Notifier.Information(T("Subscription {0} updated successfully", subscription.Title));
return RedirectToAction("Index");
}
public ActionResult RunSubscriptionTask(int id) {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ConfigureDeployments, T("Not allowed to configure deployments.")))
return new HttpUnauthorizedResult();
_subscriptionService.RunSubscriptionTask(id);
Services.Notifier.Information(T("Subscription has been run"));
return RedirectToAction("Index");
}
public ActionResult DownloadSubscription(int id, string executionId = null) {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ConfigureDeployments, T("Not allowed to configure deployments.")))
return new HttpUnauthorizedResult();
var deploymentFile = _subscriptionService.GetDeploymentFile(id, executionId ?? Guid.NewGuid().ToString("n"));
if (string.IsNullOrEmpty(deploymentFile))
return HttpNotFound();
if (Path.GetExtension(deploymentFile) == ".xml") {
return File(deploymentFile, "text/xml", "subscription.xml");
}
return File(deploymentFile, "application/zip", "subscription.nupkg");
}
public ActionResult GetRecipeJournal(string executionId) {
if (!Services.Authorizer.Authorize(DeploymentPermissions.ViewDeploymentHistory, T("Not allowed to view deployment history.")))
return new HttpUnauthorizedResult();
var journal = _recipeJournal.GetResult(executionId);
var result = Json(journal);
result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
return result;
}
bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) {
return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
}
public void AddModelError(string key, LocalizedString errorMessage) {
ModelState.AddModelError(key, errorMessage.ToString());
}
private SubscriptionSummaryViewModel PopulateSubscriptionSummary(DeploymentSubscriptionPart part) {
var lastTaskRun = _recurringScheduledTaskManager.GetLastTaskRun(part.Id, null);
var nextTaskRun = _recurringScheduledTaskManager.GetNextScheduledTask(part.Id);
var summary = new SubscriptionSummaryViewModel {
Id = part.Id,
Name = part.Title,
DeploymentType = part.DeploymentType,
ContentItem = part.ContentItem
};
if (lastTaskRun != null) {
summary.LastRunStatus = lastTaskRun.RunStatus.ToString();
//Due to roll back of transactions on error, update status if failure logged in journal
if (lastTaskRun.RunStatus == RunStatus.Running) {
var recipeStatus = _recipeJournal.GetResult(lastTaskRun.ExecutionId);
if (!recipeStatus.IsSuccessful) {
_recurringScheduledTaskManager.SetTaskCompleted(lastTaskRun.ExecutionId, RunStatus.Fail);
}
}
switch (lastTaskRun.RunStatus) {
case RunStatus.Started:
case RunStatus.Running:
summary.LastRunDateTime = lastTaskRun.RunStartUtc;
break;
case RunStatus.Success:
case RunStatus.Fail:
case RunStatus.Cancelled:
summary.LastRunDateTime = lastTaskRun.RunCompletedUtc;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
if (nextTaskRun != null && nextTaskRun.ScheduledUtc.HasValue) {
summary.NextRun = nextTaskRun.ScheduledUtc;
}
return summary;
}
}
}

View File

@@ -0,0 +1,35 @@
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Deployment;
using Orchard.Localization;
using Orchard.UI.Navigation;
namespace Orchard.ImportExport.Menus {
[OrchardFeature("Orchard.Deployment")]
public class DeploymentAdminMenu : INavigationProvider {
public Localizer T { get; set; }
public string MenuName {
get { return "admin"; }
}
public void GetNavigation(NavigationBuilder builder) {
builder.AddImageSet("importexport")
.Add(T("Import/Export"), "42", BuildMenu);
}
private void BuildMenu(NavigationItemBuilder menu) {
menu.Add(T("Sources and Targets"), "10", item => item
.Action("Index", "DeploymentConfiguration", new { area = "Orchard.ImportExport" })
.Permission(DeploymentPermissions.ConfigureDeployments)
.LocalNav());
menu.Add(T("Deployments"), "11", item => item
.Action("Index", "Subscription", new { area = "Orchard.ImportExport" })
.Permission(DeploymentPermissions.ConfigureDeployments)
.LocalNav());
menu.Add(T("History"), "12", item => item
.Action("Index", "Deployment", new { area = "Orchard.ImportExport" })
.Permission(DeploymentPermissions.ViewDeploymentHistory)
.LocalNav());
}
}
}

View File

@@ -0,0 +1,58 @@
using System.Collections.Generic;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.Security.Permissions;
namespace Orchard.ImportExport.Deployment {
[OrchardFeature("Orchard.Deployment")]
public class DeploymentPermissions : IPermissionProvider {
public static readonly Permission ExportToDeploymentTargets = new Permission {
Description = "Export to deployment targets",
Name = "ExportToDeploymentTargets"
};
public static readonly Permission ImportFromDeploymentSources = new Permission {
Description = "Import from deployment sources",
Name = "ImportFromDeploymentSources"
};
public static readonly Permission ConfigureDeployments = new Permission {
Description = "Configure Deployments",
Name = "ConfigureDeployments"
};
public static readonly Permission ViewDeploymentHistory = new Permission {
Description = "View deployment history",
Name = "ViewDeploymentHistory",
ImpliedBy = new[] {ConfigureDeployments}
};
public virtual Feature Feature { get; set; }
public IEnumerable<Permission> GetPermissions() {
return new[] {
ExportToDeploymentTargets,
ImportFromDeploymentSources,
ConfigureDeployments,
ViewDeploymentHistory
};
}
public IEnumerable<PermissionStereotype> GetDefaultStereotypes() {
return new[] {
new PermissionStereotype {
Name = "Administrator",
Permissions = new[] {ExportToDeploymentTargets, ImportFromDeploymentSources, ConfigureDeployments}
},
new PermissionStereotype {
Name = "Remote Orchard Publisher",
Permissions = new[] {ExportToDeploymentTargets, Orchard.ImportExport.Permissions.Export}
},
new PermissionStereotype {
Name = "Remote Orchard Subscriber",
Permissions = new[] {ImportFromDeploymentSources, Orchard.ImportExport.Permissions.Import}
}
};
}
}
}

View File

@@ -0,0 +1,170 @@
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Web;
using Orchard.FileSystems.AppData;
using Orchard.ImportExport.Models;
using Orchard.ImportExport.Services;
using Orchard.Localization;
using Orchard.Services;
namespace Orchard.ImportExport.DeploymentTargets {
public class RemoteOrchardApiClient {
private readonly RemoteOrchardDeploymentPart _config;
private readonly ISigningService _signingService;
private readonly IClock _clock;
private readonly IAppDataFolder _appData;
public RemoteOrchardApiClient(
RemoteOrchardDeploymentPart config,
ISigningService signingService,
IClock clock,
IAppDataFolder appData
) {
_config = config;
_signingService = signingService;
_clock = clock;
_appData = appData;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public string Get(string url) {
var fullyQualifiedUri = BuildUri(url);
var timestamp = _clock.UtcNow.ToString(_signingService.TimestampFormat);
var signature = _signingService.SignRequest("GET", timestamp, fullyQualifiedUri.AbsolutePath, _config.PrivateApiKey);
using (var webClient = CreateWebClient(_config.UserName, timestamp, signature, null)) {
using (var stream = webClient.OpenRead(fullyQualifiedUri.ToString())) {
if (stream == null) {
throw new WebException(T("Deployment API did not return a valid stream.").Text);
}
// Response stream doesn't support seeking, need to copy it locally to a temp file.
var tempPath = Path.Combine("temp", Guid.NewGuid().ToString("n"));
using (var tempStream = _appData.CreateFile(tempPath)) {
try {
stream.CopyTo(tempStream);
tempStream.Seek(0, SeekOrigin.Begin);
if (!ResponseIsValid(tempStream,
webClient.ResponseHeaders[_signingService.TimestampHeaderName],
webClient.ResponseHeaders[_signingService.ContentHashHeaderName])) {
throw new WebException(T("Deployment API response does not contain a valid hash").Text);
}
tempStream.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(tempStream)) {
return reader.ReadToEnd();
}
}
finally {
_appData.DeleteFile(tempPath);
}
}
}
}
}
public void PostStream(string url, Stream data) {
var fullyQualifiedUri = BuildUri(url);
var timestamp = _clock.UtcNow.ToString(_signingService.TimestampFormat);
var signature = _signingService.SignRequest("POST", timestamp, fullyQualifiedUri.AbsolutePath, _config.PrivateApiKey);
var requestContentHash = _signingService.SignContent(data, timestamp, _config.PrivateApiKey);
var request = CreateWebRequest(fullyQualifiedUri.ToString(), _config.UserName, timestamp, signature, requestContentHash);
var boundary = "--------------------------" + Guid.NewGuid().ToString("n");
request.ContentType = "multipart/form-data, boundary=" + boundary;
request.Method = "POST";
var headerBytes = Encoding.UTF8.GetBytes(
"--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"export\"; filename=\"export.nupkg\"\r\n" +
"Content-Type: application/zip\r\n\r\n");
var boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
var length = headerBytes.Length + data.Length + boundaryBytes.Length;
request.ContentLength = length;
using(var requestStream = request.GetRequestStream()) {
requestStream.Write(headerBytes, 0, headerBytes.Length);
data.CopyTo(requestStream);
requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
}
request.GetResponse();
}
public string PostForFile(string url, string data, string targetFilePath, string contentType = "application/json") {
var response = GetPostResponse(url, data, contentType);
var deploymentId = Guid.NewGuid().ToString("n");
var filename = deploymentId + (response.Headers["content-type"] == "text/xml" ? ".xml" : ".nupkg");
var path = _appData.Combine(targetFilePath, filename);
using (var file = _appData.CreateFile(path)) {
try {
response.GetResponseStream().CopyTo(file);
file.Seek(0, SeekOrigin.Begin);
if (!ResponseIsValid(file,
response.Headers[_signingService.TimestampHeaderName],
response.Headers[_signingService.ContentHashHeaderName])) {
throw new WebException(T("Deployment API response does not contain a valid hash").Text);
}
return _appData.MapPath(path);
}
catch(Exception) {
_appData.DeleteFile(path);
throw;
}
}
}
public void Post(string url, string data, string contentType = "application/json") {
GetPostResponse(url, data, contentType);
}
private WebResponse GetPostResponse(string url, string data, string contentType) {
var fullyQualifiedUri = BuildUri(url);
var timestamp = _clock.UtcNow.ToString(_signingService.TimestampFormat);
var signature = _signingService.SignRequest("POST", timestamp, fullyQualifiedUri.AbsolutePath, _config.PrivateApiKey);
var requestContentHash = _signingService.SignContent(data, timestamp, _config.PrivateApiKey);
var request = CreateWebRequest(fullyQualifiedUri.ToString(), _config.UserName, timestamp, signature, requestContentHash);
request.Method = "POST";
request.ContentType = contentType;
var dataBytes = Encoding.UTF8.GetBytes(data);
request.GetRequestStream().Write(dataBytes, 0, dataBytes.Length);
var response = request.GetResponse();
// Skip response validation if it's empty
if (response.ContentLength == 0) return response;
return response;
}
private Uri BuildUri(string relativeUrl) {
var baseUri = new Uri(_config.BaseUrl);
return new Uri(baseUri, relativeUrl);
}
private WebClient CreateWebClient(string username, string timestamp, string signature, string contentHash) {
var webClient = new WebClient {Encoding = Encoding.UTF8};
webClient.Headers[_signingService.AuthenticationHeaderName] =
username + ":" + HttpUtility.UrlEncode(signature);
webClient.Headers[_signingService.TimestampHeaderName] = timestamp;
if (!string.IsNullOrEmpty(contentHash)) {
webClient.Headers[_signingService.ContentHashHeaderName] = HttpUtility.UrlEncode(contentHash);
}
return webClient;
}
private WebRequest CreateWebRequest(string url, string username, string timestamp, string signature, string contentHash) {
var request = WebRequest.Create(url);
request.Headers[_signingService.AuthenticationHeaderName] =
username + ":" + HttpUtility.UrlEncode(signature);
request.Headers[_signingService.TimestampHeaderName] = timestamp;
if (!string.IsNullOrEmpty(contentHash)) {
request.Headers[_signingService.ContentHashHeaderName] = HttpUtility.UrlEncode(contentHash);
}
return request;
}
private bool ResponseIsValid(Stream result, string timestamp, string contentHash) {
return (!result.CanRead || _signingService.ValidateContent(result, timestamp, _config.PrivateApiKey, contentHash));
}
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Web.Mvc;
using Newtonsoft.Json;
using Orchard.ContentManagement;
using Orchard.FileSystems.AppData;
using Orchard.ImportExport.Models;
using Orchard.ImportExport.Services;
using Orchard.Services;
namespace Orchard.ImportExport.DeploymentTargets {
public class RemoteOrchardDeploymentSource : IDeploymentSource, IDeploymentSourceProvider {
private RemoteOrchardDeploymentPart DeploymentPart { get; set; }
private Lazy<RemoteOrchardApiClient> Client { get; set; }
private readonly IAppDataFolder _appDataFolder;
private readonly ISigningService _signingService;
private readonly IClock _clock;
private readonly UrlHelper _url;
public RemoteOrchardDeploymentSource(IAppDataFolder appDataFolder, ISigningService signingService, IClock clock, UrlHelper url) {
_appDataFolder = appDataFolder;
_signingService = signingService;
_clock = clock;
_url = url;
}
public DeploymentSourceMatch Match(IContent sourceConfiguration) {
if (!sourceConfiguration.Is<RemoteOrchardDeploymentPart>()) return null;
DeploymentPart = sourceConfiguration.As<RemoteOrchardDeploymentPart>();
Client = new Lazy<RemoteOrchardApiClient>(() => new RemoteOrchardApiClient(DeploymentPart, _signingService, _clock, _appDataFolder));
return new DeploymentSourceMatch {DeploymentSource = this, Priority = 0};
}
public string GetDeploymentFile(RecipeRequest request) {
var actionUrl = _url.Action("Recipe", "Export", new {
area = "Orchard.ImportExport"
});
var data = JsonConvert.SerializeObject(request);
return Client.Value.PostForFile(actionUrl, data, "Deployments");
}
public IList<DeploymentContentType> GetContentTypes() {
var actionUrl = _url.Action("ContentTypes", "Export", new {
area = "Orchard.ImportExport"
});
var result = Client.Value.Get(actionUrl);
return JsonConvert.DeserializeObject<List<DeploymentContentType>>(result);
}
public IList<DeploymentQuery> GetQueries() {
var actionUrl = _url.Action("Queries", "Export", new {
area = "Orchard.ImportExport"
});
var result = Client.Value.Get(actionUrl);
return JsonConvert.DeserializeObject<List<DeploymentQuery>>(result);
}
}
}

View File

@@ -0,0 +1,132 @@
using System;
using System.IO;
using System.Web.Mvc;
using System.Xml.Linq;
using Orchard.ContentManagement;
using Orchard.FileSystems.AppData;
using Orchard.ImportExport.Models;
using Orchard.ImportExport.Services;
using Orchard.Recipes.Models;
using Orchard.Services;
using System.Collections.Generic;
using System.Linq;
namespace Orchard.ImportExport.DeploymentTargets {
public class OrchardDeploymentTarget : IDeploymentTarget, IDeploymentTargetProvider {
private readonly IImportExportService _importExportService;
private readonly ISigningService _signingService;
private RemoteOrchardDeploymentPart DeploymentPart { get; set; }
private Lazy<RemoteOrchardApiClient> Client { get; set; }
private readonly IClock _clock;
private readonly UrlHelper _url;
private readonly IDeploymentPackageBuilder _deploymentPackageBuilder;
private readonly IAppDataFolder _appData;
private readonly IEnumerable<IExportAction> _exportActions;
private readonly IEnumerable<IImportAction> _importActions;
public OrchardDeploymentTarget(
IImportExportService importExportService,
ISigningService signingService,
IClock clock,
UrlHelper url,
IDeploymentPackageBuilder deploymentPackageBuilder,
IAppDataFolder appData,
IEnumerable<IExportAction> exportActions,
IEnumerable<IImportAction> importActions
) {
_importExportService = importExportService;
_signingService = signingService;
_clock = clock;
_url = url;
_deploymentPackageBuilder = deploymentPackageBuilder;
_appData = appData;
_exportActions = exportActions;
_importActions = importActions;
}
public DeploymentTargetMatch Match(IContent targetConfiguration) {
if (targetConfiguration.Is<RemoteOrchardDeploymentPart>()) {
DeploymentPart = targetConfiguration.As<RemoteOrchardDeploymentPart>();
Client = new Lazy<RemoteOrchardApiClient>(() => new RemoteOrchardApiClient(DeploymentPart, _signingService, _clock, _appData));
return new DeploymentTargetMatch { DeploymentTarget = this, Priority = 0 };
}
return null;
}
public void PushDeploymentFile(string executionId, string deploymentFilePath) {
if (Path.GetExtension(deploymentFilePath) == ".xml") {
var actionUrl = _url.Action("Recipe", "Import", new {
area = "Orchard.ImportExport",
executionId
});
var recipe = File.ReadAllText(deploymentFilePath);
Client.Value.Post(actionUrl, recipe, "text/xml");
}
else {
var actionUrl = _url.Action("DeployContent", "Import", new {
area = "Orchard.ImportExport",
executionId
});
using (var deploymentStream = File.OpenRead(deploymentFilePath)) {
Client.Value.PostStream(actionUrl, deploymentStream);
}
}
}
public void PushRecipe(string executionId, string recipeText) {
var actionUrl = _url.Action("Recipe", "Import", new {
area = "Orchard.ImportExport",
executionId
});
Client.Value.Post(actionUrl, recipeText, "text/xml");
}
public bool? GetRecipeDeploymentStatus(string executionId) {
var actionUrl = _url.Action("RecipeJournal", "Import", new {
area = "Orchard.ImportExport",
executionId
});
var journal = Client.Value.Get(actionUrl);
var element = XElement.Parse(journal);
var statusElement = element.Element("Status");
bool status;
if (statusElement != null && Boolean.TryParse(statusElement.Value, out status)) {
return status;
}
return null;
}
public void PushContent(IContent content, bool deployAsDraft = false) {
var actionUrl = _url.Action("DeployContent", "Import", new {
area = "Orchard.ImportExport"
});
//var exportedFilePathResult = _importExportService.Export(
// new[] { content.ContentItem.ContentType },
// new[] { content.ContentItem },
// new ExportOptions {
// ExportData = true,
// ExportAsDraft = deployAsDraft,
// VersionHistoryOptions = VersionHistoryOptions.Published
// });
var exportActionContext = new ExportActionContext();
var exportedFilePathResult = _importExportService.WriteExportFile(exportActionContext);
if (Path.GetExtension("") == ".nupkg") {
using (var packageStream = File.OpenRead(exportedFilePathResult)) {
Client.Value.PostStream(actionUrl, packageStream);
}
}
else {
var recipeText = File.ReadAllText(exportedFilePathResult);
Client.Value.Post(actionUrl, recipeText, "text/xml");
}
File.Delete(exportedFilePathResult);
}
}
}

View File

@@ -0,0 +1,9 @@
using Orchard.ContentManagement.Drivers;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
namespace Orchard.ImportExport.Drivers {
[OrchardFeature("Orchard.Deployment")]
public class DeployableItemTargetPartDriver : ContentPartDriver<DeployableItemTargetPart> {
}
}

View File

@@ -0,0 +1,92 @@
using System;
using System.Linq;
using System.Web.UI.WebControls;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.Core.Common.Models;
using Orchard.Core.Contents.Settings;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
using Orchard.ImportExport.Services;
using Orchard.ImportExport.ViewModels;
using Orchard.Localization;
namespace Orchard.ImportExport.Drivers {
[OrchardFeature("Orchard.Deployment")]
public class CommonPartDriver : ContentPartDriver<CommonPart> {
private readonly IDeploymentService _deploymentService;
public CommonPartDriver(IDeploymentService deploymentService,
IOrchardServices services) {
_deploymentService = deploymentService;
T = NullLocalizer.Instance;
Services = services;
}
public Localizer T { get; set; }
public IOrchardServices Services { get; set; }
//GET
protected override DriverResult Editor(CommonPart part, dynamic shapeHelper) {
var targets = _deploymentService.GetDeploymentTargetConfigurations();
var id = part.ContentItem.Id;
// Don't show deployment info for a new item
if (id == 0) return null;
var contentManager = part.ContentItem.ContentManager;
var typeDefinition = part.ContentItem.TypeDefinition;
var isDraftable = typeDefinition.Settings.GetModel<ContentTypeSettings>().Draftable;
var hasPublished = contentManager.Get(id, VersionOptions.Published) != null;
var itemDeploymentHistory = part.ContentItem.GetDeploymentHistory()
.GroupBy(entry => entry.TargetId)
.Select(group => group.OrderByDescending(entry => entry.DeploymentCompletedUtc).FirstOrDefault())
.ToDictionary(entry => entry.TargetId, entry => entry);
var model = new DeployablePartViewModel {
Part = part,
IsDraftable = isDraftable,
HasPublishedVersion = hasPublished,
Targets = targets.Select(t =>
CreateTargetSummary(part, t, itemDeploymentHistory.ContainsKey(t.Id) ? itemDeploymentHistory[t.Id] : null))
.ToList()
};
return ContentShape("Parts_DeployablePart_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: "Parts/Deployment.DeployablePart",
Model: model,
Prefix: Prefix));
}
//POST
protected override DriverResult Editor(
CommonPart part, IUpdateModel updater, dynamic shapeHelper) {
var model = part;
updater.TryUpdateModel(model, Prefix, null, null);
return Editor(part, shapeHelper);
}
private DeployablePartTargetSummary CreateTargetSummary(CommonPart part, IContent target, ItemDeploymentEntry deploymentEntry) {
var targetName = Services.ContentManager.GetItemMetadata(target).DisplayText;
var itemTarget = _deploymentService.GetDeploymentItemTarget(part, target, false);
var summary = new DeployablePartTargetSummary {
Target = target,
TargetName = targetName,
LastDeploy = deploymentEntry != null
? deploymentEntry.DeploymentCompletedUtc
: itemTarget != null && itemTarget.DeployedUtc.HasValue
? itemTarget.DeployedUtc
: null,
Status = deploymentEntry != null
? deploymentEntry.Status
: itemTarget != null
? itemTarget.DeploymentStatus
: DeploymentStatus.Unknown,
Description = deploymentEntry != null ? deploymentEntry.Description : ""
};
return summary;
}
}
}

View File

@@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
using Orchard.ImportExport.Services;
using Orchard.ImportExport.ViewModels;
using Orchard.Localization;
using Orchard.Projections.Models;
using Orchard.Recipes.Models;
namespace Orchard.ImportExport.Drivers {
[OrchardFeature("Orchard.Deployment")]
public class DeploymentSubscriptionDriver : ContentPartDriver<DeploymentSubscriptionPart> {
private readonly IOrchardServices _orchardServices;
private readonly IDeploymentService _deploymentService;
private readonly Lazy<CultureInfo> _cultureInfo;
public DeploymentSubscriptionDriver(
IOrchardServices orchardServices,
IDeploymentService deploymentService
) {
_orchardServices = orchardServices;
_deploymentService = deploymentService;
// initializing the culture info lazy initializer
_cultureInfo = new Lazy<CultureInfo>(() => CultureInfo.GetCultureInfo(_orchardServices.WorkContext.CurrentCulture));
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
//GET
protected override DriverResult Editor(DeploymentSubscriptionPart part, dynamic shapeHelper) {
var contentTypes = new List<DeploymentContentType>();
var queries = new List<DeploymentQuery>();
List<IContent> deploymentConfigurations;
switch (part.DeploymentType) {
case DeploymentType.Export:
contentTypes = _orchardServices.ContentManager.GetContentTypeDefinitions()
.Select(c => new DeploymentContentType {Name = c.Name, DisplayName = c.DisplayName}).ToList();
queries = _orchardServices.ContentManager.Query("Query").List<QueryPart>().Select(q =>
new DeploymentQuery {
Name = q.Name,
Identity = _orchardServices.ContentManager.GetItemMetadata(q).Identity.ToString()
}).ToList();
deploymentConfigurations = _deploymentService.GetDeploymentTargetConfigurations();
break;
case DeploymentType.Import:
var deploymentSource = _deploymentService.GetDeploymentSource(part.DeploymentConfiguration);
if (deploymentSource != null) {
contentTypes = deploymentSource.GetContentTypes().ToList();
queries = deploymentSource.GetQueries().ToList();
}
deploymentConfigurations = _deploymentService.GetDeploymentSourceConfigurations();
break;
default:
throw new ArgumentOutOfRangeException();
}
queries.Insert(0, new DeploymentQuery {Identity = string.Empty, Name = "None"});
var deploymentDescription = string.Empty;
if (part.DeploymentConfiguration != null) {
deploymentDescription = _orchardServices.ContentManager.GetItemMetadata(part.DeploymentConfiguration).DisplayText;
}
var viewModel = new SubscriptionPartViewModel {
ContentItem = part.ContentItem,
Metadata = part.IncludeMetadata,
Data = part.IncludeData,
Files = part.IncludeFiles,
DeployAsDraft = part.DeployAsDrafts,
ContentTypes = contentTypes,
SelectedContentTypes = part.ContentTypes,
CustomSteps = new List<CustomStepEntry>(),
DeploymentType = part.DeploymentType.ToString(),
DeploymentConfigurationId = part.DeploymentConfiguration != null ? part.DeploymentConfiguration.Id : 0,
DeploymentDescription = deploymentDescription,
FilterChoice = part.Filter.ToString(),
DataImportChoice = part.VersionHistoryOption.ToString(),
Queries = queries,
SelectedQueryIdentity = part.QueryIdentity,
DeploymentConfigurations = deploymentConfigurations
};
if (!part.DeployedChangesToUtc.HasValue) {
return ContentShape("Parts_DeploymentSubscription_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: "Parts/Deployment.Subscription",
Model: viewModel,
Prefix: Prefix));
}
// date and time are formatted using the same patterns as DateTimePicker is, preventing other cultures issues
var localDate = new Lazy<DateTime>(() => TimeZoneInfo.ConvertTimeFromUtc(
part.DeployedChangesToUtc.Value,
_orchardServices.WorkContext.CurrentTimeZone));
viewModel.DeployedChangesToDisplay = string.Format("{0} {1}",
localDate.Value.ToString("d", _cultureInfo.Value),
localDate.Value.ToString("t", _cultureInfo.Value));
return ContentShape("Parts_DeploymentSubscription_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: "Parts/Deployment.Subscription",
Model: viewModel,
Prefix: Prefix));
}
//POST
protected override DriverResult Editor(
DeploymentSubscriptionPart part, IUpdateModel updater, dynamic shapeHelper) {
var viewModel = new SubscriptionPartViewModel();
if (!updater.TryUpdateModel(viewModel, Prefix, null, null)) return Editor(part, shapeHelper);
part.IncludeMetadata = viewModel.Metadata;
part.IncludeData = viewModel.Data;
part.IncludeFiles = viewModel.Files;
part.DeployAsDrafts = viewModel.DeployAsDraft;
part.Filter = (FilterOptions) Enum.Parse(typeof (FilterOptions), viewModel.FilterChoice);
part.VersionHistoryOption = (VersionHistoryOptions) Enum.Parse(typeof (VersionHistoryOptions), viewModel.DataImportChoice);
part.ContentTypes = viewModel.SelectedContentTypes.ToList();
part.QueryIdentity = viewModel.SelectedQueryIdentity;
part.DeploymentConfiguration = _orchardServices.ContentManager.Get(viewModel.DeploymentConfigurationId);
if (string.IsNullOrWhiteSpace(viewModel.DeployedChangesToDate)
|| string.IsNullOrWhiteSpace(viewModel.DeployedChangesToTime)) {
return Editor(part, shapeHelper);
}
DateTime scheduled;
var parseDateTime = String.Concat(viewModel.DeployedChangesToDate, " ", viewModel.DeployedChangesToTime);
// use current culture
if (!DateTime.TryParse(parseDateTime, _cultureInfo.Value, DateTimeStyles.None, out scheduled)) {
return Editor(part, shapeHelper);
}
// the date time is entered locally for the configured timezone
var timeZone = _orchardServices.WorkContext.CurrentTimeZone;
part.DeployedChangesToUtc = TimeZoneInfo.ConvertTimeToUtc(scheduled, timeZone);
return Editor(part, shapeHelper);
}
}
}

View File

@@ -0,0 +1,58 @@
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
using Orchard.ImportExport.ViewModels;
using Orchard.Localization;
namespace Orchard.ImportExport.Drivers {
[OrchardFeature("Orchard.Deployment")]
public class DeploymentUserPartDriver : ContentPartDriver<DeploymentUserPart> {
public DeploymentUserPartDriver() {
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
//GET
protected override DriverResult Editor(DeploymentUserPart part, dynamic shapeHelper) {
var model = new EditDeploymentUserViewModel {
PrivateApiKey = part.PrivateApiKey,
EnableApiAccess = !string.IsNullOrEmpty(part.PrivateApiKey),
};
return ContentShape("Parts_DeploymentUser_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: "Parts/Deployment.DeploymentUser",
Model: model,
Prefix: Prefix));
}
//POST
protected override DriverResult Editor(
DeploymentUserPart part, IUpdateModel updater, dynamic shapeHelper) {
var model = new EditDeploymentUserViewModel();
updater.TryUpdateModel(model, Prefix, null, null);
if (model.EnableApiAccess) {
if (string.IsNullOrEmpty(model.PrivateApiKey) && string.IsNullOrEmpty(part.PrivateApiKey)) {
updater.AddModelError("PrivateApiKey", T("If API access is enabled, the private API key is required."));
}
else if (!string.IsNullOrEmpty(model.PrivateApiKey) && model.PrivateApiKey.Length < 20) {
updater.AddModelError("PrivateApiKey", T("API key must be a minimum of 20 characters."));
}
else if (!string.IsNullOrEmpty(model.PrivateApiKey)) {
part.PrivateApiKey = model.PrivateApiKey;
}
//Otherwise key has not been updated.
}
else {
part.PrivateApiKey = null;
}
return Editor(part, shapeHelper);
}
}
}

View File

@@ -0,0 +1,56 @@
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
using Orchard.ImportExport.Services;
using Orchard.ImportExport.ViewModels;
using Orchard.Localization;
namespace Orchard.ImportExport.Drivers {
[OrchardFeature("Orchard.Deployment")]
public class RecurringTaskPartDriver : ContentPartDriver<RecurringTaskPart> {
private readonly IRecurringScheduledTaskManager _taskManager;
public RecurringTaskPartDriver(
IRecurringScheduledTaskManager taskManager,
IOrchardServices services
) {
_taskManager = taskManager;
T = NullLocalizer.Instance;
Services = services;
}
public Localizer T { get; set; }
public IOrchardServices Services { get; set; }
//GET
protected override DriverResult Editor(RecurringTaskPart part, dynamic shapeHelper) {
var model = new RecurringTaskViewModel {
IsActive = part.IsActive,
RepeatFrequencyInMinutes = part.RepeatFrequencyInMinutes,
};
return ContentShape("Parts_RecurringTask_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: "Parts/Deployment.RecurringTask",
Model: model,
Prefix: Prefix));
}
//POST
protected override DriverResult Editor(
RecurringTaskPart part, IUpdateModel updater, dynamic shapeHelper) {
var model = new RecurringTaskViewModel();
updater.TryUpdateModel(model, Prefix, null, null);
part.RepeatFrequencyInMinutes = model.RepeatFrequencyInMinutes;
part.IsActive = model.IsActive;
//Schedule date time is not required if the task is run on demand
if (model.IsActive) {
_taskManager.ScheduleTaskForNextRun(part, true);
}
return Editor(part, shapeHelper);
}
}
}

View File

@@ -0,0 +1,33 @@
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
namespace Orchard.ImportExport.Drivers {
[OrchardFeature("Orchard.Deployment")]
public class RemoteOrchardDeploymentPartDriver : ContentPartDriver<RemoteOrchardDeploymentPart> {
//GET
protected override DriverResult Editor(RemoteOrchardDeploymentPart part, dynamic shapeHelper) {
return ContentShape("Parts_RemoteOrchardDeployment_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: "Parts/Deployment.RemoteOrchardDeployment",
Model: part,
Prefix: Prefix));
}
//POST
protected override DriverResult Editor(
RemoteOrchardDeploymentPart part, IUpdateModel updater, dynamic shapeHelper) {
var previousPassword = part.PrivateApiKey;
updater.TryUpdateModel(part, Prefix, null, null);
// restore password if the input is empty, meaning it has not been reset
if (string.IsNullOrEmpty(part.PrivateApiKey)) {
part.PrivateApiKey = previousPassword;
}
return Editor(part, shapeHelper);
}
}
}

View File

@@ -0,0 +1,13 @@
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
namespace Orchard.ImportExport.Handlers {
[OrchardFeature("Orchard.Deployment")]
public class DeployableItemTargetPartHandler : ContentHandler {
public DeployableItemTargetPartHandler(IRepository<DeployableItemTargetPartRecord> repository) {
Filters.Add(StorageFilter.For(repository));
}
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Linq;
using System.Xml.Linq;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
using Orchard.ImportExport.Services;
using Orchard.Localization;
namespace Orchard.ImportExport.Handlers {
[OrchardFeature("Orchard.Deployment")]
public class DeploymentMetadataExportEventHandler : IExportEventHandler {
public const string StepName = "DeploymentMeta";
public DeploymentMetadataExportEventHandler() {
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public void Exporting(ExportContext context) {
//Not required
}
public void Exported(ExportContext context) {
var deploymentMetaSteps = context.ExportOptions.CustomSteps
.Select(DeploymentMetadata.FromExportStep)
.Where(c => c != null)
.Select(m => m.ToDisplayString())
.ToList();
if (!deploymentMetaSteps.Any()) return;
var recipeDescription = string.Join(";", deploymentMetaSteps);
var orchardElement = context.Document.Element("Orchard");
if (orchardElement == null) {
throw new InvalidOperationException(T("Recipe document does not have a top-level Orchard element.").Text);
}
var recipeElement = orchardElement.Element("Recipe");
if (recipeElement == null) {
throw new InvalidOperationException(T("Recipe document does not have a recipe element under the Orchard element.").Text);
}
recipeElement.Add(new XElement("Description", recipeDescription));
}
}
}

View File

@@ -0,0 +1,13 @@
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
namespace Orchard.ImportExport.Handlers {
[OrchardFeature("Orchard.Deployment")]
public class DeploymentSubscriptionHandler : ContentHandler {
public DeploymentSubscriptionHandler(IRepository<DeploymentSubscriptionPartRecord> repository) {
Filters.Add(StorageFilter.For(repository));
}
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Security.Cryptography;
using System.Text;
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Security;
namespace Orchard.ImportExport.Handlers {
[OrchardFeature("Orchard.Deployment")]
public class DeploymentUserPartHandler : ContentHandler {
private readonly IEncryptionService _encryptionService;
public DeploymentUserPartHandler(IRepository<DeploymentUserPartRecord> repository,
IEncryptionService encryptionService) {
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
Filters.Add(StorageFilter.For(repository));
_encryptionService = encryptionService;
OnActivated<DeploymentUserPart>(LazyLoadHandlers);
Filters.Add(new ActivatingFilter<DeploymentUserPart>("User"));
}
public new ILogger Logger { get; set; }
public Localizer T { get; set; }
private void LazyLoadHandlers(ActivatedContentContext context, DeploymentUserPart part) {
part.PrivateApiKeyField.Getter(() => {
try {
return String.IsNullOrWhiteSpace(part.Record.PrivateApiKey) ?
String.Empty : Encoding.UTF8.GetString(_encryptionService.Decode(Convert.FromBase64String(part.Record.PrivateApiKey)));
}
catch (CryptographicException) {
LogDecryptionError();
}
catch (ArgumentException) {
LogDecryptionError();
}
catch (FormatException) {
LogDecryptionError();
}
return null;
});
part.PrivateApiKeyField.Setter(value => part.Record.PrivateApiKey = String.IsNullOrWhiteSpace(value) ?
String.Empty : Convert.ToBase64String(_encryptionService.Encode(Encoding.UTF8.GetBytes(value))));
}
private void LogDecryptionError() {
Logger.Error(T("The remote orchard user password could not be decrypted. It might be corrupted, try to reset it.").Text);
}
}
}

View File

@@ -0,0 +1,144 @@
using System;
using System.Linq;
using Orchard.ContentManagement;
using Orchard.Environment.Extensions;
using Orchard.FileSystems.AppData;
using Orchard.ImportExport.Models;
using Orchard.ImportExport.Services;
using Orchard.Recipes.Events;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
using Orchard.Services;
using Orchard.Logging;
namespace Orchard.ImportExport.Handlers {
[OrchardFeature("Orchard.Deployment")]
public class RecipeExecuteEventHandler : Component, IRecipeExecuteEventHandler {
private readonly IRecurringScheduledTaskManager _recurringScheduledTaskManager;
private readonly IContentManager _contentManager;
private readonly IRecipeResultAccessor _recipeJournal;
private readonly IAppDataFolder _appDataFolder;
private readonly IRecipeSerializer _recipeSerializer;
private readonly IRecipeLoggerFactory _recipeLoggerFactory;
private readonly IRecipeResultAccessor _recipeResultAccessor;
private readonly IClock _clock;
public RecipeExecuteEventHandler(
IRecurringScheduledTaskManager recurringScheduledTaskManager,
IContentManager contentManager,
IRecipeResultAccessor recipeJournal,
IAppDataFolder appDataFolder,
IRecipeSerializer recipeSerializer,
IRecipeLoggerFactory recipeLoggerFactory,
IRecipeResultAccessor recipeResultAccessor,
IClock clock
) {
_recurringScheduledTaskManager = recurringScheduledTaskManager;
_contentManager = contentManager;
_recipeJournal = recipeJournal;
_appDataFolder = appDataFolder;
_recipeSerializer = recipeSerializer;
_recipeLoggerFactory = recipeLoggerFactory;
_recipeResultAccessor = recipeResultAccessor;
_clock = clock;
}
public void ExecutionStart(string executionId, Recipe recipe) {
var deploymentMetaItems = (recipe.Description ?? string.Empty)
.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries)
.Select(DeploymentMetadata.FromDisplayString)
.ToList();
int subscriptionId;
var subscriptionMeta = deploymentMetaItems.FirstOrDefault(m => m.Key == "Subscription");
if (subscriptionMeta != null &&
int.TryParse(subscriptionMeta.Value, out subscriptionId)) {
var task = _contentManager.Get<RecurringTaskPart>(subscriptionId);
if (task != null) {
_recurringScheduledTaskManager.SetTaskStarted(task, executionId);
}
}
if (recipe == null) throw new ArgumentNullException("recipe");
var recipeLogger = _recipeLoggerFactory.CreateLogger(executionId);
recipeLogger.Information(new DeploymentMetadata("DeploymentType", DeploymentType.Import.ToString()).ToDisplayString());
foreach (var deploymentMetaItem in deploymentMetaItems) {
recipeLogger.Information(deploymentMetaItem.ToDisplayString());
}
if (recipe.ExportUtc.HasValue) {
recipeLogger.Information(new DeploymentMetadata("ExportUtc", recipe.ExportUtc.Value.ToString("u")).ToDisplayString());
}
recipeLogger.Information(new DeploymentMetadata("StartedUtc", _clock.UtcNow.ToString("u")).ToDisplayString());
//Save recipe to deployment folder
if (!_appDataFolder.DirectoryExists("Deployments")) {
_appDataFolder.CreateDirectory("Deployments");
}
var path = _appDataFolder.Combine("Deployments", executionId + ".xml");
if (!_appDataFolder.FileExists(path)) {
_appDataFolder.CreateFile(path, _recipeSerializer.Serialize(recipe));
}
}
public void RecipeStepExecuting(string executionId, RecipeContext context) {
_recurringScheduledTaskManager.UpdateTaskRunStatus(executionId, RunStatus.Running);
}
public void RecipeStepExecuted(string executionId, RecipeContext context) {
if (context.RecipeStep.Name != "Data") return;
int tempBatchSize, tempBatchStartIndex;
var itemCount = context.RecipeStep.Step.Elements().Count();
var recipeLogger = _recipeLoggerFactory.CreateLogger(executionId);
if (context.RecipeStep.Step.Attribute("BatchSize") != null &&
int.TryParse(context.RecipeStep.Step.Attribute("BatchSize").Value, out tempBatchSize) &&
context.RecipeStep.Step.Attribute("BatchStartIndex") != null &&
int.TryParse(context.RecipeStep.Step.Attribute("BatchStartIndex").Value, out tempBatchStartIndex)) {
var lastIndex = tempBatchStartIndex + tempBatchSize > itemCount - 1 ? itemCount - 1 : tempBatchStartIndex + tempBatchSize - 1;
var firstId = context.RecipeStep.Step.Elements().ElementAt(tempBatchStartIndex).Attribute("Id");
var lastId = context.RecipeStep.Step.Elements().ElementAt(lastIndex).Attribute("Id");
recipeLogger.Information(T("Successfully imported items {0} to {1}. First item id {2} to last item id {3}",
tempBatchStartIndex + 1, lastIndex + 1, firstId.Value, lastId.Value).ToString());
}
else {
recipeLogger.Information(T("Successfully imported {0} items.", itemCount).ToString());
}
}
public void ExecutionComplete(string executionId) {
var recipeLogger = _recipeLoggerFactory.CreateLogger(executionId);
recipeLogger.Information(new DeploymentMetadata("CompletedUtc", _clock.UtcNow.ToString("u")).ToDisplayString());
//Update the generic task
_recurringScheduledTaskManager.SetTaskCompleted(executionId, RunStatus.Success);
//Update subscription export date
var task = _recurringScheduledTaskManager.GetTaskRunByExecutionId(executionId);
if (task == null || task.ContentItemRecord == null) return;
var subscription = _contentManager.Get<DeploymentSubscriptionPart>(task.ContentItemRecord.Id);
if (subscription == null) return;
var journal = _recipeResultAccessor.GetResult(executionId);
var exportUtcMeta = journal != null ?
journal.Steps.Select(m => DeploymentMetadata.FromDisplayString(m.ErrorMessage)).FirstOrDefault(m => m != null && m.Key == "ExportUtc") : null;
DateTime exportUtc;
if (exportUtcMeta != null && DateTime.TryParse(exportUtcMeta.Value, out exportUtc)) {
subscription.DeployedChangesToUtc = exportUtc;
}
}
public void ExecutionFailed(string executionId) {
_recurringScheduledTaskManager.SetTaskCompleted(executionId, RunStatus.Fail);
}
}
}

View File

@@ -0,0 +1,13 @@
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
namespace Orchard.ImportExport.Handlers {
[OrchardFeature("Orchard.Deployment")]
public class RecurringTaskPartHandler : ContentHandler {
public RecurringTaskPartHandler(IRepository<RecurringTaskPartRecord> repository) {
Filters.Add(StorageFilter.For(repository));
}
}
}

View File

@@ -0,0 +1,57 @@
using System;
using System.Security.Cryptography;
using System.Text;
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Security;
namespace Orchard.ImportExport.Handlers {
[OrchardFeature("Orchard.Deployment")]
public class RemoteOrchardDeploymentPartHandler : ContentHandler {
private readonly IEncryptionService _encryptionService;
public RemoteOrchardDeploymentPartHandler(IRepository<RemoteOrchardDeploymentPartRecord> repository,
IEncryptionService encryptionService) {
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
Filters.Add(StorageFilter.For(repository));
_encryptionService = encryptionService;
OnActivated<RemoteOrchardDeploymentPart>(LazyLoadHandlers);
}
public new ILogger Logger { get; set; }
public Localizer T { get; set; }
private void LazyLoadHandlers(ActivatedContentContext context, RemoteOrchardDeploymentPart part) {
part.PrivateApiKeyField.Getter(() => {
try {
return String.IsNullOrWhiteSpace(part.Record.PrivateApiKey) ?
String.Empty : Encoding.UTF8.GetString(_encryptionService.Decode(Convert.FromBase64String(part.Record.PrivateApiKey)));
}
catch (CryptographicException) {
LogDecryptionError();
}
catch (ArgumentException) {
LogDecryptionError();
}
catch (FormatException) {
LogDecryptionError();
}
return null;
});
part.PrivateApiKeyField.Setter(value => part.Record.PrivateApiKey = String.IsNullOrWhiteSpace(value) ?
String.Empty : Convert.ToBase64String(_encryptionService.Encode(Encoding.UTF8.GetBytes(value))));
}
private void LogDecryptionError() {
Logger.Error(T("The remote orchard user password could not be decrypted. It might be corrupted, try to reset it.").Text);
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
using Orchard.ContentManagement;
using Orchard.Core.Common.Models;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
using Orchard.ImportExport.Services;
using Orchard.Logging;
using Orchard.Security;
using Orchard.Tasks.Scheduling;
namespace Orchard.ImportExport.Handlers {
[OrchardFeature("Orchard.Deployment")]
public class SubscriptionTaskHandler : IScheduledTaskHandler {
private readonly IRecurringScheduledTaskManager _taskManager;
private readonly ISubscriptionService _subscriptionService;
private readonly IAuthenticationService _authenticationService;
public const string TaskType = "RecurringTask.Subscription";
public SubscriptionTaskHandler(IRecurringScheduledTaskManager taskManager,
ISubscriptionService subscriptionService,
IAuthenticationService authenticationService) {
_taskManager = taskManager;
_subscriptionService = subscriptionService;
_authenticationService = authenticationService;
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public void Process(ScheduledTaskContext context) {
//each step of the import process is executed in its own transaction
if (context.Task.TaskType == TaskType && context.Task.ContentItem != null) {
var subscription = context.Task.ContentItem.As<DeploymentSubscriptionPart>();
string executionId = null;
try {
//By default there is no current user in the workcontext for background tasks.
//Set to the owner of the subscription.
_authenticationService.SetAuthenticatedUserForRequest(subscription.As<CommonPart>().Owner);
//recipe event handlers update run status as the recipe executes
executionId = _subscriptionService.RunSubscriptionTask(subscription.Id);
}
catch (Exception e) {
//Scheduling of import has failed.
Logger.Error(e, e.Message);
if (!string.IsNullOrEmpty(executionId)) {
_taskManager.SetTaskCompleted(executionId, RunStatus.Fail);
}
}
}
}
}
}

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
using Orchard.Environment.Extensions.Models;
using Orchard.Security.Permissions;
namespace Orchard.ImportExport.Permissions {
public class ImportExportPermissions : IPermissionProvider {
public static readonly Permission Import = new Permission { Description = "Import Data", Name = "Import" };
public static readonly Permission Export = new Permission { Description = "Export Data", Name = "Export" };
public virtual Feature Feature { get; set; }
public IEnumerable<Permission> GetPermissions() {
return new[] { Import, Export };
}
public IEnumerable<PermissionStereotype> GetDefaultStereotypes() {
return new[] {
new PermissionStereotype {
Name = "Administrator",
Permissions = new[] {Import, Export}
}
};
}
}
}

View File

@@ -0,0 +1,100 @@
using System;
using Orchard.ContentManagement.MetaData;
using Orchard.Core.Common.Models;
using Orchard.Core.Title.Models;
using Orchard.Data.Migration;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Handlers;
using Orchard.ImportExport.Models;
namespace Orchard.ImportExport.Migrations {
[OrchardFeature("Orchard.Deployment")]
public class DeploymentMigrations : DataMigrationImpl {
public int Create() {
SchemaBuilder.CreateTable("DeploymentSubscriptionPartRecord", table => table
.ContentPartRecord()
.Column<bool>("IncludeMetadata")
.Column<bool>("IncludeData")
.Column<bool>("IncludeFiles")
.Column<bool>("DeployAsDrafts")
.Column<string>("VersionHistoryOption")
.Column<string>("ContentTypes", col => col.Unlimited())
.Column<string>("QueryIdentity")
.Column<DateTime>("DeployedChangesToUtc", c => c.Nullable())
.Column<string>("Filter")
.Column<int>("DeploymentConfigurationId", c => c.Nullable())
.Column<string>("DeploymentType"));
SchemaBuilder.CreateTable("ScheduledTaskRunHistory", table => table
.Column<int>("Id", column => column.PrimaryKey().Identity())
.Column<int>("ContentItemRecord_id")
.Column<string>("ExecutionId")
.Column<DateTime>("RunStartUtc", c => c.Nullable())
.Column<DateTime>("RunCompletedUtc", c => c.Nullable())
.Column<string>("RunStatus"));
SchemaBuilder.CreateTable("RecurringTaskPartRecord", table => table
.ContentPartRecord()
.Column<bool>("IsActive")
.Column<int>("RepeatFrequencyInMinutes"));
SchemaBuilder.CreateTable("RemoteOrchardDeploymentPartRecord", table => table
.ContentPartRecord()
.Column<string>("BaseUrl")
.Column<string>("UserName"));
SchemaBuilder.CreateTable("DeployablePartRecord", table => table
.ContentPartVersionRecord()
.Column<DateTime>("ImportedPublishedUtc", c => c.Nullable())
.Column<DateTime>("UnpublishedUtc", c => c.Nullable())
.Column<bool>("Latest"));
SchemaBuilder.CreateTable("DeployableItemTargetPartRecord", table => table
.ContentPartVersionRecord()
.Column<int>("DeployableContentId")
.Column<int>("DeploymentTargetId")
.Column<DateTime>("DeployedUtc", c => c.Nullable())
.Column<string>("DeploymentStatus")
.Column<string>("ExecutionId"));
ContentDefinitionManager.AlterTypeDefinition("DeploymentSubscription", cfg => cfg
.WithPart(typeof (DeploymentSubscriptionPart).Name)
.WithPart(typeof (RecurringTaskPart).Name, part => part.WithSetting("TaskType", SubscriptionTaskHandler.TaskType))
.WithPart(typeof (TitlePart).Name)
.WithPart(typeof (CommonPart).Name)
.WithPart(typeof (IdentityPart).Name));
ContentDefinitionManager.AlterTypeDefinition("RemoteOrchardDeployment", cfg => cfg
.WithPart(typeof (RemoteOrchardDeploymentPart).Name)
.WithPart(typeof (TitlePart).Name)
.WithPart(typeof (CommonPart).Name)
.WithPart(typeof (IdentityPart).Name)
.WithSetting("Stereotype", "DeploymentConfiguration"));
ContentDefinitionManager.AlterTypeDefinition("DeployableItemTarget", cfg => cfg
.WithPart(typeof (DeployableItemTargetPart).Name)
.WithPart(typeof (CommonPart).Name));
SchemaBuilder.CreateTable("DeploymentUserPartRecord", table => table
.ContentPartRecord()
.Column<string>("PrivateApiKey", c => c.Unlimited()));
SchemaBuilder.AlterTable("RemoteOrchardDeploymentPartRecord", table => table
.AddColumn<string>("PrivateApiKey", c => c.Unlimited()));
return 3;
}
public int UpdateFrom1() {
SchemaBuilder.AlterTable("DeploymentSubscriptionPartRecord", table => table
.AddColumn<bool>("IncludeFiles"));
return 2;
}
public int UpdateFrom2() {
SchemaBuilder.AlterTable("DeploymentSubscriptionPartRecord", table => table
.AddColumn<bool>("DeployAsDrafts"));
return 3;
}
}
}

View File

@@ -0,0 +1,71 @@
using System;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
using Orchard.Environment.Extensions;
namespace Orchard.ImportExport.Models {
[OrchardFeature("Orchard.Deployment")]
public class DeployableItemTargetPartRecord : ContentPartVersionRecord {
public virtual int DeployableContentId { get; set; }
public virtual int DeploymentTargetId { get; set; }
public virtual DateTime? DeployedUtc { get; set; }
public virtual string DeploymentStatus { get; set; }
public virtual string ExecutionId { get; set; }
}
public class DeployableItemTargetPart : ContentPart<DeployableItemTargetPartRecord> {
private IContent _deployableContent;
public IContent DeployableContent {
get {
_deployableContent = _deployableContent ?? ContentItem.ContentManager.Get(Record.DeployableContentId);
return _deployableContent;
}
set {
if (value == null) {
throw new ArgumentNullException("value", "DeployableContent cannot be set to null");
}
Record.DeployableContentId = value.Id;
_deployableContent = value;
}
}
public IContent DeploymentTarget {
get {
return ContentItem.ContentManager.Get<IContent>(Record.DeploymentTargetId);
}
set {
if (value == null) {
throw new ArgumentNullException("value", "DeploymentTarget cannot be set to null");
}
Record.DeploymentTargetId = value.Id;
}
}
public DateTime? DeployedUtc {
get { return Record.DeployedUtc; }
set { Record.DeployedUtc = value; }
}
public DeploymentStatus DeploymentStatus {
get {
DeploymentStatus status;
return Enum.TryParse(Record.DeploymentStatus, out status) ? status : DeploymentStatus.Unknown;
}
set { Record.DeploymentStatus = value.ToString(); }
}
public string ExecutionId
{
get { return Record.ExecutionId; }
set { Record.ExecutionId = value; }
}
}
public enum DeploymentStatus {
Queued,
Successful,
Failed,
Unknown
}
}

View File

@@ -0,0 +1,6 @@
namespace Orchard.ImportExport.Models {
public enum DeploymentAction {
Deploy,
Queue
}
}

View File

@@ -0,0 +1,6 @@
namespace Orchard.ImportExport.Models {
public class DeploymentContentType {
public string Name { get; set; }
public string DisplayName { get; set; }
}
}

View File

@@ -0,0 +1,65 @@
using System.Linq;
namespace Orchard.ImportExport.Models {
public class DeploymentMetadata {
private string _key;
private string _value;
public const string ExportStepPrefix = "DeploymentMetadata";
public string Key {
get { return _key; }
set { _key = CleanString(value, ";", ":"); }
}
public string Value {
get { return _value; }
set { _value = CleanString(value, ";"); }
}
public DeploymentMetadata() {}
public DeploymentMetadata(string key, string value) {
Key = key;
Value = value;
}
public string ToDisplayString() {
return string.Format("{0}: {1}", Key, Value);
}
public string ToExportStep() {
return string.Format("{0}{1}: {2}", ExportStepPrefix, Key, Value);
}
public static DeploymentMetadata FromDisplayString(string displayString) {
if (string.IsNullOrWhiteSpace(displayString) || displayString.IndexOf(':') < 0)
return null;
var key = displayString.Substring(0, displayString.IndexOf(':')).Trim();
var value = displayString.Substring(displayString.IndexOf(':') + 1).Trim();
return !string.IsNullOrEmpty(key) ? new DeploymentMetadata {Key = key, Value = value} : null;
}
/// <summary>
/// Extras a key (SomeKey) and value (Some value) pair from a string e.g. 'DeploymentMetadataSomeKey: Some value'
/// </summary>
/// <param name="exportStep"></param>
/// <returns></returns>
public static DeploymentMetadata FromExportStep(string exportStep) {
if (exportStep == null || !exportStep.StartsWith(ExportStepPrefix))
return null;
var key = exportStep.Substring(0, exportStep.IndexOf(':')).Replace(ExportStepPrefix, string.Empty).Trim();
var value = exportStep.Substring(exportStep.IndexOf(':') + 1).Trim();
return !string.IsNullOrEmpty(key) ? new DeploymentMetadata {Key = key, Value = value} : null;
}
private static string CleanString(string input, params string[] illegalStrings) {
return string.IsNullOrEmpty(input)
? input
: illegalStrings.Aggregate(input, (current, illegalString) => current.Replace(illegalString, string.Empty));
}
}
}

View File

@@ -0,0 +1,6 @@
namespace Orchard.ImportExport.Models {
public class DeploymentQuery {
public string Identity { get; set; }
public string Name { get; set; }
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
using Orchard.Core.Title.Models;
using Orchard.Data.Conventions;
using Orchard.Environment.Extensions;
using Orchard.Recipes.Models;
namespace Orchard.ImportExport.Models {
[OrchardFeature("Orchard.Deployment")]
public class DeploymentSubscriptionPartRecord : ContentPartRecord {
public virtual int? DeploymentConfigurationId { get; set; }
public virtual bool IncludeMetadata { get; set; }
public virtual bool IncludeData { get; set; }
public virtual bool IncludeFiles { get; set; }
public virtual bool DeployAsDrafts { get; set; }
public virtual VersionHistoryOptions VersionHistoryOption { get; set; }
public virtual FilterOptions Filter { get; set; }
public virtual string DeploymentType { get; set; }
[StringLengthMax]
public virtual string ContentTypes { get; set; }
public virtual string QueryIdentity { get; set; }
public virtual DateTime? DeployedChangesToUtc { get; set; }
}
public class DeploymentSubscriptionPart : ContentPart<DeploymentSubscriptionPartRecord> {
public string Title {
get { return ContentItem.As<TitlePart>().Title; }
set { ContentItem.As<TitlePart>().Title = value; }
}
public IContent DeploymentConfiguration {
get {
return Record.DeploymentConfigurationId.HasValue ?
ContentItem.ContentManager.Get(Record.DeploymentConfigurationId.Value) : null;
}
set { Record.DeploymentConfigurationId = value.Id; }
}
public DeploymentType DeploymentType {
get {
DeploymentType type;
return Enum.TryParse(Record.DeploymentType, out type) ? type : DeploymentType.Export;
}
set { Record.DeploymentType = value.ToString(); }
}
public bool IncludeMetadata {
get { return Record.IncludeMetadata; }
set { Record.IncludeMetadata = value; }
}
public bool IncludeData {
get { return Record.IncludeData; }
set { Record.IncludeData = value; }
}
public bool IncludeFiles {
get { return Record.IncludeFiles; }
set { Record.IncludeFiles = value; }
}
public bool DeployAsDrafts {
get { return Record.DeployAsDrafts; }
set { Record.DeployAsDrafts = value; }
}
public VersionHistoryOptions VersionHistoryOption {
get { return Record.VersionHistoryOption; }
set { Record.VersionHistoryOption = value; }
}
public FilterOptions Filter {
get { return Record.Filter; }
set { Record.Filter = value; }
}
public List<string> ContentTypes {
get {
return string.IsNullOrEmpty(Record.ContentTypes) ?
new List<string>() : Record.ContentTypes.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries).ToList();
}
set { Record.ContentTypes = string.Join(",", value); }
}
public string QueryIdentity {
get { return Record.QueryIdentity; }
set { Record.QueryIdentity = value; }
}
public DateTime? DeployedChangesToUtc {
get { return Record.DeployedChangesToUtc; }
set { Record.DeployedChangesToUtc = value; }
}
}
public enum FilterOptions {
AllItems,
ChangesSinceLastImport,
QueuedDeployableItems
}
}

View File

@@ -0,0 +1,6 @@
namespace Orchard.ImportExport.Models {
public enum DeploymentType {
Import,
Export
}
}

View File

@@ -0,0 +1,25 @@
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
using Orchard.ContentManagement.Utilities;
using Orchard.Environment.Extensions;
namespace Orchard.ImportExport.Models {
[OrchardFeature("Orchard.Deployment")]
public class DeploymentUserPartRecord : ContentPartRecord {
public virtual string PrivateApiKey { get; set; }
}
[OrchardFeature("Orchard.Deployment")]
public class DeploymentUserPart : ContentPart<DeploymentUserPartRecord> {
private readonly ComputedField<string> _privateApiKey = new ComputedField<string>();
public ComputedField<string> PrivateApiKeyField {
get { return _privateApiKey; }
}
public string PrivateApiKey {
get { return _privateApiKey.Value; }
set { _privateApiKey.Value = value; }
}
}
}

View File

@@ -1,10 +1,15 @@
using System.Xml.Linq;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Handlers;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Orchard.ImportExport.Models {
public class ExportActionContext {
public ExportActionContext() {
RecipeDocument = new XDocument();
Files = new List<ExportedFileDescription>();
}
public XDocument RecipeDocument { get; set; }
public IList<ExportedFileDescription> Files { get; set; }
}
}

View File

@@ -1,10 +1,14 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Orchard.ContentManagement.Handlers;
namespace Orchard.ImportExport.Models {
public class ExportContext {
public XDocument Document { get; set; }
[Obsolete]
public ExportOptions ExportOptions { get; set; }
public IList<ExportedFileDescription> Files { get; set; }
}
}

View File

@@ -6,4 +6,9 @@ namespace Orchard.ImportExport.Models {
public class ExportOptions {
public IEnumerable<string> CustomSteps { get; set; }
}
//public enum VersionHistoryOptions
//{
// Published,
// Draft,
//}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Orchard.ImportExport.Models {
public class ItemDeploymentEntry {
public int TargetId { get; set; }
public DateTime? DeploymentCompletedUtc { get; set; }
public DeploymentStatus Status { get; set; }
public string Description { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using Orchard.Recipes.Models;
namespace Orchard.ImportExport.Models {
public class RecipeRequest {
public bool IncludeMetadata { get; set; }
public bool IncludeData { get; set; }
public bool IncludeFiles { get; set; }
public bool DeployAsDrafts { get; set; }
public string QueryIdentity { get; set; }
public VersionHistoryOptions VersionHistoryOption { get; set; }
public DateTime? DeployChangesAfterUtc { get; set; }
public List<string> ContentIdentities { get; set; }
public List<string> ContentTypes { get; set; }
public List<DeploymentMetadata> DeploymentMetadata { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
using Orchard.Environment.Extensions;
namespace Orchard.ImportExport.Models {
[OrchardFeature("Orchard.Deployment")]
public class RecurringTaskPartRecord : ContentPartRecord {
public virtual bool IsActive { get; set; }
public virtual int RepeatFrequencyInMinutes { get; set; }
}
[OrchardFeature("Orchard.Deployment")]
public class RecurringTaskPart : ContentPart<RecurringTaskPartRecord> {
public bool IsActive {
get { return Record.IsActive; }
set { Record.IsActive = value; }
}
public int RepeatFrequencyInMinutes {
get { return Record.RepeatFrequencyInMinutes; }
set { Record.RepeatFrequencyInMinutes = value; }
}
}
}

View File

@@ -0,0 +1,38 @@
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
using Orchard.ContentManagement.Utilities;
using Orchard.Environment.Extensions;
namespace Orchard.ImportExport.Models {
[OrchardFeature("Orchard.Deployment")]
public class RemoteOrchardDeploymentPartRecord : ContentPartRecord {
public virtual string BaseUrl { get; set; }
public virtual string UserName { get; set; }
public virtual string PrivateApiKey { get; set; }
}
[OrchardFeature("Orchard.Deployment")]
public class RemoteOrchardDeploymentPart : ContentPart<RemoteOrchardDeploymentPartRecord> {
private readonly ComputedField<string> _privateApiKey = new ComputedField<string>();
public ComputedField<string> PrivateApiKeyField
{
get { return _privateApiKey; }
}
public string BaseUrl {
get { return Record.BaseUrl; }
set { Record.BaseUrl = value; }
}
public string UserName {
get { return Record.UserName; }
set { Record.UserName = value; }
}
public string PrivateApiKey {
get { return _privateApiKey.Value; }
set { _privateApiKey.Value = value; }
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using Orchard.ContentManagement.Records;
namespace Orchard.ImportExport.Models {
public class ScheduledTaskRunHistory {
public virtual int Id { get; set; }
public virtual string ExecutionId { get; set; }
public virtual DateTime? RunStartUtc { get; set; }
public virtual DateTime? RunCompletedUtc { get; set; }
public virtual RunStatus RunStatus { get; set; }
public virtual ContentItemRecord ContentItemRecord { get; set; }
}
public enum RunStatus {
Started,
Running,
Success,
Fail,
Cancelled
}
}

View File

@@ -12,3 +12,18 @@ Features:
Description: Imports and exports content item data.
Category: Content
Dependencies: Orchard.Resources, Orchard.Recipes, Orchard.Setup.Services
Orchard.Deployment:
Name: Orchard Deployment
Description: Deploy content between CMS instances using workflows, subscriptions or individual items
Category: Content
Dependencies: Orchard.ImportExport,Orchard.Projections,Orchard.Recipes,Orchard.Workflows
Orchard.Deployment.ImportApi:
Name: Orchard Deployment Import API
Category: Content
Description: Exposes remote export APIs for use by the deployment feature.
Dependencies: Orchard.Deployment
Orchard.Deployment.ExportApi:
Name: Orchard Deployment Export API
Category: Content
Description: Exposes remote import APIs for use by the deployment feature.
Dependencies: Orchard.Deployment

View File

@@ -61,10 +61,15 @@
<HintPath>..\..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NHibernate, Version=4.0.0.4000, Culture=neutral, PublicKeyToken=aa95f207798dfdb4, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\NHibernate.4.0.1.4000\lib\net40\NHibernate.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NuGet.Core, Version=1.6.30117.9648, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Data.DataSetExtensions" />
@@ -77,6 +82,10 @@
<HintPath>..\..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
</Reference>
<Reference Include="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll</HintPath>
<Private>True</Private>
@@ -102,18 +111,89 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AdminMenu.cs" />
<Compile Include="Controllers\BaseApiController.cs" />
<Compile Include="Controllers\DeploymentConfigurationController.cs" />
<Compile Include="Controllers\DeploymentController.cs" />
<Compile Include="Controllers\ExportController.cs" />
<Compile Include="Controllers\ImportController.cs" />
<Compile Include="Controllers\SubscriptionController.cs" />
<Compile Include="DeploymentAdminMenu.cs" />
<Compile Include="DeploymentPermissions.cs" />
<Compile Include="DeploymentTargets\RemoteOrchardApiClient.cs" />
<Compile Include="DeploymentTargets\RemoteOrchardDeploymentSource.cs" />
<Compile Include="DeploymentTargets\RemoteOrchardDeploymentTarget.cs" />
<Compile Include="Drivers\DeployableItemTargetPartDriver.cs" />
<Compile Include="Drivers\DeployablePartDriver.cs" />
<Compile Include="Drivers\DeploymentSubscriptionPartDriver.cs" />
<Compile Include="Drivers\DeploymentUserPartDriver.cs" />
<Compile Include="Drivers\RecurringTaskPartDriver.cs" />
<Compile Include="Drivers\RemoteOrchardDeploymentPartDriver.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Handlers\DeployableItemTargetPartHandler.cs" />
<Compile Include="Handlers\DeploymentMetadataExportEventHandler.cs" />
<Compile Include="Handlers\DeploymentSubscriptionPartHandler.cs" />
<Compile Include="Handlers\DeploymentUserPartHandler.cs" />
<Compile Include="Handlers\RecurringTaskPartHandler.cs" />
<Compile Include="Handlers\RemoteOrchardDeploymentPartHandler.cs" />
<Compile Include="Handlers\SubscriptionTaskHandler.cs" />
<Compile Include="Migrations\DeploymentMigrations.cs" />
<Compile Include="Models\ConfigureImportActionsContext.cs" />
<Compile Include="Commands\ImportExportCommands.cs" />
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Models\DeployableItemTargetPart.cs" />
<Compile Include="Models\DeploymentAction.cs" />
<Compile Include="Models\DeploymentContentType.cs" />
<Compile Include="Models\DeploymentMetadata.cs" />
<Compile Include="Models\DeploymentQuery.cs" />
<Compile Include="Models\DeploymentSubscriptionPart.cs" />
<Compile Include="Models\DeploymentType.cs" />
<Compile Include="Models\DeploymentUserPart.cs" />
<Compile Include="Models\ExportOptions.cs" />
<Compile Include="Permissions.cs" />
<Compile Include="Models\ItemDeploymentEntry.cs" />
<Compile Include="Models\RecipeRequest.cs" />
<Compile Include="Models\RecurringTaskPart.cs" />
<Compile Include="Models\RemoteOrchardDeploymentPart.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Models\ScheduledTaskRunHistory.cs" />
<Compile Include="Permissions.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Providers\ExportActions\BuildRecipeAction.cs" />
<Compile Include="Models\ExportActionConfigurationContext.cs" />
<Compile Include="Models\ImportActionConfigurationContext.cs" />
<Compile Include="Routes\DeploymentApiRoutes.cs" />
<Compile Include="Security\ApiIdentity.cs" />
<Compile Include="Security\ApiPrincipal.cs" />
<Compile Include="Security\AuthenticateApiAttribute.cs" />
<Compile Include="Services\DatabaseManager.cs" />
<Compile Include="Services\DeploymentPackageBuilder.cs" />
<Compile Include="Services\DeploymentService.cs" />
<Compile Include="Services\IDatabaseManager.cs" />
<Compile Include="Services\IDeploymentService.cs" />
<Compile Include="Services\IDeploymentSource.cs" />
<Compile Include="Services\IDeploymentTarget.cs" />
<Compile Include="Services\IItemDeploymentHistory.cs" />
<Compile Include="Services\IRecurringScheduledTaskManager.cs" />
<Compile Include="Services\ISigningService.cs" />
<Compile Include="Services\ISubscriptionService.cs" />
<Compile Include="Services\ItemDeploymentHistory.cs" />
<Compile Include="Services\RecurringScheduledTaskManager.cs" />
<Compile Include="Services\SigningService.cs" />
<Compile Include="Services\SubscriptionService.cs" />
<Compile Include="Tokens\DeploymentTokens.cs" />
<Compile Include="ViewModels\CreateSubscriptionViewModel.cs" />
<Compile Include="ViewModels\DeployablePartTargetSettingViewModel.cs" />
<Compile Include="ViewModels\DeployablePartViewModel.cs" />
<Compile Include="ViewModels\DeploymentHistoryViewModel.cs" />
<Compile Include="ViewModels\EditDeploymentUserViewModel.cs" />
<Compile Include="ViewModels\RecipeExecutionStepViewModel.cs" />
<Compile Include="ViewModels\RecurringTaskViewModel.cs" />
<Compile Include="ViewModels\SubscriptionPartViewModel.cs" />
<Compile Include="ViewModels\SubscriptionSummaryViewModel.cs" />
<Compile Include="ViewModels\SubscriptionsViewModel.cs" />
<Compile Include="ViewModels\UploadRecipeViewModel.cs" />
<Compile Include="Providers\ImportActions\ExecuteRecipeAction.cs" />
<Compile Include="Recipes\Builders\CustomStepsStep.cs" />
@@ -137,6 +217,8 @@
<Compile Include="ViewModels\ImportResultViewModel.cs" />
<Compile Include="ViewModels\ImportViewModel.cs" />
<Compile Include="ViewModels\RecipeBuilderViewModel.cs" />
<Compile Include="Workflow\DeployActionForm.cs" />
<Compile Include="Workflow\DeployActivity.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Module.txt" />
@@ -152,18 +234,34 @@
<Name>Orchard.Core</Name>
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\Orchard.Forms\Orchard.Forms.csproj">
<Project>{642a49d7-8752-4177-80d6-bfbbcfad3de0}</Project>
<Name>Orchard.Forms</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.MultiTenancy\Orchard.MultiTenancy.csproj">
<Project>{72457126-e118-4171-a08f-9a709ee4b7fc}</Project>
<Name>Orchard.MultiTenancy</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Projections\Orchard.Projections.csproj">
<Project>{5531e894-d259-45a3-aa61-26dbe720c1ce}</Project>
<Name>Orchard.Projections</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Recipes\Orchard.Recipes.csproj">
<Project>{fc1d74e8-7a4d-48f4-83de-95c6173780c4}</Project>
<Name>Orchard.Recipes</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Roles\Orchard.Roles.csproj">
<Project>{d10ad48f-407d-4db5-a328-173ec7cb010f}</Project>
<Name>Orchard.Roles</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Setup\Orchard.Setup.csproj">
<Project>{8c7fcbc2-e6e1-405e-bfb5-d8d9e67a09c4}</Project>
<Name>Orchard.Setup</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Workflows\Orchard.Workflows.csproj">
<Project>{7059493c-8251-4764-9c1e-2368b8b485bc}</Project>
<Name>Orchard.Workflows</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="Styles\images\menu.importexport.png" />
@@ -192,11 +290,30 @@
<Content Include="Views\EditorTemplates\ExportActions\BuildRecipe.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Admin\ImportResult.cshtml" />
<Content Include="placement.info" />
<Content Include="Views\DeploymentConfiguration.cshtml" />
<Content Include="Views\DeploymentConfiguration.Edit.cshtml" />
<Content Include="Views\DeploymentConfiguration\CreatableTypeList.cshtml" />
<Content Include="Views\DeploymentConfiguration\Index.cshtml" />
<Content Include="Views\Deployment\Index.cshtml" />
<Content Include="Views\SubscriptionList.cshtml" />
<Content Include="Views\Subscription\Create.cshtml" />
<Content Include="Views\Subscription\Edit.cshtml" />
<Content Include="Views\Subscription\Index.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Admin\ImportResult.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\EditorTemplates\Parts\Deployment.DeployablePart.cshtml" />
<Content Include="Views\EditorTemplates\Parts\Deployment.DeploymentUser.cshtml" />
<Content Include="Views\EditorTemplates\Parts\Deployment.RecurringTask.cshtml" />
<Content Include="Views\EditorTemplates\Parts\Deployment.RemoteOrchardDeployment.cshtml" />
<Content Include="Views\EditorTemplates\Parts\Deployment.Subscription.cshtml" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>

View File

@@ -1,32 +1,35 @@
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Routing;
using Orchard.Environment.Extensions;
using Orchard.Mvc.Routes;
namespace Orchard.Azure.Authentication {
public class Routes : IRouteProvider {
namespace Orchard.ImportExport.Routes {
[OrchardFeature("Orchard.Deployment")]
public class DeploymentApiRoutes : IRouteProvider {
public void GetRoutes(ICollection<RouteDescriptor> routes) {
foreach (var routeDescriptor in GetRoutes()) {
routes.Add(routeDescriptor);
}
}
public IEnumerable<RouteDescriptor> GetRoutes() {
return new[] {
new RouteDescriptor {
Route = new Route(
"Users/Account/{action}",
Priority = 5,
Route = new Route("api/deployment/{controller}/{action}/{id}",
new RouteValueDictionary {
{"area", "Orchard.Azure.Authentication"},
{"controller", "Account"}
{"area", "Orchard.ImportExport"},
{"id", null}
},
new RouteValueDictionary(),
new RouteValueDictionary {
{"area", "Orchard.Azure.Authentication"}
{"area", "Orchard.ImportExport"}
},
new MvcRouteHandler())
}
};
}
public void GetRoutes(ICollection<RouteDescriptor> routes) {
foreach (var route in GetRoutes()) {
routes.Add(route);
}
}
}
}

View File

@@ -0,0 +1,15 @@
using System.Security.Principal;
using Orchard.Security;
namespace Orchard.ImportExport.Security {
public class ApiIdentity : IIdentity {
private readonly IUser _user;
public ApiIdentity(IUser user) {
_user = user;
}
public string Name { get { return _user.UserName; } }
public string AuthenticationType { get { return "Api"; } }
public bool IsAuthenticated { get { return true; } }
}
}

View File

@@ -0,0 +1,19 @@
using System.Security.Principal;
using Orchard.ContentManagement;
using Orchard.Roles.Models;
using Orchard.Security;
namespace Orchard.ImportExport.Security {
public class ApiPrincipal : IPrincipal {
private readonly IUser _user;
public ApiPrincipal(IUser user) {
_user = user;
}
public bool IsInRole(string role) {
return _user.As<UserRolesPart>().Roles.Contains(role);
}
public IIdentity Identity { get { return new ApiIdentity(_user); } }
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Specialized;
using System.Web;
using System.Web.ClientServices;
using System.Web.Mvc;
using System.Web.Mvc.Filters;
using System.Web.Security;
using Orchard.ContentManagement;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
using Orchard.ImportExport.Deployment;
using Orchard.ImportExport.Services;
using Orchard.Logging;
using Orchard.Security;
namespace Orchard.ImportExport.Security {
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
[OrchardFeature("Orchard.Deployment")]
public class AuthenticateApiAttribute : ActionFilterAttribute, IAuthenticationFilter {
public ILogger Logger { get; set; }
public void OnAuthentication(AuthenticationContext filterContext) {
var workContext = filterContext.Controller.ControllerContext.GetWorkContext();
var membershipService = workContext.Resolve<IMembershipService>();
var authenticationService = workContext.Resolve<IAuthenticationService>();
var authorizationService = workContext.Resolve<IAuthorizationService>();
var signingService = workContext.Resolve<ISigningService>();
Logger = NullLogger.Instance;
try {
var request = filterContext.RequestContext.HttpContext.Request;
var headers = request.Headers;
var timeStampString = GetHttpRequestHeader(headers, signingService.TimestampHeaderName);
var authenticationString = GetHttpRequestHeader(headers, signingService.AuthenticationHeaderName);
if (string.IsNullOrEmpty(timeStampString) || string.IsNullOrEmpty(authenticationString)) {
filterContext.Result = new HttpUnauthorizedResult();
return;
}
var authenticationParts = authenticationString.Split(new[] {":"},
StringSplitOptions.RemoveEmptyEntries);
if (authenticationParts.Length != 2) {
filterContext.Result = new HttpUnauthorizedResult();
return;
}
var username = authenticationParts[0];
var signature = HttpUtility.UrlDecode(authenticationParts[1]);
var user = membershipService.GetUser(username);
var methodType = request.HttpMethod;
var absolutePath = request.Url.AbsolutePath.ToLower();
var uri = HttpUtility.UrlDecode(absolutePath);
var deploymentUser = user.ContentItem.As<DeploymentUserPart>();
var isAuthenticated = signingService.ValidateRequest(methodType, timeStampString, uri, deploymentUser.PrivateApiKey, signature);
if (isAuthenticated &&
(authorizationService.TryCheckAccess(DeploymentPermissions.ImportFromDeploymentSources, user, null) ||
authorizationService.TryCheckAccess(DeploymentPermissions.ExportToDeploymentTargets, user, null))) {
filterContext.Principal = new ApiPrincipal(user);
authenticationService.SetAuthenticatedUserForRequest(user);
}
else {
filterContext.Result = new HttpUnauthorizedResult();
}
}
catch (Exception ex) {
Logger.Error("Credentials could not be validated. Ensure they are in the correct format.", ex);
filterContext.Result = new HttpUnauthorizedResult();
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext) {
var user = filterContext.HttpContext.User;
if (user == null || !user.Identity.IsAuthenticated) {
filterContext.Result = new HttpUnauthorizedResult();
}
}
private static string GetHttpRequestHeader(NameValueCollection headers, string headerName) {
return headers[headerName] ?? "";
}
}
}

View File

@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;
using NuGet;
using Orchard.ContentManagement.Handlers;
using Orchard.Localization;
namespace Orchard.ImportExport.Services {
public interface IDeploymentPackageBuilder : IDependency {
Stream BuildPackage(string packageName, XDocument recipe, IList<ExportedFileDescription> files);
}
public class DeploymentPackageBuilder : IDeploymentPackageBuilder {
public Localizer T { get; set; }
public DeploymentPackageBuilder() {
T = NullLocalizer.Instance;
}
public Stream BuildPackage(string packageName, XDocument recipe, IList<ExportedFileDescription> files) {
var context = new CreateContext {
TargetPath = @"\Content\",
Files = files
};
BeginPackage(context);
try {
SetCoreProperties(context, packageName);
EmbedXmlFile(context, recipe, "export.xml");
EmbedFiles(context);
}
finally {
EndPackage(context);
}
if (context.Stream.CanSeek) {
context.Stream.Seek(0, SeekOrigin.Begin);
}
return context.Stream;
}
private void SetCoreProperties(CreateContext context, string packageName) {
context.Builder.Id = packageName;
context.Builder.Version = new SemanticVersion(new Version());
context.Builder.Title = packageName;
context.Builder.Description = T("{0} Deployment Package generated by Orchard", packageName).Text;
context.Builder.Authors.Add(T("Orchard").Text);
}
private void EmbedFiles(CreateContext context) {
foreach (var file in context.Files) {
EmbedExportedFile(context, file);
}
}
private static void BeginPackage(CreateContext context) {
context.Stream = new MemoryStream();
context.Builder = new PackageBuilder();
}
private static void EmbedExportedFile(CreateContext context, ExportedFileDescription fileDescription) {
var file = new ExportedPackageFile(
fileDescription,
context.TargetPath + fileDescription.LocalPath);
context.Builder.Files.Add(file);
}
private static void EmbedXmlFile(CreateContext context, XDocument recipe, string path) {
var file = new XmlPackageFile(recipe, context.TargetPath + path);
context.Builder.Files.Add(file);
}
private static void EndPackage(CreateContext context) {
context.Builder.Save(context.Stream);
}
private class CreateContext {
public Stream Stream { get; set; }
public PackageBuilder Builder { get; set; }
public string TargetPath { get; set; }
public IList<ExportedFileDescription> Files { get; set; }
}
private class ExportedPackageFile : IPackageFile {
private readonly ExportedFileDescription _fileDescription;
private readonly string _packagePath;
public ExportedPackageFile(ExportedFileDescription fileDescription, string packagePath) {
_fileDescription = fileDescription;
_packagePath = packagePath;
}
public string Path { get { return _packagePath; } }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Supposed to return an open stream.")]
public Stream GetStream() {
return _fileDescription.Contents.OpenRead();
}
}
private class XmlPackageFile : IPackageFile {
private readonly XDocument _document;
private readonly string _packagePath;
public XmlPackageFile(XDocument document, string packagePath) {
_document = document;
_packagePath = packagePath;
}
public string Path { get { return _packagePath; } }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Supposed to return an open stream.")]
public Stream GetStream() {
var stream = new MemoryStream();
_document.Save(stream);
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
}
}
}

View File

@@ -0,0 +1,274 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData.Models;
using Orchard.Core.Common.Models;
using Orchard.Environment.Extensions;
using Orchard.ImportExport.Models;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Projections.Services;
using Orchard.Services;
using Orchard.Recipes.Models;
namespace Orchard.ImportExport.Services {
[OrchardFeature("Orchard.Deployment")]
public class DeploymentService : IDeploymentService {
private readonly IOrchardServices _orchardServices;
private readonly IProjectionManager _projectionManager;
private readonly Lazy<IEnumerable<IDeploymentSourceProvider>> _deploymentSourceProviders;
private readonly Lazy<IEnumerable<IDeploymentTargetProvider>> _deploymentTargetProviders;
private readonly IClock _clock;
public DeploymentService(
IOrchardServices orchardServices,
IProjectionManager projectionManager,
Lazy<IEnumerable<IDeploymentSourceProvider>> deploymentSourceProviders,
Lazy<IEnumerable<IDeploymentTargetProvider>> deploymentTargetProviders,
IClock clock
) {
_orchardServices = orchardServices;
_projectionManager = projectionManager;
_deploymentSourceProviders = deploymentSourceProviders;
_deploymentTargetProviders = deploymentTargetProviders;
_clock = clock;
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
}
public Localizer T { get; set; }
public ILogger Logger { get; private set; }
public string DeploymentStoragePath {
get { return "Deployments"; }
}
public IDeploymentSource GetDeploymentSource(IContent configuration) {
var bestSourceProviderMatch = _deploymentSourceProviders.Value
.Select(provider => provider.Match(configuration))
.Where(match => match != null && match.DeploymentSource != null)
.OrderByDescending(match => match.Priority)
.FirstOrDefault();
return bestSourceProviderMatch != null
? bestSourceProviderMatch.DeploymentSource
: null;
}
public IDeploymentTarget GetDeploymentTarget(IContent configuration) {
var bestTargetProviderMatch = _deploymentTargetProviders.Value
.Select(provider => provider.Match(configuration))
.Where(match => match != null && match.DeploymentTarget != null)
.OrderByDescending(match => match.Priority)
.FirstOrDefault();
return bestTargetProviderMatch != null
? bestTargetProviderMatch.DeploymentTarget
: null;
}
public List<IContent> GetDeploymentSourceConfigurations() {
return _orchardServices.ContentManager
.Query(
GetDeploymentConfigurationContentTypes()
.Select(c => c.Name)
.ToArray())
.List<IContent>()
.Where(config =>
GetDeploymentSource(config) != null)
.ToList();
}
public List<IContent> GetDeploymentTargetConfigurations() {
return _orchardServices.ContentManager
.Query(
GetDeploymentConfigurationContentTypes()
.Select(c => c.Name)
.ToArray())
.List<IContent>()
.Where(config =>
GetDeploymentTarget(config) != null)
.ToList();
}
public List<ContentTypeDefinition> GetDeploymentConfigurationContentTypes() {
return _orchardServices.ContentManager.GetContentTypeDefinitions()
.Where(contentTypeDefinition =>
contentTypeDefinition.Settings
.ContainsKey("Stereotype")
&& contentTypeDefinition.Settings["Stereotype"]
.Split(',')
.Select(s => s.Trim())
.Contains("DeploymentConfiguration"))
.ToList();
}
public List<DeployableItemTargetPart> GetItemsPendingDeployment(IContent deploymentTarget) {
return _orchardServices.ContentManager
.Query<DeployableItemTargetPart, DeployableItemTargetPartRecord>()
.Where(c =>
c.DeploymentTargetId == deploymentTarget.Id
&& c.DeploymentStatus == DeploymentStatus.Queued.ToString())
.List()
.ToList();
}
public DeployableItemTargetPart GetDeploymentItemTarget(IContent deployableContent, IContent targetConfiguration, bool createIfNotFound = true) {
var itemTarget = _orchardServices.ContentManager
.Query<DeployableItemTargetPart, DeployableItemTargetPartRecord>()
.Where(c =>
c.DeployableContentId == deployableContent.Id
&& c.DeploymentTargetId == targetConfiguration.Id)
.Slice(0, 2)
.SingleOrDefault();
if (itemTarget != null || !createIfNotFound) {
return itemTarget;
}
itemTarget = _orchardServices.ContentManager.Create<DeployableItemTargetPart>("DeployableItemTarget");
itemTarget.DeployableContent = deployableContent;
itemTarget.DeploymentTarget = targetConfiguration;
return itemTarget;
}
public void DeployContent(DeployableItemTargetPart deployableContent) {
var targetConfigs = GetDeploymentTargetConfigurations();
foreach (var config in targetConfigs) {
DeployContentToTarget(deployableContent, config);
}
}
public void DeployContentToTarget(IContent content, IContent targetConfiguration, bool deployAsDraft = false) {
var deploymentTarget = GetDeploymentTarget(targetConfiguration);
if (deploymentTarget == null) return;
var itemTarget = GetDeploymentItemTarget(content, targetConfiguration);
try {
deploymentTarget.PushContent(content, deployAsDraft);
itemTarget.DeploymentStatus = DeploymentStatus.Successful;
content.AddDeploymentHistoryEntry(new ItemDeploymentEntry {
DeploymentCompletedUtc = DateTime.UtcNow,
TargetId = targetConfiguration.Id,
Status = DeploymentStatus.Successful,
Description = deployAsDraft
? T("Deployed as single draft item.").Text
: T("Deployed as single item.").Text
});
}
catch (Exception ex) {
Logger.Error(ex, "Error deploying content item {0}", content.Id);
itemTarget.DeploymentStatus = DeploymentStatus.Failed;
content.AddDeploymentHistoryEntry(new ItemDeploymentEntry {
DeploymentCompletedUtc = DateTime.UtcNow,
TargetId = targetConfiguration.Id,
Status = DeploymentStatus.Failed,
Description = T("Single item deployment failed with \"{0}\".", ex.Message).Text
});
}
itemTarget.DeployedUtc = _clock.UtcNow;
}
public void QueueContentForDeployment(IContent content, IContent targetConfiguration) {
try {
var itemTarget = GetDeploymentItemTarget(content, targetConfiguration);
itemTarget.DeploymentStatus = DeploymentStatus.Queued;
content.AddDeploymentHistoryEntry(new ItemDeploymentEntry {
DeploymentCompletedUtc = DateTime.UtcNow,
TargetId = targetConfiguration.Id,
Status = DeploymentStatus.Queued,
Description = T("Queued as single item.").Text
});
}
catch (Exception e) {
content.AddDeploymentHistoryEntry(new ItemDeploymentEntry {
DeploymentCompletedUtc = DateTime.UtcNow,
TargetId = targetConfiguration.Id,
Status = DeploymentStatus.Failed,
Description = T("Single item queueing failed with \"{0}\".", e.Message).Text
});
throw;
}
}
public List<ContentItem> GetContentForExport(RecipeRequest request, int? queuedToTargetId = null) {
var contentItems = new List<ContentItem>();
//ensure date is specified as Utc
var deployChangesAfterUtc = request.DeployChangesAfterUtc.HasValue ? new DateTime(request.DeployChangesAfterUtc.Value.Ticks, DateTimeKind.Utc) : (DateTime?) null;
if (request.ContentIdentities != null && request.ContentIdentities.Any()) {
return request.ContentIdentities
.Select(c => _orchardServices.ContentManager
.ResolveIdentity(new ContentIdentity(c)))
.Where(c => c != null).ToList();
}
if (!string.IsNullOrEmpty(request.QueryIdentity)) {
var queryItem = _orchardServices.ContentManager
.ResolveIdentity(new ContentIdentity(request.QueryIdentity));
return _projectionManager.GetContentItems(queryItem.Id).ToList();
}
if (request.ContentTypes == null || !request.ContentTypes.Any()) {
return contentItems;
}
var version = request.VersionHistoryOption.HasFlag(VersionHistoryOptions.Draft)
? VersionOptions.Draft
: VersionOptions.Published;
var query = _orchardServices.ContentManager.HqlQuery()
.ForType(request.ContentTypes.ToArray())
.ForVersion(version);
if (queuedToTargetId.HasValue) {
var pendingItems = _orchardServices.ContentManager
.Query<DeployableItemTargetPart>()
.Where<DeployableItemTargetPartRecord>(c =>
c.DeploymentStatus == DeploymentStatus.Queued.ToString()
&& c.DeploymentTargetId == queuedToTargetId.Value)
.List()
.Select(c => c.DeployableContent.Id)
.ToList();
if (!pendingItems.Any()) {
//if there are no queued items, no need to run outer query
return contentItems;
}
query = query.Where(a => a.ContentItem(), p => p.In("Id", pendingItems));
}
if (deployChangesAfterUtc.HasValue) {
var changeDateField =
request.VersionHistoryOption == VersionHistoryOptions.Published
? "PublishedUtc"
: "ModifiedUtc";
query = query.Where(
a => a.ContentPartRecord(typeof (CommonPartRecord)),
exp => exp.Gt(changeDateField, deployChangesAfterUtc));
}
//Order by id so that dependencies are usually imported first
//Also needed to fix bug with HQL in orchard 1.7 pre-release where query would fail if order not specified
query = query.OrderBy(a => a.ContentItem(), o => o.Asc("Id"));
contentItems = query.List().ToList();
return contentItems;
}
public void UpdateDeployableContentStatus(string executionId, DeploymentStatus status) {
var deployedItemTargets = _orchardServices.ContentManager
.Query<DeployableItemTargetPart>()
.Where<DeployableItemTargetPartRecord>(d => d.ExecutionId == executionId)
.ForVersion(VersionOptions.Latest)
.List();
foreach (var item in deployedItemTargets) {
item.DeploymentStatus = status;
}
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData.Models;
using Orchard.ImportExport.Models;
namespace Orchard.ImportExport.Services {
public interface IDeploymentService : IDependency {
string DeploymentStoragePath { get; }
IDeploymentSource GetDeploymentSource(IContent sourceConfiguration);
IDeploymentTarget GetDeploymentTarget(IContent targetConfiguration);
List<IContent> GetDeploymentSourceConfigurations();
List<IContent> GetDeploymentTargetConfigurations();
List<ContentTypeDefinition> GetDeploymentConfigurationContentTypes();
List<DeployableItemTargetPart> GetItemsPendingDeployment(IContent targetConfiguration);
DeployableItemTargetPart GetDeploymentItemTarget(IContent deployableContent, IContent targetConfiguration, bool createIfNotFound = true);
void DeployContent(DeployableItemTargetPart deployableContent);
void DeployContentToTarget(IContent content, IContent targetConfiguration, bool deployAsDraft = false);
void QueueContentForDeployment(IContent content, IContent targetConfiguration);
List<ContentItem> GetContentForExport(RecipeRequest request, int? queuedToTargetId = null);
void UpdateDeployableContentStatus(string executionId, DeploymentStatus status);
}
}

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.IO;
using Orchard.ContentManagement;
using Orchard.ImportExport.Models;
namespace Orchard.ImportExport.Services {
public interface IDeploymentSource : IDependency {
string GetDeploymentFile(RecipeRequest request);
IList<DeploymentContentType> GetContentTypes();
IList<DeploymentQuery> GetQueries();
}
public interface IDeploymentSourceProvider : IDependency {
DeploymentSourceMatch Match(IContent sourceConfiguration);
}
public class DeploymentSourceMatch {
public int Priority { get; set; }
public IDeploymentSource DeploymentSource { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
using Orchard.ContentManagement;
namespace Orchard.ImportExport.Services {
public interface IDeploymentTarget : IDependency {
void PushDeploymentFile(string deploymentExecutionId, string deploymentFilePath);
void PushRecipe(string deploymentExecutionId, string recipeText);
bool? GetRecipeDeploymentStatus(string deploymentExecutionId);
void PushContent(IContent content, bool deployAsDraft = false);
}
public interface IDeploymentTargetProvider : IDependency {
DeploymentTargetMatch Match(IContent targetConfiguration);
}
public class DeploymentTargetMatch {
public int Priority { get; set; }
public IDeploymentTarget DeploymentTarget { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show More