From 18258fe4878ed2e828150d92b149aaae367b7311 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 23 Nov 2018 16:16:06 +0100 Subject: [PATCH 01/21] Initial setup of public access dialog --- .../content/content.protect.controller.js | 46 +++++++++++ .../src/views/content/protect.html | 82 +++++++++++++++++++ src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 6 +- 3 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/content/protect.html diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js new file mode 100644 index 0000000000..fc2da0ef75 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js @@ -0,0 +1,46 @@ +(function () { + "use strict"; + + function ContentProtectController($scope, $routeParams, contentResource, memberGroupResource, navigationService) { + + var vm = this; + var id = $scope.currentNode.id; + + vm.loading = false; + vm.saveButtonState = "init"; + + vm.next = next; + vm.save = save; + vm.close = close; + + vm.type = null; + + function onInit() { + vm.loading = true; + // Get all member groups + memberGroupResource.getGroups().then(function (groups) { + vm.groups = groups; + vm.step = "type"; + vm.loading = false; + }); + } + + function next() { + vm.step = vm.type; + } + + function save() { + vm.saveButtonState = "busy"; + console.log("TODO: SAVE!") + } + + function close() { + navigationService.hideDialog(); + } + + onInit(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.Content.ProtectController", ContentProtectController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/protect.html b/src/Umbraco.Web.UI.Client/src/views/content/protect.html new file mode 100644 index 0000000000..3823d2bba1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/content/protect.html @@ -0,0 +1,82 @@ +
+
+
+ +
+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
+
+ + + + + +

+ Choose how to restrict access to the page {{currentNode.name}} +

+ +
+ + + +
+ +
+ + + +
+
+ + + TODO: user config + + + +

You need to create a membergroup before you can use role-based authentication.

+
+ + + TODO: role config + + + + +
+
+ + +
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 7c25782442..15c8c1c7d2 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1126,10 +1126,10 @@ To manage your website, simply open the Umbraco back office and start adding con Role based protection If you wish to control access to the page using role-based authentication, using Umbraco's member groups. - You need to create a membergroup before you can use role-based authentication + You need to create a membergroup before you can use role-based authentication. Error Page Used when people are logged on, but do not have access - Choose how to restrict access to this page + Choose how to restrict access to the page %0% is now protected Protection removed from %0% Login Page @@ -1139,7 +1139,7 @@ To manage your website, simply open the Umbraco back office and start adding con Pick the roles who have access to this page Set the login and password for this page Single user protection - If you just want to setup simple protection using a single login and password + If you just want to setup simple protection using a single login and password. Date: Sat, 24 Nov 2018 19:39:15 +0100 Subject: [PATCH 02/21] A lot more implementation :) --- .../src/common/resources/content.resource.js | 56 +++++ .../content/content.protect.controller.js | 128 ++++++++++- .../src/views/content/protect.html | 209 ++++++++++++------ src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 14 +- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 20 +- src/Umbraco.Web/Actions/ActionProtect.cs | 4 +- src/Umbraco.Web/Editors/ContentController.cs | 121 ++++++++++ .../Models/ContentEditing/PublicAccess.cs | 20 ++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 9 files changed, 479 insertions(+), 94 deletions(-) create mode 100644 src/Umbraco.Web/Models/ContentEditing/PublicAccess.cs diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 06ef8748a0..c056f70a92 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -940,6 +940,62 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { ), "Failed to roll back content item with id " + contentId ); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getPublicAccess + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns the public access protection for a content item + * + * ##usage + *
+          * contentResource.getPublicAccess(contentId)
+          *    .then(function(publicAccess) {
+          *        // do your thing
+          *    });
+          * 
+ * + * @param {Int} contentId The content Id + * @returns {Promise} resourcePromise object containing the public access protection + * + */ + getPublicAccess: function (contentId) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetPublicAccess", { + contentId: contentId + }) + ), + "Failed to get public access for content item with id " + contentId + ); + }, + + // TODO KJAC: ngdoc this + updatePublicAccess: function (contentId, userName, password, roles, loginPageId, errorPageId) { + var publicAccess = { + contentId: contentId, + loginPageId: loginPageId, + errorPageId: errorPageId + }; + if (userName && password) { + publicAccess.userName = userName; + publicAccess.password = password; + } + else if (angular.isArray(roles) && roles.length) { + publicAccess.roles = roles; + } + else { + throw "must supply either userName/password or roles"; + } + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostPublicAccess", publicAccess) + ), + "Failed to update public access for content item with id " + contentId + ); } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js index fc2da0ef75..84d8619303 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function ContentProtectController($scope, $routeParams, contentResource, memberGroupResource, navigationService) { + function ContentProtectController($scope, $routeParams, contentResource, memberGroupResource, navigationService, editorService) { var vm = this; var id = $scope.currentNode.id; @@ -9,35 +9,147 @@ vm.loading = false; vm.saveButtonState = "init"; + vm.isValid = isValid; vm.next = next; vm.save = save; vm.close = close; + vm.toggle = toggle; + vm.pickLoginPage = pickLoginPage; + vm.pickErrorPage = pickErrorPage; + vm.remove = remove; + vm.removeConfirm = removeConfirm; vm.type = null; + vm.step = null; function onInit() { vm.loading = true; - // Get all member groups - memberGroupResource.getGroups().then(function (groups) { - vm.groups = groups; - vm.step = "type"; - vm.loading = false; + + // get the current public access protection + contentResource.getPublicAccess(id).then(function (publicAccess) { + // init the current settings for public access (if any) + vm.loginPage = publicAccess.loginPage; + vm.errorPage = publicAccess.errorPage; + vm.userName = publicAccess.userName; + vm.roles = publicAccess.roles; + vm.canRemove = true; + + if (vm.userName) { + vm.type = "user"; + next(); + } + else if (vm.roles) { + vm.type = "role"; + next(); + } + else { + vm.canRemove = false; + vm.loading = false; + } }); } function next() { - vm.step = vm.type; + if (vm.type === "role") { + vm.loading = true; + // Get all member groups + memberGroupResource.getGroups().then(function (groups) { + vm.step = vm.type; + vm.groups = groups; + // set groups "selected" according to currently selected roles for public access + _.each(groups, function(group) { + group.selected = _.contains(vm.roles, group.name); + }); + vm.loading = false; + }); + } + else { + vm.step = vm.type; + } + } + + function isValid() { + if (!vm.type) { + return false; + } + if (!vm.protectForm.$valid) { + return false; + } + if (!vm.loginPage || !vm.errorPage) { + return false; + } + if (vm.type === "role") { + return !!_.find(vm.groups, function(group) { return group.selected; }); + } + return true; } function save() { vm.saveButtonState = "busy"; - console.log("TODO: SAVE!") + var selectedGroups = _.filter(vm.groups, function(group) { return group.selected; }); + var roles = _.map(selectedGroups, function(group) { return group.name; }); + contentResource.updatePublicAccess(id, vm.userName, vm.password, roles, vm.loginPage.id, vm.errorPage.id).then( + function () { + vm.saveButtonState = "success"; + navigationService.syncTree({ tree: "content", path: $scope.currentNode.path, forceReload: true }); + }, function (error) { + vm.error = error; + vm.saveButtonState = "error"; + } + ); } function close() { navigationService.hideDialog(); } + function toggle(group) { + group.selected = !group.selected; + } + + function pickLoginPage() { + // TODO KJAC: temporary test values until we fix the content picker + if (!vm.loginPage) { + vm.loginPage = { id: 1092 }; + } + + //editorService.contentPicker({ + // submit: function(model) { + // console.log("I picked", model) + // editorService.close(); + // }, + // close: function () { + // editorService.close(); + // } + //}); + } + + function pickErrorPage() { + // TODO KJAC: temporary test values until we fix the content picker + if (!vm.errorPage) { + vm.errorPage = { id: 1093 }; + } + + //editorService.contentPicker({ + // submit: function(model) { + // console.log("I picked", model) + // editorService.close(); + // }, + // close: function () { + // editorService.close(); + // } + //}); + } + + function remove() { + vm.removing = true; + } + + function removeConfirm() { + vm.saveButtonState = "busy"; + // TODO KJAC: remove protection from the page + } + onInit(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/protect.html b/src/Umbraco.Web.UI.Client/src/views/content/protect.html index 3823d2bba1..93381303d0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/protect.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/protect.html @@ -1,82 +1,151 @@
-
-
+
+
+ -
-
-
{{error.errorMsg}}
-
{{error.data.message}}
-
-
- - - - - -

- Choose how to restrict access to the page {{currentNode.name}} -

- -
- - - +
+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
-
- + + - +
+

+ Choose how to restrict access to this page +

+ +
+ + + +
+ +
+ + + +
+ +
+
+

Set the login and password for this page

+ + + + + + + +
+ +
+

You need to create a membergroup before you can use role-based authentication

+
+ +
+

Pick the roles who have access to this page

+ + + + +
+ +
+

Select the pages that contain login form and error messages

+ + + Add + + + + + + + Add + + + + +
+
+ +
+

Are you sure you want to remove the protection from this page?

+
+ + + - - - TODO: user config - - - -

You need to create a membergroup before you can use role-based authentication.

-
- - - TODO: role config - - - -
-
- + + +
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 7864d3baae..9f6ca212d1 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -872,20 +872,22 @@ Mange hilsner fra Umbraco robotten Indsæt, men fjern formattering som ikke bør være på en webside (Anbefales) + Rollebaseret beskyttelse Hvis du ønsker at kontrollere adgang til siden ved hjælp af rollebaseret godkendelse via Umbracos medlemsgrupper. Du skal oprette en medlemsgruppe før du kan bruge rollebaseret godkendelse Fejlside Brugt når folk er logget ind, men ingen adgang - Vælg hvordan siden skal beskyttes - %0% er nu beskyttet - Beskyttelse fjernet fra %0% + %0% skal beskyttes]]> + Log ind-side Vælg siden der indeholder log ind-formularen - Fjern beskyttelse + Fjern beskyttelse... + %0%?]]> Vælg siderne der indeholder log ind-formularer og fejlmeddelelser - Vælg de roller der har adgang til denne side - Indstil login og kodeord for denne side + %0%]]> + %0%]]> Enkel brugerbeskyttelse Hvis du blot ønsker at opsætte simpel beskyttelse ved hjælp af et enkelt login og kodeord diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 15c8c1c7d2..dff3d21ea4 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1124,22 +1124,24 @@ To manage your website, simply open the Umbraco back office and start adding con Paste, but remove formatting (Recommended) + Role based protection - If you wish to control access to the page using role-based authentication, using Umbraco's member groups. - You need to create a membergroup before you can use role-based authentication. + If you wish to control access to the page using role-based authentication, using Umbraco's member groups + You need to create a membergroup before you can use role-based authentication Error Page Used when people are logged on, but do not have access - Choose how to restrict access to the page - %0% is now protected - Protection removed from %0% + %0%]]> + Login Page Choose the page that contains the login form - Remove Protection + Remove protection... + %0%?]]> Select the pages that contain login form and error messages - Pick the roles who have access to this page - Set the login and password for this page + %0%]]> + %0%]]> Single user protection - If you just want to setup simple protection using a single login and password. + If you just want to setup simple protection using a single login and password public class ActionProtect : IAction { - public char Letter => 'P'; + public const char ActionLetter = 'P'; + + public char Letter => ActionLetter; public string Alias => "protect"; public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory; public string Icon => "lock"; diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 53ecddd015..55bbe0be18 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -2153,5 +2153,126 @@ namespace Umbraco.Web.Editors return Request.CreateValidationErrorResponse(notificationModel); } + + [EnsureUserPermissionForContent("contentId", ActionProtect.ActionLetter)] + [HttpGet] + public HttpResponseMessage GetPublicAccess(int contentId) + { + var content = Services.ContentService.GetById(contentId); + if (content == null) + { + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + } + + var entry = Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) + { + return Request.CreateResponse(HttpStatusCode.OK); + } + + var loginPageEntity = Services.EntityService.Get(entry.LoginNodeId, UmbracoObjectTypes.Document); + var errorPageEntity = Services.EntityService.Get(entry.NoAccessNodeId, UmbracoObjectTypes.Document); + + // unwrap the current public access setup for the client + // - this API method is the single point of entry for both "modes" of public access (single user and role based) + var userName = entry.Rules + .FirstOrDefault(rule => rule.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType) + ?.RuleValue; + var roles = entry.Rules + .Where(rule => rule.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType) + .Select(rule => rule.RuleValue) + .ToArray(); + + return Request.CreateResponse(HttpStatusCode.OK, new PublicAccess + { + UserName = userName, + Roles = roles, + LoginPage = loginPageEntity != null ? Mapper.Map(loginPageEntity) : null, + ErrorPage = errorPageEntity != null ? Mapper.Map(errorPageEntity) : null + }); + } + + // set up public access using role based access + [EnsureUserPermissionForContent("contentId", ActionProtect.ActionLetter)] + [HttpPost] + public HttpResponseMessage PostPublicAccess(int contentId, [FromUri]string[] roles, int loginPageId, int errorPageId) + { + if (roles == null || roles.Any() == false) + { + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest)); + } + + var content = Services.ContentService.GetById(contentId); + var loginPage = Services.ContentService.GetById(loginPageId); + var errorPage = Services.ContentService.GetById(errorPageId); + if (content == null || loginPage == null || errorPage == null) + { + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest)); + } + + var entry = Services.PublicAccessService.GetEntryForContent(content); + + if (entry == null) + { + entry = new PublicAccessEntry(content, loginPage, errorPage, new List()); + foreach (var role in roles) + { + entry.AddRule(role, Constants.Conventions.PublicAccess.MemberRoleRuleType); + } + } + else + { + entry.LoginNodeId = loginPage.Id; + entry.NoAccessNodeId = errorPage.Id; + + var currentRules = entry.Rules.ToArray(); + var obsoleteRules = currentRules.Where(rule => + rule.RuleType != Constants.Conventions.PublicAccess.MemberRoleRuleType + || roles.Contains(rule.RuleValue) == false + ); + var newRoles = roles.Where(group => + currentRules.Any(rule => + rule.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType + && rule.RuleValue == group + ) == false + ); + foreach (var rule in obsoleteRules) + { + entry.RemoveRule(rule); + } + foreach (var role in newRoles) + { + entry.AddRule(Constants.Conventions.PublicAccess.MemberRoleRuleType, role); + } + } + + Services.PublicAccessService.Save(entry); + + return Request.CreateResponse(HttpStatusCode.OK); + } + + // set up public access using username and password + [EnsureUserPermissionForContent("contentId", ActionProtect.ActionLetter)] + [HttpPost] + public HttpResponseMessage PostPublicAccess(int contentId, string userName, string password, int loginPageId, int errorPageId) + { + // TODO KJAC: validate password regex + if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password)) + { + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest)); + } + + var content = Services.ContentService.GetById(contentId); + var loginPage = Services.ContentService.GetById(loginPageId); + var errorPage = Services.ContentService.GetById(errorPageId); + if (content == null || loginPage == null || errorPage == null) + { + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest)); + } + + // TODO KJAC: implement + + return Request.CreateResponse(HttpStatusCode.OK); + } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/PublicAccess.cs b/src/Umbraco.Web/Models/ContentEditing/PublicAccess.cs new file mode 100644 index 0000000000..68236d5934 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/PublicAccess.cs @@ -0,0 +1,20 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "publicAccess", Namespace = "")] + public class PublicAccess + { + [DataMember(Name = "userName")] + public string UserName { get; set; } + + [DataMember(Name = "roles")] + public string[] Roles { get; set; } + + [DataMember(Name = "loginPage")] + public EntityBasic LoginPage { get; set; } + + [DataMember(Name = "errorPage")] + public EntityBasic ErrorPage { get; set; } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 32134e7a45..ef70df6c7d 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -150,6 +150,7 @@ + From ed56845bc1d4e6891cb3372eb5a9fc5323c1b30e Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 25 Nov 2018 09:17:04 +0100 Subject: [PATCH 03/21] Implement remove protection + add success messages --- .../src/common/resources/content.resource.js | 32 ++++++++++++++++- .../content/content.protect.controller.js | 22 ++++++++++-- .../src/views/content/protect.html | 36 ++++++++++++------- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 4 +-- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 4 +-- src/Umbraco.Web/Editors/ContentController.cs | 27 ++++++++++++-- 6 files changed, 101 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index c056f70a92..a663d8e52c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -996,8 +996,38 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { ), "Failed to update public access for content item with id " + contentId ); - } + }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#removePublicAccess + * @methodOf umbraco.resources.contentResource + * + * @description + * Removes the public access protection for a content item + * + * ##usage + *
+          * contentResource.removePublicAccess(contentId)
+          *    .then(function() {
+          *        // do your thing
+          *    });
+          * 
+ * + * @param {Int} contentId The content Id + * @returns {Promise} resourcePromise object that's resolved once the public access has been removed + * + */ + removePublicAccess: function (contentId) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "RemovePublicAccess", { + contentId: contentId + }) + ), + "Failed to remove public access for content item with id " + contentId + ); + } }; } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js index 84d8619303..3ef930d7af 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function ContentProtectController($scope, $routeParams, contentResource, memberGroupResource, navigationService, editorService) { + function ContentProtectController($scope, $routeParams, contentResource, memberGroupResource, navigationService, localizationService) { var vm = this; var id = $scope.currentNode.id; @@ -90,7 +90,11 @@ var roles = _.map(selectedGroups, function(group) { return group.name; }); contentResource.updatePublicAccess(id, vm.userName, vm.password, roles, vm.loginPage.id, vm.errorPage.id).then( function () { - vm.saveButtonState = "success"; + localizationService.localize("publicAccess_paIsProtected", [$scope.currentNode.name]).then(function (value) { + vm.success = { + message: value + }; + }); navigationService.syncTree({ tree: "content", path: $scope.currentNode.path, forceReload: true }); }, function (error) { vm.error = error; @@ -147,7 +151,19 @@ function removeConfirm() { vm.saveButtonState = "busy"; - // TODO KJAC: remove protection from the page + contentResource.removePublicAccess(id).then( + function () { + localizationService.localize("publicAccess_paIsRemoved", [$scope.currentNode.name]).then(function(value) { + vm.success = { + message: value + }; + }); + navigationService.syncTree({ tree: "content", path: $scope.currentNode.path, forceReload: true }); + }, function (error) { + vm.error = error; + vm.saveButtonState = "error"; + } + ); } onInit(); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/protect.html b/src/Umbraco.Web.UI.Client/src/views/content/protect.html index 93381303d0..8e1a8259cd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/protect.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/protect.html @@ -1,12 +1,12 @@
- + -
+
-
{{error.errorMsg}}
-
{{error.data.message}}
+
{{vm.error.errorMsg}}
+
{{vm.error.data.message}}
@@ -97,16 +97,26 @@ + + +
+ + +
- -
-

Select the pages that contain login form and error messages

+

Select the pages that contain login form and error messages

Add From 4192a11de092987941aa02aa6e62f321e36cfae6 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 25 Nov 2018 11:02:33 +0100 Subject: [PATCH 07/21] Update ngdoc and en-us translations --- .../src/common/resources/content.resource.js | 26 ++++++++++++++++++- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 1 - src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 1 - .../Umbraco/config/lang/en_us.xml | 15 ++++++----- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index a663d8e52c..b7ea16ea19 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -973,7 +973,31 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { ); }, - // TODO KJAC: ngdoc this + /** + * @ngdoc method + * @name umbraco.resources.contentResource#updatePublicAccess + * @methodOf umbraco.resources.contentResource + * + * @description + * Sets or updates the public access protection for a content item + * + * ##usage + *
+          * contentResource.updatePublicAccess(contentId, userName, password, roles, loginPageId, errorPageId)
+          *    .then(function() {
+          *        // do your thing
+          *    });
+          * 
+ * + * @param {Int} contentId The content Id + * @param {String} userName The name of the user that should have access (if using specific user protection) + * @param {String} password The password for the user that should have access (if using specific user protection) + * @param {Array} roles The roles that should have access (if using role based protection) + * @param {Int} loginPageId The Id of the login page + * @param {Int} errorPageId The Id of the error page + * @returns {Promise} resourcePromise object containing the public access protection + * + */ updatePublicAccess: function (contentId, userName, password, roles, loginPageId, errorPageId) { var publicAccess = { contentId: contentId, diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 40718b508c..f30b4aa7bc 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -872,7 +872,6 @@ Mange hilsner fra Umbraco robotten Indsæt, men fjern formattering som ikke bør være på en webside (Anbefales) - Rollebaseret beskyttelse Hvis du ønsker at kontrollere adgang til siden ved hjælp af rollebaseret godkendelse via Umbracos medlemsgrupper. Du skal oprette en medlemsgruppe før du kan bruge rollebaseret godkendelse diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 662eb705e1..0f3926adf2 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1124,7 +1124,6 @@ To manage your website, simply open the Umbraco back office and start adding con Paste, but remove formatting (Recommended) - Role based protection If you wish to control access to the page using role-based authentication, using Umbraco's member groups You need to create a membergroup before you can use role-based authentication diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 5b39f3d25f..ba4156bf93 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1146,19 +1146,20 @@ To manage your website, simply open the Umbraco back office and start adding con Role based protection - If you wish to control access to the page using role-based authentication, using Umbraco's member groups. + If you wish to control access to the page using role-based authentication, using Umbraco's member groups You need to create a membergroup before you can use role-based authentication Error Page Used when people are logged on, but do not have access - Choose how to restrict access to this page - %0% is now protected - Protection removed from %0% + %0%]]> + %0% is now protected]]> + %0%]]> Login Page Choose the page that contains the login form - Remove Protection + Remove protection... + %0%?]]> Select the pages that contain login form and error messages - Pick the roles who have access to this page - Set the login and password for this page + %0%]]> + %0%]]> Single user protection If you just want to setup simple protection using a single login and password From 924419f403239f33c0f20051524ea92a30ef8f86 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 25 Nov 2018 11:04:28 +0100 Subject: [PATCH 08/21] Ensure that the dialog always closes on close() --- .../src/views/content/content.protect.controller.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js index e8a6a54622..69c73b5ec9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js @@ -104,6 +104,8 @@ } function close() { + // ensure that we haven't set a locked state on the dialog before closing it + navigationService.allowHideDialog(true); navigationService.hideDialog(); } From 34416d9c0a72f759a4f914f0f0e0c9eafeb5be06 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 27 Nov 2018 22:11:18 +0100 Subject: [PATCH 09/21] Change role toggles to checkboxes and restyle the page pickers --- .../src/views/content/protect.html | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/protect.html b/src/Umbraco.Web.UI.Client/src/views/content/protect.html index 4f4c15a2d6..a405a8e569 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/protect.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/protect.html @@ -55,38 +55,49 @@

Pick the roles who have access to this page

- - - - +
+ +

Select the pages that contain login form and error messages

- -
- Add - - - - - - - Add - - - - +
+
+ + + Add + + + +
+
+
+
+ + + Add + + + +
+
From 9ac3b98acf54ba70aee9f4eb69e3936aad597d86 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 28 Nov 2018 07:39:48 +0100 Subject: [PATCH 10/21] Add some indents around the various sections of configuration --- .../src/views/content/protect.html | 124 ++++++++++-------- 1 file changed, 66 insertions(+), 58 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/protect.html b/src/Umbraco.Web.UI.Client/src/views/content/protect.html index a405a8e569..3746e46aa3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/protect.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/protect.html @@ -18,35 +18,39 @@ Choose how to restrict access to this page

-
- + +
+ - -
+ +
-
- +
+ - -
+ +
+

Set the login and password for this page

- - - - - - - + + + + + + + + +
@@ -55,49 +59,53 @@

Pick the roles who have access to this page

-
- -
+ +
+ +
+

Select the pages that contain login form and error messages

-
-
- - - Add - - - + +
+
+ + + Add + + + +
-
-
-
- - - Add - - - +
+
+ + + Add + + + +
-
+
From a795ed66e8e14eb0d6028c35b8698fc50bfa078e Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 8 Dec 2018 14:50:05 +0100 Subject: [PATCH 11/21] Use a member group picker instead of checkboxes --- .../content/content.protect.controller.js | 62 ++++++++++++++----- .../src/views/content/protect.html | 30 +++++---- src/Umbraco.Web/Editors/ContentController.cs | 2 +- 3 files changed, 64 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js index 69c73b5ec9..4504ebd372 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js @@ -7,7 +7,7 @@ var id = $scope.currentNode.id; vm.loading = false; - vm.saveButtonState = "init"; + vm.buttonState = "init"; vm.isValid = isValid; vm.next = next; @@ -16,8 +16,10 @@ vm.toggle = toggle; vm.pickLoginPage = pickLoginPage; vm.pickErrorPage = pickErrorPage; - vm.remove = remove; - vm.removeConfirm = removeConfirm; + vm.pickGroup = pickGroup; + vm.removeGroup = removeGroup; + vm.removeProtection = removeProtection; + vm.removeProtectionConfirm = removeProtectionConfirm; vm.type = null; vm.step = null; @@ -55,10 +57,10 @@ // Get all member groups memberGroupResource.getGroups().then(function (groups) { vm.step = vm.type; - vm.groups = groups; - // set groups "selected" according to currently selected roles for public access - _.each(groups, function(group) { - group.selected = _.contains(vm.roles, group.name); + vm.allGroups = groups; + vm.hasGroups = groups.length > 0; + vm.groups = _.filter(groups, function(group) { + return _.contains(vm.roles, group.name); }); vm.loading = false; }); @@ -79,15 +81,14 @@ return false; } if (vm.type === "role") { - return !!_.find(vm.groups, function(group) { return group.selected; }); + return vm.groups && vm.groups.length > 0; } return true; } function save() { - vm.saveButtonState = "busy"; - var selectedGroups = _.filter(vm.groups, function(group) { return group.selected; }); - var roles = _.map(selectedGroups, function(group) { return group.name; }); + vm.buttonState = "busy"; + var roles = _.map(vm.groups, function (group) { return group.name; }); contentResource.updatePublicAccess(id, vm.userName, vm.password, roles, vm.loginPage.id, vm.errorPage.id).then( function () { localizationService.localize("publicAccess_paIsProtected", [$scope.currentNode.name]).then(function (value) { @@ -98,7 +99,7 @@ navigationService.syncTree({ tree: "content", path: $scope.currentNode.path, forceReload: true }); }, function (error) { vm.error = error; - vm.saveButtonState = "error"; + vm.buttonState = "error"; } ); } @@ -113,6 +114,35 @@ group.selected = !group.selected; } + function pickGroup() { + navigationService.allowHideDialog(false); + editorService.memberGroupPicker({ + multiPicker: true, + submit: function(model) { + var selectedGroupIds = model.selectedMemberGroups + ? model.selectedMemberGroups + : [model.selectedMemberGroup]; + _.each(selectedGroupIds, + function(groupId) { + var group = _.find(vm.allGroups, function(g) { return g.id === parseInt(groupId); }); + if (group && !_.contains(vm.groups, group)) { + vm.groups.push(group); + } + }); + editorService.close(); + navigationService.allowHideDialog(true); + }, + close: function() { + editorService.close(); + navigationService.allowHideDialog(true); + } + }); + } + + function removeGroup(group) { + vm.groups = _.without(vm.groups, group); + } + function pickLoginPage() { pickPage(vm.loginPage); } @@ -141,12 +171,12 @@ }); } - function remove() { + function removeProtection() { vm.removing = true; } - function removeConfirm() { - vm.saveButtonState = "busy"; + function removeProtectionConfirm() { + vm.buttonState = "busy"; contentResource.removePublicAccess(id).then( function () { localizationService.localize("publicAccess_paIsRemoved", [$scope.currentNode.name]).then(function(value) { @@ -157,7 +187,7 @@ navigationService.syncTree({ tree: "content", path: $scope.currentNode.path, forceReload: true }); }, function (error) { vm.error = error; - vm.saveButtonState = "error"; + vm.buttonState = "error"; } ); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/protect.html b/src/Umbraco.Web.UI.Client/src/views/content/protect.html index 3746e46aa3..4b2bf54b5c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/protect.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/protect.html @@ -53,22 +53,26 @@
-
+

You need to create a membergroup before you can use role-based authentication

-
+

Pick the roles who have access to this page

-
- -
+ + + + Add +
-
+

Select the pages that contain login form and error messages

@@ -145,16 +149,16 @@ + disabled="vm.buttonState === 'busy'"> @@ -169,8 +173,8 @@ diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index a026977fc1..b7a33c3ab8 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -2240,7 +2240,7 @@ namespace Umbraco.Web.Editors } foreach (var role in newRoles) { - entry.AddRule(Constants.Conventions.PublicAccess.MemberRoleRuleType, role); + entry.AddRule(role, Constants.Conventions.PublicAccess.MemberRoleRuleType); } } From c860a6b574123beb0a94b6c300c7cd07b2837773 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 9 Dec 2018 14:58:52 +0100 Subject: [PATCH 12/21] Pick members instead of entering username and password --- .../src/common/resources/content.resource.js | 14 ++-- src/Umbraco.Web.UI.Client/src/less/hacks.less | 1 - .../content/content.protect.controller.js | 53 +++++++++++++-- .../src/views/content/protect.html | 30 +++++---- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 6 +- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 8 +-- .../Umbraco/config/lang/en_us.xml | 8 +-- src/Umbraco.Web/Editors/ContentController.cs | 64 ++++++++----------- .../Models/ContentEditing/PublicAccess.cs | 7 +- 9 files changed, 110 insertions(+), 81 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index b7ea16ea19..681a629ed0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -990,27 +990,25 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * * @param {Int} contentId The content Id - * @param {String} userName The name of the user that should have access (if using specific user protection) - * @param {String} password The password for the user that should have access (if using specific user protection) * @param {Array} roles The roles that should have access (if using role based protection) + * @param {Array} memberIds The IDs of the members that should have access (if using member based protection) * @param {Int} loginPageId The Id of the login page * @param {Int} errorPageId The Id of the error page * @returns {Promise} resourcePromise object containing the public access protection * */ - updatePublicAccess: function (contentId, userName, password, roles, loginPageId, errorPageId) { + updatePublicAccess: function (contentId, roles, memberIds, loginPageId, errorPageId) { var publicAccess = { contentId: contentId, loginPageId: loginPageId, errorPageId: errorPageId }; - if (userName && password) { - publicAccess.userName = userName; - publicAccess.password = password; - } - else if (angular.isArray(roles) && roles.length) { + if (angular.isArray(roles) && roles.length) { publicAccess.roles = roles; } + else if (angular.isArray(memberIds) && memberIds.length) { + publicAccess.memberIds = memberIds; + } else { throw "must supply either userName/password or roles"; } diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less index cd32c64782..0bbb89a250 100644 --- a/src/Umbraco.Web.UI.Client/src/less/hacks.less +++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less @@ -106,7 +106,6 @@ iframe, .content-column-body { display: flex; flex-wrap: nowrap; flex-direction: row; - justify-content: center; align-items: flex-start; margin-top: 15px; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js index 4504ebd372..ace37d4ac5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js @@ -18,6 +18,8 @@ vm.pickErrorPage = pickErrorPage; vm.pickGroup = pickGroup; vm.removeGroup = removeGroup; + vm.pickMember = pickMember; + vm.removeMember = removeMember; vm.removeProtection = removeProtection; vm.removeProtectionConfirm = removeProtectionConfirm; @@ -29,24 +31,25 @@ // get the current public access protection contentResource.getPublicAccess(id).then(function (publicAccess) { + vm.loading = false; + // init the current settings for public access (if any) vm.loginPage = publicAccess.loginPage; vm.errorPage = publicAccess.errorPage; - vm.userName = publicAccess.userName; - vm.roles = publicAccess.roles; + vm.roles = publicAccess.roles || []; + vm.members = publicAccess.members || []; vm.canRemove = true; - if (vm.userName) { - vm.type = "user"; + if (vm.members.length) { + vm.type = "member"; next(); } - else if (vm.roles) { + else if (vm.roles.length) { vm.type = "role"; next(); } else { vm.canRemove = false; - vm.loading = false; } }); } @@ -89,7 +92,8 @@ function save() { vm.buttonState = "busy"; var roles = _.map(vm.groups, function (group) { return group.name; }); - contentResource.updatePublicAccess(id, vm.userName, vm.password, roles, vm.loginPage.id, vm.errorPage.id).then( + var memberIds = _.map(vm.members, function (member) { return member.id; }); + contentResource.updatePublicAccess(id, roles, memberIds, vm.loginPage.id, vm.errorPage.id).then( function () { localizationService.localize("publicAccess_paIsProtected", [$scope.currentNode.name]).then(function (value) { vm.success = { @@ -143,6 +147,41 @@ vm.groups = _.without(vm.groups, group); } + function pickMember() { + navigationService.allowHideDialog(false); + // TODO: once editorService has a memberPicker method, use that instead + editorService.treePicker({ + multiPicker: true, + entityType: "Member", + section: "member", + treeAlias: "member", + filter: function (i) { + return i.metaData.isContainer; + }, + filterCssClass: "not-allowed", + submit: function (model) { + if (model.selection && model.selection.length) { + _.each(model.selection, + function (member) { + if (!_.find(vm.members, function (m) { return m.id === member.id })) { + vm.members.push(member); + } + }); + } + editorService.close(); + navigationService.allowHideDialog(true); + }, + close: function () { + editorService.close(); + navigationService.allowHideDialog(true); + } + }); + } + + function removeMember(member) { + vm.members = _.without(vm.members, member); + } + function pickLoginPage() { pickPage(vm.loginPage); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/protect.html b/src/Umbraco.Web.UI.Client/src/views/content/protect.html index 4b2bf54b5c..f179f1c63c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/protect.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/protect.html @@ -20,11 +20,11 @@
- + -
@@ -40,16 +40,18 @@
-
-

Set the login and password for this page

+
+

Select the members that should have access to this page

- - - - - - - + + + + Add +
@@ -72,7 +74,7 @@
-
+

Select the pages that contain login form and error messages

diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index f30b4aa7bc..c0377e16ee 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -886,9 +886,9 @@ Mange hilsner fra Umbraco robotten %0%?]]> Vælg siderne der indeholder log ind-formularer og fejlmeddelelser %0%]]> - %0%]]> - Enkel brugerbeskyttelse - Hvis du blot ønsker at opsætte simpel beskyttelse ved hjælp af et enkelt login og kodeord + %0%]]> + Adgang til enkelte medlemmer + Hvis du ønsker at give adgang til enkelte medlemmer Udgivelsen kunne ikke udgives da publiceringsdato er sat diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 49111e9eab..f61186a98f 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1137,10 +1137,10 @@ To manage your website, simply open the Umbraco back office and start adding con Remove protection... %0%?]]> Select the pages that contain login form and error messages - %0%]]> - %0%]]> - Single user protection - If you just want to setup simple protection using a single login and password + %0%]]> + %0%]]> + Specific members protection + If you wish to grant access to specific members Remove protection... %0%?]]> Select the pages that contain login form and error messages - %0%]]> - %0%]]> - Single user protection - If you just want to setup simple protection using a single login and password + %0%]]> + %0%]]> + Specific members protection + If you wish to grant access to specific members Insufficient user permissions to publish all descendant documents diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index b7a33c3ab8..015354f0c6 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -2173,9 +2173,12 @@ namespace Umbraco.Web.Editors // unwrap the current public access setup for the client // - this API method is the single point of entry for both "modes" of public access (single user and role based) - var userName = entry.Rules - .FirstOrDefault(rule => rule.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType) - ?.RuleValue; + var members = entry.Rules + .Where(rule => rule.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType) + .Select(rule => Services.MemberService.GetByUsername(rule.RuleValue)) + .Where(member => member != null) + .Select(Mapper.Map) + .ToArray(); var roles = entry.Rules .Where(rule => rule.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType) .Select(rule => rule.RuleValue) @@ -2183,7 +2186,7 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.OK, new PublicAccess { - UserName = userName, + Members = members, Roles = roles, LoginPage = loginPageEntity != null ? Mapper.Map(loginPageEntity) : null, ErrorPage = errorPageEntity != null ? Mapper.Map(errorPageEntity) : null @@ -2193,9 +2196,9 @@ namespace Umbraco.Web.Editors // set up public access using role based access [EnsureUserPermissionForContent("contentId", ActionProtect.ActionLetter)] [HttpPost] - public HttpResponseMessage PostPublicAccess(int contentId, [FromUri]string[] roles, int loginPageId, int errorPageId) + public HttpResponseMessage PostPublicAccess(int contentId, [FromUri]string[] roles, [FromUri]int[] memberIds, int loginPageId, int errorPageId) { - if (roles == null || roles.Any() == false) + if ((roles == null || roles.Any() == false) && (userIds == null || userIds.Any() == false)) { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest)); } @@ -2208,14 +2211,23 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest)); } + var isRoleBased = roles != null && roles.Any(); + var candidateRuleValues = isRoleBased + ? roles + : memberIds.Select(id => Services.MemberService.GetById(id)?.Username).Where(s => s != null).ToArray(); + var newRuleType = isRoleBased + ? Constants.Conventions.PublicAccess.MemberRoleRuleType + : Constants.Conventions.PublicAccess.MemberUsernameRuleType; + var entry = Services.PublicAccessService.GetEntryForContent(content); if (entry == null) { entry = new PublicAccessEntry(content, loginPage, errorPage, new List()); - foreach (var role in roles) + + foreach (var role in candidateRuleValues) { - entry.AddRule(role, Constants.Conventions.PublicAccess.MemberRoleRuleType); + entry.AddRule(role, newRuleType); } } else @@ -2225,12 +2237,12 @@ namespace Umbraco.Web.Editors var currentRules = entry.Rules.ToArray(); var obsoleteRules = currentRules.Where(rule => - rule.RuleType != Constants.Conventions.PublicAccess.MemberRoleRuleType - || roles.Contains(rule.RuleValue) == false + rule.RuleType != newRuleType + || candidateRuleValues.Contains(rule.RuleValue) == false ); - var newRoles = roles.Where(group => + var newRuleValues = candidateRuleValues.Where(group => currentRules.Any(rule => - rule.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType + rule.RuleType == newRuleType && rule.RuleValue == group ) == false ); @@ -2238,9 +2250,9 @@ namespace Umbraco.Web.Editors { entry.RemoveRule(rule); } - foreach (var role in newRoles) + foreach (var role in newRuleValues) { - entry.AddRule(role, Constants.Conventions.PublicAccess.MemberRoleRuleType); + entry.AddRule(role, newRuleType); } } @@ -2249,30 +2261,6 @@ namespace Umbraco.Web.Editors : Request.CreateResponse(HttpStatusCode.InternalServerError); } - // set up public access using username and password - [EnsureUserPermissionForContent("contentId", ActionProtect.ActionLetter)] - [HttpPost] - public HttpResponseMessage PostPublicAccess(int contentId, string userName, string password, int loginPageId, int errorPageId) - { - // TODO KJAC: validate password regex - if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password)) - { - throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest)); - } - - var content = Services.ContentService.GetById(contentId); - var loginPage = Services.ContentService.GetById(loginPageId); - var errorPage = Services.ContentService.GetById(errorPageId); - if (content == null || loginPage == null || errorPage == null) - { - throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest)); - } - - // TODO KJAC: implement - - return Request.CreateResponse(HttpStatusCode.OK); - } - [EnsureUserPermissionForContent("contentId", ActionProtect.ActionLetter)] [HttpPost] public HttpResponseMessage RemovePublicAccess(int contentId) diff --git a/src/Umbraco.Web/Models/ContentEditing/PublicAccess.cs b/src/Umbraco.Web/Models/ContentEditing/PublicAccess.cs index 68236d5934..b8035c9f25 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PublicAccess.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PublicAccess.cs @@ -5,8 +5,8 @@ namespace Umbraco.Web.Models.ContentEditing [DataContract(Name = "publicAccess", Namespace = "")] public class PublicAccess { - [DataMember(Name = "userName")] - public string UserName { get; set; } + //[DataMember(Name = "userName")] + //public string UserName { get; set; } [DataMember(Name = "roles")] public string[] Roles { get; set; } @@ -16,5 +16,8 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "errorPage")] public EntityBasic ErrorPage { get; set; } + + [DataMember(Name = "members")] + public MemberDisplay[] Members { get; set; } } } From a8b0ede770a5d68fc44c7f593c86a94c2a2d41c7 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 9 Dec 2018 15:03:35 +0100 Subject: [PATCH 13/21] Whoops forgot a file --- src/Umbraco.Web/Editors/ContentController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 015354f0c6..e245883ed7 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -2198,7 +2198,7 @@ namespace Umbraco.Web.Editors [HttpPost] public HttpResponseMessage PostPublicAccess(int contentId, [FromUri]string[] roles, [FromUri]int[] memberIds, int loginPageId, int errorPageId) { - if ((roles == null || roles.Any() == false) && (userIds == null || userIds.Any() == false)) + if ((roles == null || roles.Any() == false) && (memberIds == null || memberIds.Any() == false)) { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest)); } From efb9b37f5daac33ef753c46f0a708d0f0a930c53 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 12 Dec 2018 09:27:03 +0100 Subject: [PATCH 14/21] Change from member IDs to usernames in member selections --- .../src/common/resources/content.resource.js | 8 ++--- .../content/content.protect.controller.js | 31 ++++++++++++++----- src/Umbraco.Web/Editors/ContentController.cs | 7 +++-- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 681a629ed0..75e1877be8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -991,13 +991,13 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @param {Int} contentId The content Id * @param {Array} roles The roles that should have access (if using role based protection) - * @param {Array} memberIds The IDs of the members that should have access (if using member based protection) + * @param {Array} usernames The usernames of the members that should have access (if using member based protection) * @param {Int} loginPageId The Id of the login page * @param {Int} errorPageId The Id of the error page * @returns {Promise} resourcePromise object containing the public access protection * */ - updatePublicAccess: function (contentId, roles, memberIds, loginPageId, errorPageId) { + updatePublicAccess: function (contentId, roles, usernames, loginPageId, errorPageId) { var publicAccess = { contentId: contentId, loginPageId: loginPageId, @@ -1006,8 +1006,8 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { if (angular.isArray(roles) && roles.length) { publicAccess.roles = roles; } - else if (angular.isArray(memberIds) && memberIds.length) { - publicAccess.memberIds = memberIds; + else if (angular.isArray(usernames) && usernames.length) { + publicAccess.usernames = usernames; } else { throw "must supply either userName/password or roles"; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js index ace37d4ac5..c6b6cecd5d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function ContentProtectController($scope, $routeParams, contentResource, memberGroupResource, navigationService, localizationService, editorService) { + function ContentProtectController($scope, $q, contentResource, memberResource, memberGroupResource, navigationService, localizationService, editorService) { var vm = this; var id = $scope.currentNode.id; @@ -92,8 +92,8 @@ function save() { vm.buttonState = "busy"; var roles = _.map(vm.groups, function (group) { return group.name; }); - var memberIds = _.map(vm.members, function (member) { return member.id; }); - contentResource.updatePublicAccess(id, roles, memberIds, vm.loginPage.id, vm.errorPage.id).then( + var usernames = _.map(vm.members, function (member) { return member.username; }); + contentResource.updatePublicAccess(id, roles, usernames, vm.loginPage.id, vm.errorPage.id).then( function () { localizationService.localize("publicAccess_paIsProtected", [$scope.currentNode.name]).then(function (value) { vm.success = { @@ -161,15 +161,30 @@ filterCssClass: "not-allowed", submit: function (model) { if (model.selection && model.selection.length) { + var promises = []; + // get the selected member usernames _.each(model.selection, function (member) { - if (!_.find(vm.members, function (m) { return m.id === member.id })) { - vm.members.push(member); - } + // TODO: + // as-is we need to fetch all the picked members one at a time to get their usernames. + // when editorService has a memberPicker method, see if this can't be avoided - otherwise + // add a memberResource.getByKeys() method to do all this in one request + promises.push( + memberResource.getByKey(member.key).then(function(newMember) { + if (!_.find(vm.members, function (currentMember) { return currentMember.username === newMember.username })) { + vm.members.push(newMember); + } + }) + ); }); + editorService.close(); + navigationService.allowHideDialog(true); + // wait for all the member lookups to complete + vm.loading = true; + $q.all(promises).then(function() { + vm.loading = false; + }); } - editorService.close(); - navigationService.allowHideDialog(true); }, close: function () { editorService.close(); diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index e245883ed7..423680fac5 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -2173,6 +2173,7 @@ namespace Umbraco.Web.Editors // unwrap the current public access setup for the client // - this API method is the single point of entry for both "modes" of public access (single user and role based) + // TODO: support custom membership providers here var members = entry.Rules .Where(rule => rule.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType) .Select(rule => Services.MemberService.GetByUsername(rule.RuleValue)) @@ -2196,9 +2197,9 @@ namespace Umbraco.Web.Editors // set up public access using role based access [EnsureUserPermissionForContent("contentId", ActionProtect.ActionLetter)] [HttpPost] - public HttpResponseMessage PostPublicAccess(int contentId, [FromUri]string[] roles, [FromUri]int[] memberIds, int loginPageId, int errorPageId) + public HttpResponseMessage PostPublicAccess(int contentId, [FromUri]string[] roles, [FromUri]string[] usernames, int loginPageId, int errorPageId) { - if ((roles == null || roles.Any() == false) && (memberIds == null || memberIds.Any() == false)) + if ((roles == null || roles.Any() == false) && (usernames == null || usernames.Any() == false)) { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest)); } @@ -2214,7 +2215,7 @@ namespace Umbraco.Web.Editors var isRoleBased = roles != null && roles.Any(); var candidateRuleValues = isRoleBased ? roles - : memberIds.Select(id => Services.MemberService.GetById(id)?.Username).Where(s => s != null).ToArray(); + : usernames; var newRuleType = isRoleBased ? Constants.Conventions.PublicAccess.MemberRoleRuleType : Constants.Conventions.PublicAccess.MemberUsernameRuleType; From 35f0bf92f2efde5d3cf436c07e3ae08c034b117a Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 12 Dec 2018 09:55:35 +0100 Subject: [PATCH 15/21] Support custom membership providers (untested) --- src/Umbraco.Web/Editors/ContentController.cs | 33 ++++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 423680fac5..3f595f9cbc 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -8,6 +8,7 @@ using System.Text; using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.ModelBinding; +using System.Web.Security; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Logging; @@ -2173,13 +2174,33 @@ namespace Umbraco.Web.Editors // unwrap the current public access setup for the client // - this API method is the single point of entry for both "modes" of public access (single user and role based) - // TODO: support custom membership providers here - var members = entry.Rules + var usernames = entry.Rules .Where(rule => rule.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType) - .Select(rule => Services.MemberService.GetByUsername(rule.RuleValue)) - .Where(member => member != null) - .Select(Mapper.Map) - .ToArray(); + .Select(rule => rule.RuleValue).ToArray(); + + MemberDisplay[] members; + switch (Services.MemberService.GetMembershipScenario()) + { + case MembershipScenario.NativeUmbraco: + members = usernames + .Select(username => Services.MemberService.GetByUsername(username)) + .Where(member => member != null) + .Select(Mapper.Map) + .ToArray(); + break; + // TODO: test support custom membership providers + case MembershipScenario.CustomProviderWithUmbracoLink: + case MembershipScenario.StandaloneCustomProvider: + default: + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + members = usernames + .Select(username => provider.GetUser(username, false)) + .Where(membershipUser => membershipUser != null) + .Select(Mapper.Map) + .ToArray(); + break; + } + var roles = entry.Rules .Where(rule => rule.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType) .Select(rule => rule.RuleValue) From 22b78ec617aaa404c385a70220ff0b0cd793bd52 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 12 Dec 2018 11:18:16 +0100 Subject: [PATCH 16/21] Make sure at least one member is selected --- .../src/views/content/content.protect.controller.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js index c6b6cecd5d..988cd10739 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js @@ -86,6 +86,9 @@ if (vm.type === "role") { return vm.groups && vm.groups.length > 0; } + if (vm.type === "member") { + return vm.members && vm.members.length > 0; + } return true; } From e4f9c2916d52028bd15061a0f5ba42a3bf6c58cd Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 12 Dec 2018 14:27:20 +0100 Subject: [PATCH 17/21] Use "groups", not "roles" + return richer group models from API --- .../src/common/resources/content.resource.js | 8 ++--- .../content/content.protect.controller.js | 27 +++++++++-------- .../src/views/content/protect.html | 16 +++++----- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 8 ++--- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 8 ++--- .../Umbraco/config/lang/en_us.xml | 8 ++--- src/Umbraco.Web/Editors/ContentController.cs | 29 ++++++++++--------- .../Models/ContentEditing/PublicAccess.cs | 7 ++--- 8 files changed, 55 insertions(+), 56 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 75e1877be8..b807a4dc31 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -990,21 +990,21 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * * @param {Int} contentId The content Id - * @param {Array} roles The roles that should have access (if using role based protection) + * @param {Array} groups The names of the groups that should have access (if using group based protection) * @param {Array} usernames The usernames of the members that should have access (if using member based protection) * @param {Int} loginPageId The Id of the login page * @param {Int} errorPageId The Id of the error page * @returns {Promise} resourcePromise object containing the public access protection * */ - updatePublicAccess: function (contentId, roles, usernames, loginPageId, errorPageId) { + updatePublicAccess: function (contentId, groups, usernames, loginPageId, errorPageId) { var publicAccess = { contentId: contentId, loginPageId: loginPageId, errorPageId: errorPageId }; - if (angular.isArray(roles) && roles.length) { - publicAccess.roles = roles; + if (angular.isArray(groups) && groups.length) { + publicAccess.groups = groups; } else if (angular.isArray(usernames) && usernames.length) { publicAccess.usernames = usernames; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js index 988cd10739..8d80f308ab 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js @@ -36,7 +36,7 @@ // init the current settings for public access (if any) vm.loginPage = publicAccess.loginPage; vm.errorPage = publicAccess.errorPage; - vm.roles = publicAccess.roles || []; + vm.groups = publicAccess.groups || []; vm.members = publicAccess.members || []; vm.canRemove = true; @@ -44,8 +44,8 @@ vm.type = "member"; next(); } - else if (vm.roles.length) { - vm.type = "role"; + else if (vm.groups.length) { + vm.type = "group"; next(); } else { @@ -55,16 +55,14 @@ } function next() { - if (vm.type === "role") { + if (vm.type === "group") { vm.loading = true; - // Get all member groups + // get all existing member groups for lookup upon selection + // NOTE: if/when member groups support infinite editing, we can't rely on using a cached lookup list of valid groups anymore memberGroupResource.getGroups().then(function (groups) { vm.step = vm.type; vm.allGroups = groups; vm.hasGroups = groups.length > 0; - vm.groups = _.filter(groups, function(group) { - return _.contains(vm.roles, group.name); - }); vm.loading = false; }); } @@ -83,7 +81,7 @@ if (!vm.loginPage || !vm.errorPage) { return false; } - if (vm.type === "role") { + if (vm.type === "group") { return vm.groups && vm.groups.length > 0; } if (vm.type === "member") { @@ -94,9 +92,9 @@ function save() { vm.buttonState = "busy"; - var roles = _.map(vm.groups, function (group) { return group.name; }); + var groups = _.map(vm.groups, function (group) { return group.name; }); var usernames = _.map(vm.members, function (member) { return member.username; }); - contentResource.updatePublicAccess(id, roles, usernames, vm.loginPage.id, vm.errorPage.id).then( + contentResource.updatePublicAccess(id, groups, usernames, vm.loginPage.id, vm.errorPage.id).then( function () { localizationService.localize("publicAccess_paIsProtected", [$scope.currentNode.name]).then(function (value) { vm.success = { @@ -130,9 +128,10 @@ ? model.selectedMemberGroups : [model.selectedMemberGroup]; _.each(selectedGroupIds, - function(groupId) { + function (groupId) { + // find the group in the lookup list and add it if it isn't already var group = _.find(vm.allGroups, function(g) { return g.id === parseInt(groupId); }); - if (group && !_.contains(vm.groups, group)) { + if (group && !_.find(vm.groups, function (g) { return g.id === group.id })) { vm.groups.push(group); } }); @@ -147,7 +146,7 @@ } function removeGroup(group) { - vm.groups = _.without(vm.groups, group); + vm.groups = _.reject(vm.groups, function(g) { return g.id === group.id }); } function pickMember() { diff --git a/src/Umbraco.Web.UI.Client/src/views/content/protect.html b/src/Umbraco.Web.UI.Client/src/views/content/protect.html index f179f1c63c..ae4a15e8c1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/protect.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/protect.html @@ -29,11 +29,11 @@
- + -
@@ -55,12 +55,12 @@
-
-

You need to create a membergroup before you can use role-based authentication

+
+

You need to create a member group before you can use group based authentication

-
-

Pick the roles who have access to this page

+
+

Select the groups that should have access to this page

Indsæt, men fjern formattering som ikke bør være på en webside (Anbefales) - Rollebaseret beskyttelse - Hvis du ønsker at kontrollere adgang til siden ved hjælp af rollebaseret godkendelse via Umbracos medlemsgrupper. - Du skal oprette en medlemsgruppe før du kan bruge rollebaseret godkendelse + Gruppebaseret beskyttelse + Hvis du ønsker at give adgang til alle medlemmer af specifikke medlemsgrupper + Du skal oprette en medlemsgruppe før du kan bruge gruppebaseret beskyttelse Fejlside Brugt når folk er logget ind, men ingen adgang %0% skal beskyttes]]> @@ -885,7 +885,7 @@ Mange hilsner fra Umbraco robotten Fjern beskyttelse... %0%?]]> Vælg siderne der indeholder log ind-formularer og fejlmeddelelser - %0%]]> + %0%]]> %0%]]> Adgang til enkelte medlemmer Hvis du ønsker at give adgang til enkelte medlemmer diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index f61186a98f..51ea51685f 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1124,9 +1124,9 @@ To manage your website, simply open the Umbraco back office and start adding con Paste, but remove formatting (Recommended) - Role based protection - If you wish to control access to the page using role-based authentication, using Umbraco's member groups - You need to create a membergroup before you can use role-based authentication + Group based protection + If you want to grant access to all members of specific member groups + You need to create a member group before you can use group based authentication Error Page Used when people are logged on, but do not have access %0%]]> @@ -1137,7 +1137,7 @@ To manage your website, simply open the Umbraco back office and start adding con Remove protection... %0%?]]> Select the pages that contain login form and error messages - %0%]]> + %0%]]> %0%]]> Specific members protection If you wish to grant access to specific members diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 1953a351be..b5efba5a32 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1146,9 +1146,9 @@ To manage your website, simply open the Umbraco back office and start adding con Paste, but remove formatting (Recommended) - Role based protection - If you wish to control access to the page using role-based authentication, using Umbraco's member groups - You need to create a membergroup before you can use role-based authentication + Group based protection + If you want to grant access to all members of specific member groups + You need to create a member group before you can use group based authentication Error Page Used when people are logged on, but do not have access %0%]]> @@ -1159,7 +1159,7 @@ To manage your website, simply open the Umbraco back office and start adding con Remove protection... %0%?]]> Select the pages that contain login form and error messages - %0%]]> + %0%]]> %0%]]> Specific members protection If you wish to grant access to specific members diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 3f595f9cbc..d1cb15e76d 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -2201,15 +2201,18 @@ namespace Umbraco.Web.Editors break; } - var roles = entry.Rules + var allGroups = Services.MemberGroupService.GetAll().ToArray(); + var groups = entry.Rules .Where(rule => rule.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType) - .Select(rule => rule.RuleValue) + .Select(rule => allGroups.FirstOrDefault(g => g.Name == rule.RuleValue)) + .Where(memberGroup => memberGroup != null) + .Select(Mapper.Map) .ToArray(); return Request.CreateResponse(HttpStatusCode.OK, new PublicAccess { Members = members, - Roles = roles, + Groups = groups, LoginPage = loginPageEntity != null ? Mapper.Map(loginPageEntity) : null, ErrorPage = errorPageEntity != null ? Mapper.Map(errorPageEntity) : null }); @@ -2218,9 +2221,9 @@ namespace Umbraco.Web.Editors // set up public access using role based access [EnsureUserPermissionForContent("contentId", ActionProtect.ActionLetter)] [HttpPost] - public HttpResponseMessage PostPublicAccess(int contentId, [FromUri]string[] roles, [FromUri]string[] usernames, int loginPageId, int errorPageId) + public HttpResponseMessage PostPublicAccess(int contentId, [FromUri]string[] groups, [FromUri]string[] usernames, int loginPageId, int errorPageId) { - if ((roles == null || roles.Any() == false) && (usernames == null || usernames.Any() == false)) + if ((groups == null || groups.Any() == false) && (usernames == null || usernames.Any() == false)) { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest)); } @@ -2233,11 +2236,11 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest)); } - var isRoleBased = roles != null && roles.Any(); - var candidateRuleValues = isRoleBased - ? roles + var isGroupBased = groups != null && groups.Any(); + var candidateRuleValues = isGroupBased + ? groups : usernames; - var newRuleType = isRoleBased + var newRuleType = isGroupBased ? Constants.Conventions.PublicAccess.MemberRoleRuleType : Constants.Conventions.PublicAccess.MemberUsernameRuleType; @@ -2247,9 +2250,9 @@ namespace Umbraco.Web.Editors { entry = new PublicAccessEntry(content, loginPage, errorPage, new List()); - foreach (var role in candidateRuleValues) + foreach (var ruleValue in candidateRuleValues) { - entry.AddRule(role, newRuleType); + entry.AddRule(ruleValue, newRuleType); } } else @@ -2272,9 +2275,9 @@ namespace Umbraco.Web.Editors { entry.RemoveRule(rule); } - foreach (var role in newRuleValues) + foreach (var ruleValue in newRuleValues) { - entry.AddRule(role, newRuleType); + entry.AddRule(ruleValue, newRuleType); } } diff --git a/src/Umbraco.Web/Models/ContentEditing/PublicAccess.cs b/src/Umbraco.Web/Models/ContentEditing/PublicAccess.cs index b8035c9f25..dcf2dcae92 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PublicAccess.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PublicAccess.cs @@ -5,11 +5,8 @@ namespace Umbraco.Web.Models.ContentEditing [DataContract(Name = "publicAccess", Namespace = "")] public class PublicAccess { - //[DataMember(Name = "userName")] - //public string UserName { get; set; } - - [DataMember(Name = "roles")] - public string[] Roles { get; set; } + [DataMember(Name = "groups")] + public MemberGroupDisplay[] Groups { get; set; } [DataMember(Name = "loginPage")] public EntityBasic LoginPage { get; set; } From 1ba41986d74ce3c4b7c9b2ba9a66e67bd0eec110 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 12 Dec 2018 15:35:34 +0100 Subject: [PATCH 18/21] Make sure the public access supports member based access --- .../Services/PublicAccessServiceExtensions.cs | 17 ++++++++++++----- src/Umbraco.Web/Routing/PublishedRouter.cs | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs b/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs index 12db4daf40..b0dc979ebf 100644 --- a/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs +++ b/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Web.Security; +using Umbraco.Core.Models; namespace Umbraco.Core.Services { @@ -41,7 +42,7 @@ namespace Umbraco.Core.Services return hasChange; } - public static bool HasAccess(this IPublicAccessService publicAccessService, int documentId, IContentService contentService, IEnumerable currentMemberRoles) + public static bool HasAccess(this IPublicAccessService publicAccessService, int documentId, IContentService contentService, string username, IEnumerable currentMemberRoles) { var content = contentService.GetById(documentId); if (content == null) return true; @@ -49,8 +50,7 @@ namespace Umbraco.Core.Services var entry = publicAccessService.GetEntryForContent(content); if (entry == null) return true; - return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType - && currentMemberRoles.Contains(x.RuleValue)); + return HasAccess(entry, username, currentMemberRoles); } public static bool HasAccess(this IPublicAccessService publicAccessService, string path, MembershipUser member, RoleProvider roleProvider) @@ -77,8 +77,15 @@ namespace Umbraco.Core.Services var roles = rolesCallback(username); - return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType - && roles.Contains(x.RuleValue)); + return HasAccess(entry, username, roles); + } + + private static bool HasAccess(PublicAccessEntry entry, string username, IEnumerable roles) + { + return entry.Rules.Any(x => + (x.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType && username.Equals(x.RuleValue, StringComparison.OrdinalIgnoreCase)) + || (x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType && roles.Contains(x.RuleValue)) + ); } } } diff --git a/src/Umbraco.Web/Routing/PublishedRouter.cs b/src/Umbraco.Web/Routing/PublishedRouter.cs index 06c23406ab..1122aaa11a 100644 --- a/src/Umbraco.Web/Routing/PublishedRouter.cs +++ b/src/Umbraco.Web/Routing/PublishedRouter.cs @@ -637,7 +637,7 @@ namespace Umbraco.Web.Routing if (loginPageId != request.PublishedContent.Id) request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(loginPageId); } - else if (_services.PublicAccessService.HasAccess(request.PublishedContent.Id, _services.ContentService, GetRolesForLogin(membershipHelper.CurrentUserName)) == false) + else if (_services.PublicAccessService.HasAccess(request.PublishedContent.Id, _services.ContentService, membershipHelper.CurrentUserName, GetRolesForLogin(membershipHelper.CurrentUserName)) == false) { _logger.Debug("EnsurePublishedContentAccess: Current member has not access, redirect to error page"); var errorPageId = publicAccessAttempt.Result.NoAccessNodeId; From 41624c8c16a6cdbfc6be9e038721316d7ed52c66 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 12 Dec 2018 23:07:41 +0100 Subject: [PATCH 19/21] Remove the old aspx page --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 9 +- .../Umbraco/dialogs/protectPage.aspx | 201 ----- .../Trees/LegacyTreeDataConverter.cs | 5 - src/Umbraco.Web/Umbraco.Web.csproj | 3 - .../umbraco/dialogs/protectPage.aspx.cs | 745 ------------------ 5 files changed, 4 insertions(+), 959 deletions(-) delete mode 100644 src/Umbraco.Web.UI/Umbraco/dialogs/protectPage.aspx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 2823908a92..963be029e5 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -336,7 +336,6 @@ - Designer @@ -452,10 +451,10 @@ $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v11.0\Web\Microsoft.Web.Publishing.Tasks.dll $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v12.0\Web\Microsoft.Web.Publishing.Tasks.dll $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v14.0\Web\Microsoft.Web.Publishing.Tasks.dll - $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v15.0\Web\Microsoft.Web.Publishing.Tasks.dll - $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v16.0\Web\Microsoft.Web.Publishing.Tasks.dll - - $(ProgramFiles32)\Microsoft Visual Studio\2019\Preview\MSBuild\Microsoft\VisualStudio\v16.0\Web\Microsoft.Web.Publishing.Tasks.dll + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v15.0\Web\Microsoft.Web.Publishing.Tasks.dll + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v16.0\Web\Microsoft.Web.Publishing.Tasks.dll + + $(ProgramFiles32)\Microsoft Visual Studio\2019\Preview\MSBuild\Microsoft\VisualStudio\v16.0\Web\Microsoft.Web.Publishing.Tasks.dll diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/protectPage.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/protectPage.aspx deleted file mode 100644 index 5bcf97310f..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/protectPage.aspx +++ /dev/null @@ -1,201 +0,0 @@ -<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master" AutoEventWireup="True" Inherits="umbraco.presentation.umbraco.dialogs.protectPage" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web._Legacy.Controls" Assembly="Umbraco.Web" %> - - - - - - - - - - - - -
- -
- -
- - - -
- - - -
- - - -
- - -
- - - -
- -
-
- - -
- - -
- - -
- - - - -
- -
- - - -
- - -

Member name already exists, click Change to use a different name or Update to continue

-
-
- - - -

<%= Services.TextService.Localize("publicAccess/paSelectRoles")%>

-
- - - -
- - - - - - <%=Services.TextService.Localize("paLoginPageHelp")%> - -
- -
- - -
- - - - - <%=Services.TextService.Localize("paErrorPageHelp")%> - -
- -
- -
- -
-
- -
- - -
- - - - - - - diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index 85cebdf3f5..1df9127b8e 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -55,11 +55,6 @@ namespace Umbraco.Web.Trees new LegacyUrlAction( "create.aspx?nodeId=" + nodeId + "&nodeType=" + nodeType + "&nodeName=" + nodeName + "&rnd=" + DateTime.UtcNow.Ticks, Current.Services.TextService.Localize("actions/create"))); - case ActionProtect actionProtect: - return Attempt.Succeed( - new LegacyUrlAction( - "dialogs/protectPage.aspx?mode=cut&nodeId=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - Current.Services.TextService.Localize("actions/protect"))); } return Attempt.Fail(); } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 132d65c520..79ee2dbbb5 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1012,9 +1012,6 @@ ASPXCodeBehind - - ASPXCodeBehind - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs deleted file mode 100644 index 07d13230e5..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs +++ /dev/null @@ -1,745 +0,0 @@ -using Umbraco.Core.Services; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Web; -using System.Web.Security; -using System.Web.UI.WebControls; -using Umbraco.Core; -using Umbraco.Core.Logging; -using umbraco.controls; -using Umbraco.Core.Models; -using Umbraco.Core.Security; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web.UI.Pages; -using MembershipProviderExtensions = Umbraco.Core.Security.MembershipProviderExtensions; - -namespace umbraco.presentation.umbraco.dialogs -{ - /// - /// Summary description for protectPage. - /// - public partial class protectPage : UmbracoEnsuredPage - { - public protectPage() - { - CurrentApp = Constants.Applications.Content.ToString(); - } - - protected Literal jsShowWindow; - protected DualSelectbox _memberGroups = new DualSelectbox(); - protected ContentPicker loginPagePicker = new ContentPicker(); - protected ContentPicker errorPagePicker = new ContentPicker(); - - private const string MemberIdRuleType = "MemberId"; // moved from Constants-Conventions.cs - - - override protected void OnInit(EventArgs e) - { - base.OnInit(e); - } - - protected void selectMode(object sender, EventArgs e) - { - p_mode.Visible = false; - p_setup.Visible = true; - - if (rb_simple.Checked) - { - pane_advanced.Visible = false; - pane_simple.Visible = true; - bt_protect.CommandName = "simple"; - } - else - { - pane_advanced.Visible = true; - pane_simple.Visible = false; - bt_protect.CommandName = "advanced"; - } - } - - private ProtectionType GetProtectionType(int documentId) - { - var content = Services.ContentService.GetById(documentId); - if (content == null) return ProtectionType.NotProtected; - - var entry = Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) return ProtectionType.NotProtected; - - //legacy states that if it is protected by a member id then it is 'simple' - return entry.Rules.Any(x => x.RuleType == MemberIdRuleType) - ? ProtectionType.Simple - : ProtectionType.Advanced; - } - - private enum ProtectionType - { - NotProtected, - Simple, - Advanced - } - - private int GetErrorPage(string path) - { - var entry = Services.PublicAccessService.GetEntryForContent(path); - if (entry == null) return -1; - var entity = Services.EntityService.Get(entry.NoAccessNodeId, UmbracoObjectTypes.Document, false); - return entity.Id; - } - - private int GetLoginPage(string path) - { - var entry = Services.PublicAccessService.GetEntryForContent(path); - if (entry == null) return -1; - var entity = Services.EntityService.Get(entry.LoginNodeId, UmbracoObjectTypes.Document, false); - return entity.Id; - } - - private MembershipUser GetAccessingMembershipUser(int documentId) - { - var content = Services.ContentService.GetById(documentId); - if (content == null) return null; - var entry = Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) return null; - //legacy would throw an exception here if it was not 'simple' and simple means based on a username - if (entry.Rules.All(x => x.RuleType != Constants.Conventions.PublicAccess.MemberUsernameRuleType)) - { - throw new Exception("Document isn't protected using Simple mechanism. Use GetAccessingMemberGroups instead"); - } - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - var usernameRule = entry.Rules.First(x => x.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType); - return provider.GetUser(usernameRule.RuleValue, false); - } - - private bool IsProtectedByMembershipRole(int documentId, string role) - { - var content = Services.ContentService.GetById(documentId); - var entry = Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) return false; - return entry.Rules - .Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType - && x.RuleValue == role); - } - - private void ProtectPage(bool Simple, int DocumentId, int LoginDocumentId, int ErrorDocumentId) - { - var content = Current.Services.ContentService.GetById(DocumentId); - var loginContent = Services.ContentService.GetById(LoginDocumentId); - if (loginContent == null) throw new NullReferenceException("No content item found with id " + LoginDocumentId); - var noAccessContent = Services.ContentService.GetById(ErrorDocumentId); - if (noAccessContent == null) throw new NullReferenceException("No content item found with id " + ErrorDocumentId); - - var entry = Services.PublicAccessService.GetEntryForContent(content.Id.ToString()); - if (entry != null) - { - if (Simple) - { - // if using simple mode, make sure that all existing groups are removed - entry.ClearRules(); - } - - //ensure the correct ids are applied - entry.LoginNodeId = loginContent.Id; - entry.NoAccessNodeId = noAccessContent.Id; - } - else - { - entry = new PublicAccessEntry(content, - Services.ContentService.GetById(LoginDocumentId), - Services.ContentService.GetById(ErrorDocumentId), - new List()); - } - Services.PublicAccessService.Save(entry); - } - - private void AddMembershipRoleToDocument(int documentId, string role) - { - //event - var content = Current.Services.ContentService.GetById(documentId); - - var entry = Services.PublicAccessService.AddRule( - content, - Constants.Conventions.PublicAccess.MemberRoleRuleType, - role); - - if (entry.Success == false && entry.Result.Entity == null) - { - throw new Exception("Document is not protected!"); - } - } - - private void AddMembershipUserToDocument(int documentId, string membershipUserName) - { - //event - var content = Current.Services.ContentService.GetById(documentId); - var entry = Services.PublicAccessService.AddRule( - content, - Constants.Conventions.PublicAccess.MemberUsernameRuleType, - membershipUserName); - - if (entry.Success == false && entry.Result.Entity == null) - { - throw new Exception("Document is not protected!"); - } - } - - private void RemoveMembershipRoleFromDocument(int documentId, string role) - { - var content = Current.Services.ContentService.GetById(documentId); - Services.PublicAccessService.RemoveRule( - content, - Constants.Conventions.PublicAccess.MemberRoleRuleType, - role); - } - - private void RemoveProtection(int documentId) - { - var content = Current.Services.ContentService.GetById(documentId); - var entry = Services.PublicAccessService.GetEntryForContent(content); - if (entry != null) - { - Services.PublicAccessService.Delete(entry); - } - } - - protected void Page_Load(object sender, EventArgs e) - { - // Check for editing - int documentId = int.Parse(Request.GetItemAsString("nodeId")); - var content = Current.Services.ContentService.GetById(documentId); - jsShowWindow.Text = ""; - - ph_errorpage.Controls.Add(errorPagePicker); - ph_loginpage.Controls.Add(loginPagePicker); - - pp_login.Text = Services.TextService.Localize("login"); - pp_pass.Text = Services.TextService.Localize("password"); - pp_loginPage.Text = Services.TextService.Localize("paLoginPage"); - pp_errorPage.Text = Services.TextService.Localize("paErrorPage"); - - pane_chooseMode.Text = Services.TextService.Localize("publicAccess/paHowWould"); - pane_pages.Text = Services.TextService.Localize("publicAccess/paSelectPages"); - pane_simple.Text = Services.TextService.Localize("publicAccess/paSimple"); - pane_advanced.Text = Services.TextService.Localize("publicAccess/paAdvanced"); - - if (IsPostBack == false) - { - if (Services.PublicAccessService.IsProtected(documentId.ToString()) - && GetProtectionType(documentId) != ProtectionType.NotProtected) - { - bt_buttonRemoveProtection.Visible = true; - bt_buttonRemoveProtection.Attributes.Add("onClick", "return confirm('" + Services.TextService.Localize("areyousure") + "')"); - - // Get login and error pages - int errorPage = GetErrorPage(content.Path); - int loginPage = GetLoginPage(content.Path); - try - { - var loginPageContent = Current.Services.ContentService.GetById(loginPage); - if (loginPageContent != null) - { - loginPagePicker.Value = loginPage.ToString(CultureInfo.InvariantCulture); - } - var errorPageContent = Current.Services.ContentService.GetById(errorPage); - errorPagePicker.Value = errorPage.ToString(CultureInfo.InvariantCulture); - } - catch (Exception ex) - { - Current.Logger.Error(ex, "An error occurred initializing the protect page editor"); - } - - if (GetProtectionType(documentId) == ProtectionType.Simple) - { - MembershipUser m = GetAccessingMembershipUser(documentId); - if (m != null) - { - pane_simple.Visible = true; - pp_pass.Visible = false; - simpleLogin.Visible = false; - SimpleLoginLabel.Visible = true; - SimpleLoginLabel.Text = m.UserName; - pane_advanced.Visible = false; - bt_protect.CommandName = "simple"; - } - - } - else if (GetProtectionType(documentId) == ProtectionType.Advanced) - { - pane_simple.Visible = false; - pane_advanced.Visible = true; - bt_protect.CommandName = "advanced"; - } - - p_mode.Visible = false; - p_setup.Visible = true; - } - } - - // Load up membergrouops - _memberGroups.ID = "Membergroups"; - _memberGroups.Width = 175; - _memberGroups.Height = 165; - var selectedGroups = ""; - - // get roles from the membership provider - var roles = Roles.GetAllRoles().OrderBy(x => x).ToArray(); - - if (roles.Length > 0) - { - foreach (var role in roles) - { - var listItem = new ListItem(role, role); - _memberGroups.Items.Add(listItem); - if (IsPostBack) continue; - - // first time, initialize selected roles - if (IsProtectedByMembershipRole(documentId, role)) - selectedGroups += role + ","; - } - } - else - { - p_noGroupsFound.Visible = true; - rb_advanced.Enabled = false; - } - - _memberGroups.Value = selectedGroups; - groupsSelector.Controls.Add(_memberGroups); - - - bt_protect.Text = Services.TextService.Localize("buttons", "select"); - bt_buttonRemoveProtection.Text = Services.TextService.Localize("paRemoveProtection"); - - // Put user code to initialize the page here - } - - protected void ChangeOnClick(object sender, EventArgs e) - { - SimpleLoginNameValidator.IsValid = true; - SimpleLoginLabel.Visible = false; - simpleLogin.Visible = true; - pp_pass.Visible = true; - } - - protected void protect_Click(object sender, CommandEventArgs e) - { - if (string.IsNullOrEmpty(errorPagePicker.Value) || errorPagePicker.Value == "-1") - cv_errorPage.IsValid = false; - - if (string.IsNullOrEmpty(loginPagePicker.Value) || loginPagePicker.Value == "-1") - cv_loginPage.IsValid = false; - - //reset - SimpleLoginNameValidator.IsValid = true; - - var provider = Umbraco.Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - var pageId = int.Parse(Request.GetItemAsString("nodeId")); - - if (e.CommandName == "simple") - { - var memberLogin = simpleLogin.Visible ? simpleLogin.Text : SimpleLoginLabel.Text; - - var member = provider.GetUser(memberLogin, false); - if (member == null) - { - var tempEmail = "u" + Guid.NewGuid().ToString("N") + "@example.com"; - - // this needs to work differently depending on umbraco members or external membership provider - if (provider.IsUmbracoMembershipProvider() == false) - { - member = provider.CreateUser(memberLogin, simplePassword.Text, tempEmail); - } - else - { - //if it's the umbraco membership provider, then we need to tell it what member type to create it with - if (Current.Services.MemberTypeService.Get(Constants.Conventions.MemberTypes.SystemDefaultProtectType) == null) - //if (MemberType.GetByAlias(Constants.Conventions.MemberTypes.SystemDefaultProtectType) == null) - { - var defm = new MemberType(null, Constants.Conventions.MemberTypes.SystemDefaultProtectType); - Current.Services.MemberTypeService.Save(defm); - } - var castedProvider = provider.AsUmbracoMembershipProvider(); - MembershipCreateStatus status; - member = castedProvider.CreateUser(Constants.Conventions.MemberTypes.SystemDefaultProtectType, - memberLogin, simplePassword.Text, tempEmail, null, null, true, null, out status); - if (status != MembershipCreateStatus.Success) - { - SimpleLoginNameValidator.IsValid = false; - SimpleLoginNameValidator.ErrorMessage = "Could not create user: " + status; - SimpleLoginNameValidator.Text = "Could not create user: " + status; - return; - } - } - } - else if (pp_pass.Visible) - { - SimpleLoginNameValidator.IsValid = false; - SimpleLoginLabel.Visible = true; - SimpleLoginLabel.Text = memberLogin; - simpleLogin.Visible = false; - pp_pass.Visible = false; - return; - } - - // Create or find a memberGroup - var simpleRoleName = "__umbracoRole_" + member.UserName; - if (Roles.RoleExists(simpleRoleName) == false) - { - Roles.CreateRole(simpleRoleName); - } - if (Roles.IsUserInRole(member.UserName, simpleRoleName) == false) - { - Roles.AddUserToRole(member.UserName, simpleRoleName); - } - - ProtectPage(true, pageId, int.Parse(loginPagePicker.Value), int.Parse(errorPagePicker.Value)); - AddMembershipRoleToDocument(pageId, simpleRoleName); - AddMembershipUserToDocument(pageId, member.UserName); - } - else if (e.CommandName == "advanced") - { - if (cv_errorPage.IsValid && cv_loginPage.IsValid) - { - ProtectPage(false, pageId, int.Parse(loginPagePicker.Value), int.Parse(errorPagePicker.Value)); - - foreach (ListItem li in _memberGroups.Items) - if (("," + _memberGroups.Value + ",").IndexOf("," + li.Value + ",", StringComparison.Ordinal) > -1) - AddMembershipRoleToDocument(pageId, li.Value); - else - RemoveMembershipRoleFromDocument(pageId, li.Value); - } - else - { - return; - } - } - - var content = Services.ContentService.GetById(pageId); - var text = content == null ? "" : content.Name; - feedback_text.Text = HttpUtility.HtmlEncode(Services.TextService.Localize("publicAccess/paIsProtected", new[] { text })); - - p_setup.Visible = false; - p_feedback.Visible = true; - - //reloads the current node in the tree - ClientTools.SyncTree(content.Path, true); - //reloads the current node's children in the tree - ClientTools.ReloadActionNode(false, true); - } - - - protected void buttonRemoveProtection_Click(object sender, System.EventArgs e) - { - int pageId = int.Parse(Request.GetItemAsString("nodeId")); - p_setup.Visible = false; - - RemoveProtection(pageId); - - var content = Services.ContentService.GetById(pageId); - var text = content == null ? "" : content.Name; - feedback_text.Text = HttpUtility.HtmlEncode(Services.TextService.Localize("publicAccess/paIsRemoved", new[] { text })); - p_feedback.Visible = true; - - - //reloads the current node in the tree - ClientTools.SyncTree(content.Path, true); - //reloads the current node's children in the tree - ClientTools.ReloadActionNode(false, true); - } - - protected CustomValidator SimpleLoginNameValidator; - protected Label SimpleLoginLabel; - - /// - /// tempFile control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlInputHidden tempFile; - - /// - /// p_feedback control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Panel p_feedback; - - /// - /// p_mode control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Panel p_mode; - - /// - /// p_setup control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Panel p_setup; - - /// - /// pane_chooseMode control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane pane_chooseMode; - - /// - /// rb_simple control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RadioButton rb_simple; - - /// - /// rb_advanced control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RadioButton rb_advanced; - - /// - /// p_noGroupsFound control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Panel p_noGroupsFound; - - /// - /// bt_selectMode control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Button bt_selectMode; - - /// - /// pane_simple control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane pane_simple; - - /// - /// PropertyPanel1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel PropertyPanel1; - - /// - /// pp_login control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_login; - - /// - /// simpleLogin control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox simpleLogin; - - /// - /// pp_pass control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_pass; - - /// - /// simplePassword control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox simplePassword; - - /// - /// pane_advanced control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane pane_advanced; - - /// - /// PropertyPanel3 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel PropertyPanel3; - - /// - /// PropertyPanel2 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel PropertyPanel2; - - /// - /// groupsSelector control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder groupsSelector; - - /// - /// pane_pages control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane pane_pages; - - /// - /// pp_loginPage control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_loginPage; - - /// - /// ph_loginpage control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder ph_loginpage; - - /// - /// cv_loginPage control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.CustomValidator cv_loginPage; - - /// - /// pp_errorPage control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_errorPage; - - /// - /// ph_errorpage control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder ph_errorpage; - - /// - /// cv_errorPage control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.CustomValidator cv_errorPage; - - /// - /// bt_protect control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Button bt_protect; - - /// - /// bt_buttonRemoveProtection control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Button bt_buttonRemoveProtection; - - /// - /// errorId control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlInputHidden errorId; - - /// - /// loginId control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlInputHidden loginId; - - /// - /// js control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder js; - - /// - /// feedback_text control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal feedback_text; - - - } -} From ebf15b905280f7d542f89b528ba2a5b17e93f2b8 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 2 Jan 2019 15:24:05 +0100 Subject: [PATCH 20/21] Fix CacheRefresher and DistributedCache --- ...ests.cs => DistributedCacheBinderTests.cs} | 40 ++++- .../Integration/ContentEventsTests.cs | 12 +- .../Scoping/ScopedNuCacheTests.cs | 11 +- .../Scoping/ScopedRepositoryTests.cs | 19 +-- src/Umbraco.Tests/Scoping/ScopedXmlTests.cs | 15 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- .../Cache/DistributedCacheBinder.cs | 85 +++++++++++ .../Cache/DistributedCacheBinderComponent.cs | 24 +++ ....cs => DistributedCacheBinder_Handlers.cs} | 138 +++++------------- .../Cache/IDistributedCacheBinder.cs | 34 +++++ src/Umbraco.Web/Services/SectionService.cs | 12 +- src/Umbraco.Web/Umbraco.Web.csproj | 5 +- 12 files changed, 253 insertions(+), 144 deletions(-) rename src/Umbraco.Tests/Cache/{CacheRefresherComponentTests.cs => DistributedCacheBinderTests.cs} (87%) create mode 100644 src/Umbraco.Web/Cache/DistributedCacheBinder.cs create mode 100644 src/Umbraco.Web/Cache/DistributedCacheBinderComponent.cs rename src/Umbraco.Web/Cache/{CacheRefresherComponent.cs => DistributedCacheBinder_Handlers.cs} (86%) create mode 100644 src/Umbraco.Web/Cache/IDistributedCacheBinder.cs diff --git a/src/Umbraco.Tests/Cache/CacheRefresherComponentTests.cs b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs similarity index 87% rename from src/Umbraco.Tests/Cache/CacheRefresherComponentTests.cs rename to src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs index 0616b4098f..3cc5e061a5 100644 --- a/src/Umbraco.Tests/Cache/CacheRefresherComponentTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs @@ -1,21 +1,23 @@ using System; -using System.Collections.Generic; using System.Linq; +using System.Threading; +using Moq; using NUnit.Framework; using Umbraco.Core.Composing; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; using Umbraco.Web.Cache; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; namespace Umbraco.Tests.Cache { [TestFixture] [UmbracoTest(WithApplication = true)] - public class CacheRefresherEventHandlerTests : UmbracoTestBase + public class DistributedCacheBinderTests : UmbracoTestBase { [Test] public void Can_Find_All_Event_Handlers() @@ -114,7 +116,7 @@ namespace Umbraco.Tests.Cache var ok = true; foreach (var definition in definitions) { - var found = CacheRefresherComponent.FindHandler(definition); + var found = DistributedCacheBinder.FindHandler(definition); if (found == null) { Console.WriteLine("Couldn't find method for " + definition.EventName + " on " + definition.Sender.GetType()); @@ -123,5 +125,35 @@ namespace Umbraco.Tests.Cache } Assert.IsTrue(ok, "see log for details"); } + + [Test] + public void CanHandleEvent() + { + // refreshers.HandleEvents wants a UmbracoContext + // which wants an HttpContext, which we build using a SimpleWorkerRequest + // which requires these to be non-null + var domain = Thread.GetDomain(); + if (domain.GetData(".appPath") == null) + domain.SetData(".appPath", ""); + if (domain.GetData(".appVPath") == null) + domain.SetData(".appVPath", ""); + + // refreshers.HandleEvents wants a UmbracoContext + // which wants these + Container.RegisterSingleton(_ => Mock.Of()); + Container.RegisterCollectionBuilder(); + + // create some event definitions + var definitions = new IEventDefinition[] + { + // works because that event definition maps to an empty handler + new EventDefinition>(null, Current.Services.ContentTypeService, new SaveEventArgs(Enumerable.Empty()), "Saved"), + + }; + + // just assert it does not throw + var refreshers = new DistributedCacheBinder(null, null); + refreshers.HandleEvents(definitions); + } } } diff --git a/src/Umbraco.Tests/Integration/ContentEventsTests.cs b/src/Umbraco.Tests/Integration/ContentEventsTests.cs index af188c6a09..2aee582acc 100644 --- a/src/Umbraco.Tests/Integration/ContentEventsTests.cs +++ b/src/Umbraco.Tests/Integration/ContentEventsTests.cs @@ -2,19 +2,17 @@ using System.Collections.Generic; using System.Linq; using LightInject; +using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Sync; -using Umbraco.Tests.Cache.DistributedCache; using Umbraco.Tests.Services; using Umbraco.Tests.TestHelpers.Entities; -using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; using Umbraco.Web.Cache; using static Umbraco.Tests.Cache.DistributedCache.DistributedCacheTests; @@ -34,8 +32,8 @@ namespace Umbraco.Tests.Integration { base.SetUp(); - _h1 = new CacheRefresherComponent(true); - _h1.Initialize(new DistributedCache()); + _h1 = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _h1.BindEvents(true); _events = new List(); @@ -76,7 +74,7 @@ namespace Umbraco.Tests.Integration { base.TearDown(); - _h1?.Unbind(); + _h1?.UnbindEvents(); // clear ALL events @@ -86,7 +84,7 @@ namespace Umbraco.Tests.Integration ContentCacheRefresher.CacheUpdated -= ContentCacheUpdated; } - private CacheRefresherComponent _h1; + private DistributedCacheBinder _h1; private IList _events; private int _msgCount; private IContentType _contentType; diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index cf3285cd7e..211bdc3cdb 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence.Repositories; @@ -36,7 +37,7 @@ namespace Umbraco.Tests.Scoping [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)] public class ScopedNuCacheTests : TestWithDatabaseBase { - private CacheRefresherComponent _cacheRefresher; + private DistributedCacheBinder _distributedCacheBinder; protected override void Compose() { @@ -56,8 +57,8 @@ namespace Umbraco.Tests.Scoping { base.TearDown(); - _cacheRefresher?.Unbind(); - _cacheRefresher = null; + _distributedCacheBinder?.UnbindEvents(); + _distributedCacheBinder = null; _onPublishedAssertAction = null; ContentService.Published -= OnPublishedAssert; @@ -129,8 +130,8 @@ namespace Umbraco.Tests.Scoping var umbracoContext = GetUmbracoContextNu("http://example.com/", setSingleton: true); // wire cache refresher - _cacheRefresher = new CacheRefresherComponent(true); - _cacheRefresher.Initialize(new DistributedCache()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder.BindEvents(true); // create document type, document var contentType = new ContentType(-1) { Alias = "CustomDocument", Name = "Custom Document" }; diff --git a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs index 48bbdb1e22..9ace5860e1 100644 --- a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs @@ -13,6 +13,7 @@ using Umbraco.Web.Cache; using LightInject; using Moq; using Umbraco.Core.Events; +using Umbraco.Core.Logging; using Umbraco.Core.Sync; namespace Umbraco.Tests.Scoping @@ -21,7 +22,7 @@ namespace Umbraco.Tests.Scoping [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true)] public class ScopedRepositoryTests : TestWithDatabaseBase { - private CacheRefresherComponent _cacheRefresher; + private DistributedCacheBinder _distributedCacheBinder; protected override void Compose() { @@ -52,8 +53,8 @@ namespace Umbraco.Tests.Scoping [TearDown] public void Teardown() { - _cacheRefresher?.Unbind(); - _cacheRefresher = null; + _distributedCacheBinder?.UnbindEvents(); + _distributedCacheBinder = null; } [TestCase(true)] @@ -76,8 +77,8 @@ namespace Umbraco.Tests.Scoping // get user again - else we'd modify the one that's in the cache user = service.GetUserById(user.Id); - _cacheRefresher = new CacheRefresherComponent(true); - _cacheRefresher.Initialize(new DistributedCache()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder.BindEvents(true); Assert.IsNull(scopeProvider.AmbientScope); using (var scope = scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) @@ -157,8 +158,8 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual(lang.Id, globalCached.Id); Assert.AreEqual("fr-FR", globalCached.IsoCode); - _cacheRefresher = new CacheRefresherComponent(true); - _cacheRefresher.Initialize(new DistributedCache()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder.BindEvents(true); Assert.IsNull(scopeProvider.AmbientScope); using (var scope = scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) @@ -249,8 +250,8 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual(item.Id, globalCached.Id); Assert.AreEqual("item-key", globalCached.ItemKey); - _cacheRefresher = new CacheRefresherComponent(true); - _cacheRefresher.Initialize(new DistributedCache()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder.BindEvents(true); Assert.IsNull(scopeProvider.AmbientScope); using (var scope = scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs index 5f4e653735..1afbf5cb12 100644 --- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs @@ -7,6 +7,7 @@ using LightInject; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Events; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; @@ -23,7 +24,7 @@ namespace Umbraco.Tests.Scoping [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)] public class ScopedXmlTests : TestWithDatabaseBase { - private CacheRefresherComponent _cacheRefresher; + private DistributedCacheBinder _distributedCacheBinder; protected override void Compose() { @@ -42,8 +43,8 @@ namespace Umbraco.Tests.Scoping [TearDown] public void Teardown() { - _cacheRefresher?.Unbind(); - _cacheRefresher = null; + _distributedCacheBinder?.UnbindEvents(); + _distributedCacheBinder = null; _onPublishedAssertAction = null; ContentService.Published -= OnPublishedAssert; @@ -90,8 +91,8 @@ namespace Umbraco.Tests.Scoping var item = new Content("name", -1, contentType); // wire cache refresher - _cacheRefresher = new CacheRefresherComponent(true); - _cacheRefresher.Initialize(new DistributedCache()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder.BindEvents(true); // check xml in context = "before" var xml = XmlInContext; @@ -209,8 +210,8 @@ namespace Umbraco.Tests.Scoping Current.Services.ContentTypeService.Save(contentType); // wire cache refresher - _cacheRefresher = new CacheRefresherComponent(true); - _cacheRefresher.Initialize(new DistributedCache()); + _distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of()); + _distributedCacheBinder.BindEvents(true); // check xml in context = "before" var xml = XmlInContext; diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 7d1753a6b2..d58cf2a5b0 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -111,7 +111,7 @@ - + diff --git a/src/Umbraco.Web/Cache/DistributedCacheBinder.cs b/src/Umbraco.Web/Cache/DistributedCacheBinder.cs new file mode 100644 index 0000000000..f7171a840a --- /dev/null +++ b/src/Umbraco.Web/Cache/DistributedCacheBinder.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Umbraco.Core; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; + +namespace Umbraco.Web.Cache +{ + /// + /// Default implementation. + /// + public partial class DistributedCacheBinder : IDistributedCacheBinder + { + private static readonly ConcurrentDictionary FoundHandlers = new ConcurrentDictionary(); + private readonly DistributedCache _distributedCache; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + public DistributedCacheBinder(DistributedCache distributedCache, ILogger logger) + { + _distributedCache = distributedCache; + _logger = logger; + } + + // internal for tests + internal static MethodInfo FindHandler(IEventDefinition eventDefinition) + { + var name = eventDefinition.Sender.GetType().Name + "_" + eventDefinition.EventName; + + return FoundHandlers.GetOrAdd(name, n => CandidateHandlers.Value.FirstOrDefault(x => x.Name == n)); + } + + private static readonly Lazy CandidateHandlers = new Lazy(() => + { + var underscore = new[] { '_' }; + + return typeof(DistributedCacheBinder) + .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Select(x => + { + if (x.Name.Contains("_") == false) return null; + + var parts = x.Name.Split(underscore, StringSplitOptions.RemoveEmptyEntries).Length; + if (parts != 2) return null; + + var parameters = x.GetParameters(); + if (parameters.Length != 2) return null; + if (typeof(EventArgs).IsAssignableFrom(parameters[1].ParameterType) == false) return null; + return x; + }) + .WhereNotNull() + .ToArray(); + }); + + /// + public void HandleEvents(IEnumerable events) + { + // ensure we run with an UmbracoContext, because this may run in a background task, + // yet developers may be using the 'current' UmbracoContext in the event handlers + using (UmbracoContext.EnsureContext()) + { + foreach (var e in events) + { + var handler = FindHandler(e); + if (handler == null) + { + // fixme - should this be fatal (ie, an exception)? + var name = e.Sender.GetType().Name + "_" + e.EventName; + _logger.Warn("Dropping event {EventName} because no corresponding handler was found.", name); + continue; + } + + handler.Invoke(this, new[] { e.Sender, e.Args }); + } + } + } + } +} diff --git a/src/Umbraco.Web/Cache/DistributedCacheBinderComponent.cs b/src/Umbraco.Web/Cache/DistributedCacheBinderComponent.cs new file mode 100644 index 0000000000..57b013a97c --- /dev/null +++ b/src/Umbraco.Web/Cache/DistributedCacheBinderComponent.cs @@ -0,0 +1,24 @@ +using Umbraco.Core; +using Umbraco.Core.Components; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Cache +{ + /// + /// Installs listeners on service events in order to refresh our caches. + /// + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + [RequiredComponent(typeof(IUmbracoCoreComponent))] // runs before every other IUmbracoCoreComponent! + public class DistributedCacheBinderComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + public override void Compose(Composition composition) + { + composition.Container.RegisterSingleton(); + } + + public void Initialize(IDistributedCacheBinder distributedCacheBinder) + { + distributedCacheBinder.BindEvents(); + } + } +} diff --git a/src/Umbraco.Web/Cache/CacheRefresherComponent.cs b/src/Umbraco.Web/Cache/DistributedCacheBinder_Handlers.cs similarity index 86% rename from src/Umbraco.Web/Cache/CacheRefresherComponent.cs rename to src/Umbraco.Web/Cache/DistributedCacheBinder_Handlers.cs index a020b0d76f..81b133b9ef 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherComponent.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheBinder_Handlers.cs @@ -1,56 +1,50 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.IO; -using Umbraco.Core; +using System.Linq; using Umbraco.Core.Events; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; -using System.Linq; -using System.Reflection; -using System.Web; -using System.Web.Hosting; -using Umbraco.Core.Components; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services.Changes; using Umbraco.Core.Services.Implement; -using Umbraco.Web.Composing; -using Umbraco.Web.Security; using Umbraco.Web.Services; -using LightInject; -using ApplicationTree = Umbraco.Core.Models.ApplicationTree; namespace Umbraco.Web.Cache { /// - /// Installs listeners on service events in order to refresh our caches. + /// Default implementation. /// - [RuntimeLevel(MinLevel = RuntimeLevel.Run)] - [RequiredComponent(typeof(IUmbracoCoreComponent))] // runs before every other IUmbracoCoreComponent! - public class CacheRefresherComponent : UmbracoComponentBase, IUmbracoCoreComponent + public partial class DistributedCacheBinder { - private static readonly ConcurrentDictionary FoundHandlers = new ConcurrentDictionary(); - private DistributedCache _distributedCache; private List _unbinders; - public CacheRefresherComponent() - { } + private void Bind(Action binder, Action unbinder) + { + // bind now + binder(); - // for tests - public CacheRefresherComponent(bool supportUnbinding) + // and register unbinder for later, if needed + _unbinders?.Add(unbinder); + } + + /// + public void UnbindEvents() + { + if (_unbinders == null) + throw new NotSupportedException(); + foreach (var unbinder in _unbinders) + unbinder(); + _unbinders = null; + } + + /// + public void BindEvents(bool supportUnbinding = false) { if (supportUnbinding) _unbinders = new List(); - } - public void Initialize(DistributedCache distributedCache) - { - Current.Logger.Info("Initializing Umbraco internal event handlers for cache refreshing."); - - _distributedCache = distributedCache; + _logger.Info("Initializing Umbraco internal event handlers for cache refreshing."); // bind to application tree events Bind(() => ApplicationTreeService.Deleted += ApplicationTreeService_Deleted, @@ -170,74 +164,6 @@ namespace Umbraco.Web.Cache () => RelationService.DeletedRelationType -= RelationService_DeletedRelationType); } - #region Events binding and handling - - private void Bind(Action binder, Action unbinder) - { - // bind now - binder(); - - // and register unbinder for later, if needed - _unbinders?.Add(unbinder); - } - - // for tests - internal void Unbind() - { - if (_unbinders == null) - throw new NotSupportedException(); - foreach (var unbinder in _unbinders) - unbinder(); - _unbinders = null; - } - - internal static MethodInfo FindHandler(IEventDefinition eventDefinition) - { - var name = eventDefinition.Sender.GetType().Name + "_" + eventDefinition.EventName; - - return FoundHandlers.GetOrAdd(name, n => CandidateHandlers.Value.FirstOrDefault(x => x.Name == n)); - } - - private static readonly Lazy CandidateHandlers = new Lazy(() => - { - var underscore = new[] { '_' }; - - return typeof(CacheRefresherComponent) - .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) - .Select(x => - { - if (x.Name.Contains("_") == false) return null; - - var parts = x.Name.Split(underscore, StringSplitOptions.RemoveEmptyEntries).Length; - if (parts != 2) return null; - - var parameters = x.GetParameters(); - if (parameters.Length != 2) return null; - if (typeof(EventArgs).IsAssignableFrom(parameters[1].ParameterType) == false) return null; - return x; - }) - .WhereNotNull() - .ToArray(); - }); - - internal static void HandleEvents(IEnumerable events) - { - // ensure we run with an UmbracoContext, because this may run in a background task, - // yet developers may be using the 'current' UmbracoContext in the event handlers - using (UmbracoContext.EnsureContext()) - { - foreach (var e in events) - { - var handler = FindHandler(e); - if (handler == null) continue; - - handler.Invoke(null, new[] { e.Sender, e.Args }); - } - } - } - - #endregion - #region PublicAccessService private void PublicAccessService_Saved(IPublicAccessService sender, SaveEventArgs e) @@ -264,7 +190,8 @@ namespace Umbraco.Web.Cache /// case then we need to clear all user permissions cache. /// private void ContentService_Copied(IContentService sender, CopyEventArgs e) - { } + { + } /// /// Handles cache refreshing for when content is saved (not published) @@ -276,7 +203,8 @@ namespace Umbraco.Web.Cache /// stay up-to-date for unpublished content. /// private void ContentService_Saved(IContentService sender, SaveEventArgs e) - { } + { + } private void ContentService_Changed(IContentService sender, TreeChange.EventArgs args) { @@ -324,12 +252,12 @@ namespace Umbraco.Web.Cache #region Application event handlers - private void SectionService_New(Section sender, EventArgs e) + private void SectionService_New(ISectionService sender, EventArgs e) { _distributedCache.RefreshAllApplicationCache(); } - private void SectionService_Deleted(Section sender, EventArgs e) + private void SectionService_Deleted(ISectionService sender, EventArgs e) { _distributedCache.RefreshAllApplicationCache(); } @@ -439,7 +367,8 @@ namespace Umbraco.Web.Cache #region UserService - static void UserService_UserGroupPermissionsAssigned(IUserService sender, SaveEventArgs e) + // fixme STATIC?? + private void UserService_UserGroupPermissionsAssigned(IUserService sender, SaveEventArgs e) { //TODO: Not sure if we need this yet depends if we start caching permissions //var groupIds = e.SavedEntities.Select(x => x.UserGroupId).Distinct(); @@ -568,6 +497,7 @@ namespace Umbraco.Web.Cache _distributedCache.RemoveMemberGroupCache(m.Id); } } + #endregion #region RelationType diff --git a/src/Umbraco.Web/Cache/IDistributedCacheBinder.cs b/src/Umbraco.Web/Cache/IDistributedCacheBinder.cs new file mode 100644 index 0000000000..e4237fea94 --- /dev/null +++ b/src/Umbraco.Web/Cache/IDistributedCacheBinder.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Umbraco.Core.Events; +using Umbraco.Core.Services.Implement; + +namespace Umbraco.Web.Cache +{ + /// + /// Binds events to the distributed cache. + /// + /// + /// Use to bind actual events, eg , to + /// the distributed cache, so that the proper refresh operations are executed when these events trigger. + /// Use to handle events that have not actually triggered, but have + /// been queued, so that the proper refresh operations are also executed. + /// + public interface IDistributedCacheBinder + { + /// + /// Handles events from definitions. + /// + void HandleEvents(IEnumerable events); + + /// + /// Binds actual events to the distributed cache. + /// + /// A value indicating whether to support unbinding the events. + void BindEvents(bool enableUnbinding = false); + + /// + /// Unbinds bounded events. + /// + void UnbindEvents(); + } +} diff --git a/src/Umbraco.Web/Services/SectionService.cs b/src/Umbraco.Web/Services/SectionService.cs index ff8279a411..5d013d7e79 100644 --- a/src/Umbraco.Web/Services/SectionService.cs +++ b/src/Umbraco.Web/Services/SectionService.cs @@ -200,7 +200,7 @@ namespace Umbraco.Web.Services }, true); //raise event - OnNew(new Section(name, alias, sortOrder), new EventArgs()); + OnNew(this /*new Section(name, alias, sortOrder)*/, new EventArgs()); } } @@ -235,7 +235,7 @@ namespace Umbraco.Web.Services }, true); //raise event - OnDeleted(section, new EventArgs()); + OnDeleted(this, new EventArgs()); } } @@ -262,8 +262,8 @@ namespace Umbraco.Web.Services return tmp; } - internal static event TypedEventHandler Deleted; - private static void OnDeleted(Section app, EventArgs args) + internal static event TypedEventHandler Deleted; + private static void OnDeleted(ISectionService app, EventArgs args) { if (Deleted != null) { @@ -271,8 +271,8 @@ namespace Umbraco.Web.Services } } - internal static event TypedEventHandler New; - private static void OnNew(Section app, EventArgs args) + internal static event TypedEventHandler New; + private static void OnNew(ISectionService app, EventArgs args) { if (New != null) { diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 6089e68325..17c2a54d81 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -108,7 +108,10 @@ + + + @@ -652,7 +655,7 @@ - + From 17d818b6049c79f2d0403c05bbe7bb13154c79b6 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 2 Jan 2019 15:29:22 +0100 Subject: [PATCH 21/21] Make Current.Container public for Deploy, wont last --- src/Umbraco.Core/Composing/Current.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs index e3bce53a3c..9515398236 100644 --- a/src/Umbraco.Core/Composing/Current.cs +++ b/src/Umbraco.Core/Composing/Current.cs @@ -38,7 +38,7 @@ namespace Umbraco.Core.Composing /// /// Gets or sets the DI container. /// - internal static IServiceContainer Container + public static IServiceContainer Container { get {