From 5026bc03e1c6cc5e48358b474019044b1a901bad Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 24 Nov 2018 19:39:15 +0100 Subject: [PATCH] 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 @@ +