diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web/Editors/SectionController.cs index ae69d9da0a..0e96551d6f 100644 --- a/src/Umbraco.Web/Editors/SectionController.cs +++ b/src/Umbraco.Web/Editors/SectionController.cs @@ -3,7 +3,9 @@ using AutoMapper; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using System.Linq; +using Umbraco.Core.Models; using Umbraco.Web.Trees; +using Section = Umbraco.Web.Models.ContentEditing.Section; namespace Umbraco.Web.Editors { @@ -54,11 +56,19 @@ namespace Umbraco.Web.Editors return sectionModels; } - + + /// + /// Returns all the sections that the user has access to + /// + /// public IEnumerable
GetAllSections() { var sections = Services.SectionService.GetSections(); - return sections.Select(Mapper.Map); + var mapped = sections.Select(Mapper.Map); + if (Security.CurrentUser.IsAdmin()) + return mapped; + + return mapped.Where(x => Security.CurrentUser.AllowedSections.Contains(x.Alias)).ToArray(); } } diff --git a/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs b/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs index ce3be3eb55..b43ea26797 100644 --- a/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs +++ b/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs @@ -31,18 +31,12 @@ namespace Umbraco.Web.Editors /// The start media ids of the user being saved (can be null or empty) /// The user aliases of the user being saved (can be null or empty) /// - public Attempt AuthorizeActions(IUser currentUser, + public Attempt IsAuthorized(IUser currentUser, IUser savingUser, IEnumerable startContentIds, IEnumerable startMediaIds, IEnumerable userGroupAliases) { - var currentIsAdmin = currentUser.IsAdmin(); - - // a1) An admin can edit anything - if (currentIsAdmin) - return Attempt.Succeed(); - - // a2) A non-admin cannot save an admin + // a) A non-admin cannot save an admin if (savingUser != null) { @@ -50,17 +44,23 @@ namespace Umbraco.Web.Editors return Attempt.Fail("The current user is not an administrator"); } - // b0) A user cannot set a start node on another user that they don't have access to + // b) A user cannot set a start node on another user that they don't have access to, this even goes for admins var pathResult = AuthorizePath(currentUser, startContentIds, startMediaIds); if (pathResult == false) return pathResult; + + // c) an admin can manage any group or section access + + var currentIsAdmin = currentUser.IsAdmin(); + if (currentIsAdmin) + return Attempt.Succeed(); if (userGroupAliases != null) { var userGroups = _userService.GetUserGroupsByAlias(userGroupAliases.ToArray()).ToArray(); - // b1) A user cannot assign a group to another user that grants them access to a start node they don't have access to + // d) A user cannot assign a group to another user that grants them access to a start node they don't have access to foreach (var group in userGroups) { pathResult = AuthorizePath(currentUser, @@ -70,7 +70,7 @@ namespace Umbraco.Web.Editors return pathResult; } - // c) A user cannot set a section on another user that they don't have access to + // e) A user cannot set a section on another user that they don't have access to var allGroupSections = userGroups.SelectMany(x => x.AllowedSections).Distinct(); var missingSectionAccess = allGroupSections.Except(currentUser.AllowedSections).ToArray(); if (missingSectionAccess.Length > 0) diff --git a/src/Umbraco.Web/Editors/UserGroupAuthorizationAttribute.cs b/src/Umbraco.Web/Editors/UserGroupAuthorizationAttribute.cs index 70ec52a0bb..a7da90c740 100644 --- a/src/Umbraco.Web/Editors/UserGroupAuthorizationAttribute.cs +++ b/src/Umbraco.Web/Editors/UserGroupAuthorizationAttribute.cs @@ -1,11 +1,10 @@ using System; using System.Linq; +using System.Net; using System.Net.Http; using System.Web.Http; using System.Web.Http.Controllers; using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; namespace Umbraco.Web.Editors { @@ -41,29 +40,22 @@ namespace Umbraco.Web.Editors protected override bool IsAuthorized(HttpActionContext actionContext) { - var currentUser = GetUmbracoContext().Security.CurrentUser; - - //admins can access any group - if (currentUser.IsAdmin()) - return base.IsAuthorized(actionContext); - - var queryString = actionContext.Request - .GetQueryNameValuePairs(); + var umbCtx = GetUmbracoContext(); + var currentUser = umbCtx.Security.CurrentUser; + + var queryString = actionContext.Request.GetQueryNameValuePairs(); var ids = queryString.Where(x => x.Key == _paramName).ToArray(); if (ids.Length == 0) return base.IsAuthorized(actionContext); var intIds = ids.Select(x => x.Value.TryConvertTo()).Where(x => x.Success).Select(x => x.Result).ToArray(); - return AuthorizeGroupAccess(currentUser, intIds); - } - - private bool AuthorizeGroupAccess(IUser currentUser, params int[] groupIds) - { - var groups = GetUmbracoContext().Application.Services.UserService.GetAllUserGroups(groupIds); - var groupAliases = groups.Select(x => x.Alias).ToArray(); - var userGroups = currentUser.Groups.Select(x => x.Alias).ToArray(); - return userGroups.ContainsAll(groupAliases); + var authHelper = new UserGroupEditorAuthorizationHelper( + umbCtx.Application.Services.UserService, + umbCtx.Application.Services.ContentService, + umbCtx.Application.Services.MediaService, + umbCtx.Application.Services.EntityService); + return authHelper.AuthorizeGroupAccess(currentUser, intIds); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/UserGroupEditorAuthorizationHelper.cs b/src/Umbraco.Web/Editors/UserGroupEditorAuthorizationHelper.cs new file mode 100644 index 0000000000..6bc39b376d --- /dev/null +++ b/src/Umbraco.Web/Editors/UserGroupEditorAuthorizationHelper.cs @@ -0,0 +1,122 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Editors +{ + internal class UserGroupEditorAuthorizationHelper + { + private readonly IUserService _userService; + private readonly IContentService _contentService; + private readonly IMediaService _mediaService; + private readonly IEntityService _entityService; + + public UserGroupEditorAuthorizationHelper(IUserService userService, IContentService contentService, IMediaService mediaService, IEntityService entityService) + { + _userService = userService; + _contentService = contentService; + _mediaService = mediaService; + _entityService = entityService; + } + + /// + /// Authorize that the current user belongs to these groups + /// + /// + /// + /// + public Attempt AuthorizeGroupAccess(IUser currentUser, params int[] groupIds) + { + if (currentUser.IsAdmin()) + return Attempt.Succeed(); + + var groups = _userService.GetAllUserGroups(groupIds.ToArray()); + var groupAliases = groups.Select(x => x.Alias).ToArray(); + var userGroups = currentUser.Groups.Select(x => x.Alias).ToArray(); + var missingAccess = groupAliases.Except(userGroups).ToArray(); + return missingAccess.Length == 0 + ? Attempt.Succeed() + : Attempt.Fail("User is not a member of " + string.Join(", ", missingAccess)); + } + + /// + /// Authorize that the current user belongs to these groups + /// + /// + /// + /// + public Attempt AuthorizeGroupAccess(IUser currentUser, params string[] groupAliases) + { + if (currentUser.IsAdmin()) + return Attempt.Succeed(); + + var userGroups = currentUser.Groups.Select(x => x.Alias).ToArray(); + var missingAccess = groupAliases.Except(userGroups).ToArray(); + return missingAccess.Length == 0 + ? Attempt.Succeed() + : Attempt.Fail("User is not a member of " + string.Join(", ", missingAccess)); + } + + /// + /// Authorize that the user is not adding a section to the group that they don't have access to + /// + /// + /// + /// + /// + public Attempt AuthorizeSectionChanges(IUser currentUser, + IEnumerable currentAllowedSections, + IEnumerable proposedAllowedSections) + { + if (currentUser.IsAdmin()) + return Attempt.Succeed(); + + var sectionsAdded = currentAllowedSections.Except(proposedAllowedSections).ToArray(); + var sectionAccessMissing = sectionsAdded.Except(currentUser.AllowedSections).ToArray(); + return sectionAccessMissing.Length > 0 + ? Attempt.Fail("Current user doesn't have access to add these sections " + string.Join(", ", sectionAccessMissing)) + : Attempt.Succeed(); + } + + /// + /// Authorize that the user is not changing to a start node that they don't have access to (including admins) + /// + /// + /// + /// + /// + /// + /// + public Attempt AuthorizeStartNodeChanges(IUser currentUser, + int? currentContentStartId, + int? proposedContentStartId, + int? currentMediaStartId, + int? proposedMediaStartId) + { + if (currentContentStartId != proposedContentStartId && proposedContentStartId.HasValue) + { + var content = _contentService.GetById(proposedContentStartId.Value); + if (content != null) + { + if (currentUser.HasPathAccess(content, _entityService) == false) + return Attempt.Fail("Current user doesn't have access to the content path " + content.Path); + } + } + + if (currentMediaStartId != proposedMediaStartId && proposedMediaStartId.HasValue) + { + var media = _mediaService.GetById(proposedMediaStartId.Value); + if (media != null) + { + if (currentUser.HasPathAccess(media, _entityService) == false) + return Attempt.Fail("Current user doesn't have access to the media path " + media.Path); + } + } + + return Attempt.Succeed(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/UserGroupsController.cs b/src/Umbraco.Web/Editors/UserGroupsController.cs index 5934ee63eb..9db73a82a0 100644 --- a/src/Umbraco.Web/Editors/UserGroupsController.cs +++ b/src/Umbraco.Web/Editors/UserGroupsController.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Web.Http; using System.Web.Http.Filters; using AutoMapper; +using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; @@ -26,9 +27,32 @@ namespace Umbraco.Web.Editors { if (userGroupSave == null) throw new ArgumentNullException("userGroupSave"); + //authorize that the user has access to save this user group + var authHelper = new UserGroupEditorAuthorizationHelper( + Services.UserService, Services.ContentService, Services.MediaService, Services.EntityService); + var isAuthorized = authHelper.AuthorizeGroupAccess(Security.CurrentUser, userGroupSave.Alias); + if (isAuthorized == false) + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, isAuthorized.Result)); + + //if sections were added we need to check that the current user has access to that section + isAuthorized = authHelper.AuthorizeSectionChanges(Security.CurrentUser, + userGroupSave.PersistedUserGroup.AllowedSections, + userGroupSave.Sections); + if (isAuthorized == false) + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, isAuthorized.Result)); + + //if start nodes were changed we need to check that the current user has access to them + isAuthorized = authHelper.AuthorizeStartNodeChanges(Security.CurrentUser, + userGroupSave.PersistedUserGroup.StartContentId, + userGroupSave.StartContentId, + userGroupSave.PersistedUserGroup.StartMediaId, + userGroupSave.StartMediaId); + if (isAuthorized == false) + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, isAuthorized.Result)); + //save the group Services.UserService.Save(userGroupSave.PersistedUserGroup, userGroupSave.Users.ToArray()); - + //deal with permissions //remove ones that have been removed @@ -71,6 +95,11 @@ namespace Umbraco.Web.Editors public IEnumerable GetUserGroups() { var allGroups = Mapper.Map, IEnumerable>(Services.UserService.GetAllUserGroups()); + + //if admin, return all groups + if (Security.CurrentUser.IsAdmin()) + return allGroups; + //we cannot return user groups that this user does not have access to var currentUserGroups = Security.CurrentUser.Groups.Select(x => x.Alias).ToArray(); return allGroups.Where(x => currentUserGroups.Contains(x.Alias)).ToArray(); diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index d304112cb5..0bcc6b7cf9 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -271,7 +271,7 @@ namespace Umbraco.Web.Editors //Perform authorization here to see if the current user can actually save this user with the info being requested var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService); - var canSaveUser = authHelper.AuthorizeActions(Security.CurrentUser, null, null, null, userSave.UserGroups); + var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, null, null, null, userSave.UserGroups); if (canSaveUser == false) { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, canSaveUser.Result)); @@ -358,7 +358,7 @@ namespace Umbraco.Web.Editors //Perform authorization here to see if the current user can actually save this user with the info being requested var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService); - var canSaveUser = authHelper.AuthorizeActions(Security.CurrentUser, user, null, null, userSave.UserGroups); + var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, user, null, null, userSave.UserGroups); if (canSaveUser == false) { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, canSaveUser.Result)); @@ -471,7 +471,7 @@ namespace Umbraco.Web.Editors //Perform authorization here to see if the current user can actually save this user with the info being requested var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService); - var canSaveUser = authHelper.AuthorizeActions(Security.CurrentUser, found, userSave.StartContentIds, userSave.StartMediaIds, userSave.UserGroups); + var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, found, userSave.StartContentIds, userSave.StartMediaIds, userSave.UserGroups); if (canSaveUser == false) { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, canSaveUser.Result)); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index aa9957b329..a210c269d0 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -330,6 +330,7 @@ +