A lot more implementation :)

This commit is contained in:
Kenn Jacobsen
2018-11-24 19:39:15 +01:00
parent 18258fe487
commit 5026bc03e1
9 changed files with 479 additions and 94 deletions

View File

@@ -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
* <pre>
* contentResource.getPublicAccess(contentId)
* .then(function(publicAccess) {
* // do your thing
* });
* </pre>
*
* @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
);
}
};

View File

@@ -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();
}

View File

@@ -1,82 +1,151 @@
<div ng-controller="Umbraco.Editors.Content.ProtectController as vm">
<div class="umb-dialog-body form-horizontal" ng-cloak>
<div class="umb-pane">
<form name="vm.protectForm" novalidate>
<div class="umb-dialog-body form-horizontal" ng-cloak>
<umb-pane>
<div ng-show="error">
<div class="alert alert-error">
<div><strong>{{error.errorMsg}}</strong></div>
<div>{{error.data.message}}</div>
</div>
</div>
<umb-load-indicator ng-show="vm.loading">
</umb-load-indicator>
<umb-pane ng-show="vm.step === 'type'">
<p class="abstract" ng-hide="success">
<localize key="publicAccess_paHowWould">Choose how to restrict access to the page</localize> <strong>{{currentNode.name}}</strong>
</p>
<div class="pa-select-type">
<input id="protectionTypeUser" type="radio" name="protectionType" value="user" ng-model="vm.type">
<label for="protectionTypeUser">
<h5 class="pa-access-header"><localize key="publicAccess_paSimple">Single user protection</localize></h5>
<p><localize key="publicAccess_paSimpleHelp">If you just want to setup simple protection using a single login and password.</localize></p>
</label>
<div ng-show="error">
<div class="alert alert-error">
<div><strong>{{error.errorMsg}}</strong></div>
<div>{{error.data.message}}</div>
</div>
</div>
<div class="pa-select-type">
<input id="protectionTypeRole" type="radio" name="protectionType" value="role" ng-model="vm.type">
<umb-load-indicator ng-show="vm.loading">
</umb-load-indicator>
<label for="protectionTypeRole">
<h5 class="pa-access-header"><localize key="publicAccess_paAdvanced">Role based protection</localize></h5>
<p><localize key="publicAccess_paAdvancedHelp">If you wish to control access to the page using role-based authentication, using Umbraco's member groups.</localize></p>
</label>
<div ng-show="!vm.step && !vm.loading">
<p class="abstract" ng-hide="success">
<localize key="publicAccess_paHowWould" tokens="[currentNode.name]">Choose how to restrict access to this page</localize>
</p>
<div class="pa-select-type">
<input id="protectionTypeUser" type="radio" name="protectionType" value="user" ng-model="vm.type">
<label for="protectionTypeUser">
<h5 class="pa-access-header"><localize key="publicAccess_paSimple">Single user protection</localize></h5>
<p><localize key="publicAccess_paSimpleHelp">If you just want to setup simple protection using a single login and password</localize></p>
</label>
</div>
<div class="pa-select-type">
<input id="protectionTypeRole" type="radio" name="protectionType" value="role" ng-model="vm.type">
<label for="protectionTypeRole">
<h5 class="pa-access-header"><localize key="publicAccess_paAdvanced">Role based protection</localize></h5>
<p><localize key="publicAccess_paAdvancedHelp">If you wish to control access to the page using role-based authentication, using Umbraco's member groups</localize></p>
</label>
</div>
</div>
<div ng-show="vm.step && !vm.loading && !vm.removing">
<div ng-if="vm.step === 'user'">
<p><localize key="publicAccess_paSetLogin" tokens="[currentNode.name]">Set the login and password for this page</localize></p>
<umb-control-group label="@general_login">
<input type="text" name="userName" ng-model="vm.userName" class="-full-width-input" required />
</umb-control-group>
<umb-control-group label="@general_password">
<!-- TODO KJAC: get password regex from server -->
<input type="password" name="password" ng-model="vm.password" class="-full-width-input" required pattern="^....$" />
</umb-control-group>
</div>
<div ng-show="vm.step === 'role' && !vm.groups.length">
<p><localize key="publicAccess_paAdvancedNoGroups">You need to create a membergroup before you can use role-based authentication</localize></p>
</div>
<div ng-show="vm.step === 'role' && vm.groups.length">
<p><localize key="publicAccess_paSelectRoles" tokens="[currentNode.name]">Pick the roles who have access to this page</localize></p>
<umb-control-group ng-repeat="group in vm.groups | orderBy:'name'" label="{{group.name}}">
<umb-toggle checked="group.selected"
on-click="vm.toggle(group, $event)"
hide-icons="true">
</umb-toggle>
</umb-control-group>
</div>
<div ng-show="vm.step === 'user' || vm.groups.length">
<p><localize key="publicAccess_paSelectPages">Select the pages that contain login form and error messages</localize></p>
<umb-control-group label="@publicAccess_paLoginPage" description="@publicAccess_paLoginPageHelp">
<a href ng-show="!vm.loginPage" ng-click="vm.pickLoginPage()" class="umb-node-preview-add" prevent-default>
<localize key="general_add">Add</localize>
</a>
<umb-node-preview ng-show="vm.loginPage"
icon="vm.loginPage.icon"
name="vm.loginPage.name"
allow-remove="true"
on-remove="vm.loginPage = null">
</umb-node-preview>
</umb-control-group>
<umb-control-group label="@publicAccess_paErrorPage" description="@publicAccess_paErrorPageHelp">
<a href ng-show="!vm.errorPage" ng-click="vm.pickErrorPage()" class="umb-node-preview-add" prevent-default>
<localize key="general_add">Add</localize>
</a>
<umb-node-preview ng-show="vm.errorPage"
icon="vm.errorPage.icon"
name="vm.errorPage.name"
allow-remove="true"
on-remove="vm.errorPage = null">
</umb-node-preview>
</umb-control-group>
</div>
</div>
<div ng-show="vm.removing">
<p><localize key="publicAccess_paRemoveProtectionConfirm" tokens="[currentNode.name]">Are you sure you want to remove the protection from this page?</localize></p>
</div>
<!--<pre>{{vm | json}}</pre>-->
</umb-pane>
<umb-pane ng-show="vm.step === 'user'">
TODO: user config
</umb-pane>
<umb-pane ng-show="vm.step === 'role' && !vm.groups.length">
<p><localize key="publicAccess_paAdvancedNoGroups">You need to create a membergroup before you can use role-based authentication.</localize></p>
</umb-pane>
<umb-pane ng-show="vm.step === 'role' && vm.groups.length">
TODO: role config
</umb-pane>
<!--<pre>{{vm | json}}</pre>-->
</div>
</div>
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar" ng-hide="success">
<umb-button
type="button"
button-style="link"
action="vm.close()"
label-key="general_close">
</umb-button>
<div class="umb-dialog-footer umb-btn-toolbar" ng-hide="vm.loading || vm.removing">
<umb-button type="button"
button-style="link"
action="vm.close()"
label-key="general_close">
</umb-button>
<umb-button
ng-hide="vm.step !== 'type'"
type="button"
action="vm.next()"
button-style="success"
label-key="general_next">
</umb-button>
<umb-button ng-hide="vm.step"
type="button"
action="vm.next()"
button-style="success"
label-key="general_next"
disabled="vm.loading || !vm.type">
</umb-button>
<umb-button
ng-show="vm.step !== 'type'"
type="button"
action="vm.save()"
state="vm.saveButtonState"
button-style="success"
label-key="buttons_save"
disabled="vm.buttonState === 'busy' || !TODO">
</umb-button>
</div>
<umb-button ng-show="vm.canRemove"
type="button"
action="vm.remove()"
button-style="warning"
label-key="publicAccess_paRemoveProtection"
disabled="vm.loading">
</umb-button>
<umb-button ng-show="vm.step"
type="button"
action="vm.save()"
state="vm.saveButtonState"
button-style="success"
label-key="buttons_save"
disabled="vm.buttonState === 'busy' || !vm.isValid()">
</umb-button>
</div>
<div class="umb-dialog-footer umb-btn-toolbar" ng-show="vm.removing">
<umb-button type="button"
button-style="link"
action="vm.close()"
label-key="buttons_confirmActionCancel">
</umb-button>
<umb-button type="button"
action="vm.removeConfirm()"
state="vm.saveButtonState"
button-style="success"
label-key="buttons_confirmActionConfirm"
disabled="vm.buttonState === 'busy'">
</umb-button>
</div>
</form>
</div>

View File

@@ -872,20 +872,22 @@ Mange hilsner fra Umbraco robotten
<key alias="removeSpecialFormattering">Indsæt, men fjern formattering som ikke bør være på en webside (Anbefales)</key>
</area>
<area alias="publicAccess">
<!-- TODO KJAC: clean up unused translations -->
<key alias="paAdvanced">Rollebaseret beskyttelse</key>
<key alias="paAdvancedHelp">Hvis du ønsker at kontrollere adgang til siden ved hjælp af rollebaseret godkendelse via Umbracos medlemsgrupper.</key>
<key alias="paAdvancedNoGroups">Du skal oprette en medlemsgruppe før du kan bruge rollebaseret godkendelse</key>
<key alias="paErrorPage">Fejlside</key>
<key alias="paErrorPageHelp">Brugt når folk er logget ind, men ingen adgang</key>
<key alias="paHowWould">Vælg hvordan siden skal beskyttes</key>
<key alias="paIsProtected">%0% er nu beskyttet</key>
<key alias="paIsRemoved">Beskyttelse fjernet fra %0%</key>
<key alias="paHowWould"><![CDATA[Vælg hvordan siden <strong>%0%</strong> skal beskyttes]]></key>
<!--<key alias="paIsProtected">%0% er nu beskyttet</key>
<key alias="paIsRemoved">Beskyttelse fjernet fra %0%</key>-->
<key alias="paLoginPage">Log ind-side</key>
<key alias="paLoginPageHelp">Vælg siden der indeholder log ind-formularen</key>
<key alias="paRemoveProtection">Fjern beskyttelse</key>
<key alias="paRemoveProtection">Fjern beskyttelse...</key>
<key alias="paRemoveProtectionConfirm"><![CDATA[Er du sikker på at du vil fjerne beskyttelsen fra siden <strong>%0%</strong>?]]></key>
<key alias="paSelectPages">Vælg siderne der indeholder log ind-formularer og fejlmeddelelser</key>
<key alias="paSelectRoles">Vælg de roller der har adgang til denne side</key>
<key alias="paSetLogin">Indstil login og kodeord for denne side</key>
<key alias="paSelectRoles"><![CDATA[Vælg de roller der har adgang til siden <strong>%0%</strong>]]></key>
<key alias="paSetLogin"><![CDATA[Indstil login og kodeord for siden <strong>%0%</strong>]]></key>
<key alias="paSimple">Enkel brugerbeskyttelse</key>
<key alias="paSimpleHelp">Hvis du blot ønsker at opsætte simpel beskyttelse ved hjælp af et enkelt login og kodeord</key>
</area>

View File

@@ -1124,22 +1124,24 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="removeSpecialFormattering">Paste, but remove formatting (Recommended)</key>
</area>
<area alias="publicAccess">
<!-- TODO KJAC: clean up unused translations -->
<key alias="paAdvanced">Role based protection</key>
<key alias="paAdvancedHelp">If you wish to control access to the page using role-based authentication, using Umbraco's member groups.</key>
<key alias="paAdvancedNoGroups">You need to create a membergroup before you can use role-based authentication.</key>
<key alias="paAdvancedHelp">If you wish to control access to the page using role-based authentication, using Umbraco's member groups</key>
<key alias="paAdvancedNoGroups">You need to create a membergroup before you can use role-based authentication</key>
<key alias="paErrorPage">Error Page</key>
<key alias="paErrorPageHelp">Used when people are logged on, but do not have access</key>
<key alias="paHowWould">Choose how to restrict access to the page</key>
<key alias="paIsProtected">%0% is now protected</key>
<key alias="paIsRemoved">Protection removed from %0%</key>
<key alias="paHowWould"><![CDATA[Choose how to restrict access to the page <strong>%0%</strong>]]></key>
<!--<key alias="paIsProtected">%0% is now protected</key>
<key alias="paIsRemoved">Protection removed from %0%</key>-->
<key alias="paLoginPage">Login Page</key>
<key alias="paLoginPageHelp">Choose the page that contains the login form</key>
<key alias="paRemoveProtection">Remove Protection</key>
<key alias="paRemoveProtection">Remove protection...</key>
<key alias="paRemoveProtectionConfirm"><![CDATA[Are you sure you want to remove the protection from the page <strong>%0%</strong>?]]></key>
<key alias="paSelectPages">Select the pages that contain login form and error messages</key>
<key alias="paSelectRoles">Pick the roles who have access to this page</key>
<key alias="paSetLogin">Set the login and password for this page</key>
<key alias="paSelectRoles"><![CDATA[Pick the roles who have access to the page <strong>%0%</strong>]]></key>
<key alias="paSetLogin"><![CDATA[Set the login and password for the page <strong>%0%</strong>]]></key>
<key alias="paSimple">Single user protection</key>
<key alias="paSimpleHelp">If you just want to setup simple protection using a single login and password.</key>
<key alias="paSimpleHelp">If you just want to setup simple protection using a single login and password</key>
</area>
<area alias="publish">
<key alias="contentPublishedFailedAwaitingRelease"><![CDATA[

View File

@@ -10,7 +10,9 @@ namespace Umbraco.Web.Actions
/// </summary>
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";

View File

@@ -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<EntityBasic>(loginPageEntity) : null,
ErrorPage = errorPageEntity != null ? Mapper.Map<EntityBasic>(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<PublicAccessRule>());
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);
}
}
}

View File

@@ -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; }
}
}

View File

@@ -150,6 +150,7 @@
<Compile Include="Media\TypeDetector\SvgDetector.cs" />
<Compile Include="Media\TypeDetector\TIFFDetector.cs" />
<Compile Include="Media\UploadAutoFillProperties.cs" />
<Compile Include="Models\ContentEditing\PublicAccess.cs" />
<Compile Include="Models\ContentEditing\RollbackVersion.cs" />
<Compile Include="Models\ContentEditing\UnpublishContent.cs" />
<Compile Include="Models\Mapping\MappingOperationOptionsExtensions.cs" />