From 0fc310cc4e3bede9461b04fad60a8214aaf32ad4 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 31 Mar 2022 15:57:23 +0200 Subject: [PATCH] start work on controllers --- .../Editors/UserEditorAuthorizationHelper.cs | 2 +- .../Extensions/PublishedContentExtensions.cs | 2 +- src/Umbraco.Core/Models/BackOfficeTour.cs | 2 +- src/Umbraco.Core/Models/ITemplate.cs | 2 +- src/Umbraco.Core/Models/Template.cs | 2 +- .../Repositories/IMemberRepository.cs | 2 +- src/Umbraco.Core/Services/FileService.cs | 2 +- src/Umbraco.Core/Services/IFileService.cs | 2 +- .../Services/IMembershipMemberService.cs | 2 +- src/Umbraco.Core/Services/IUserService.cs | 2 +- src/Umbraco.Core/Services/MemberService.cs | 2 +- src/Umbraco.Core/Services/UserService.cs | 4 +- .../Services/UserServiceExtensions.cs | 2 +- .../Implement/MemberRepository.cs | 2 +- .../Security/BackOfficeIdentityUser.cs | 2 +- .../Controllers/AuthenticationController.cs | 2 +- .../Controllers/BackOfficeController.cs | 4 +- .../Controllers/TemplateController.cs | 23 ++-- .../Controllers/TemplateQueryController.cs | 61 ++++----- .../Controllers/TourController.cs | 21 +-- .../UmbracoAuthorizedApiController.cs | 18 +-- .../Controllers/UpdateCheckController.cs | 4 +- .../UserGroupEditorAuthorizationHelper.cs | 30 ++--- .../Controllers/UserGroupsController.cs | 65 ++++----- .../Controllers/UsersController.cs | 123 ++++++++++-------- .../ActionsResults/ValidationErrorResult.cs | 4 +- 26 files changed, 210 insertions(+), 177 deletions(-) diff --git a/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs b/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs index eeb3982118..b2cd34875c 100644 --- a/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs +++ b/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs @@ -37,7 +37,7 @@ namespace Umbraco.Cms.Core.Editors /// The user aliases of the user being saved (can be null or empty) /// public Attempt IsAuthorized(IUser? currentUser, - IUser savingUser, + IUser? savingUser, IEnumerable? startContentIds, IEnumerable? startMediaIds, IEnumerable? userGroupAliases) { diff --git a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs index 280f25ad23..27f092b6ac 100644 --- a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs @@ -963,7 +963,7 @@ namespace Umbraco.Extensions /// The specific culture to filter for. If null is used the current culture is used. (Default is null) /// The content type alias. /// The children of the content, of any of the specified types. - public static IEnumerable? ChildrenOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) + public static IEnumerable? ChildrenOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? contentTypeAlias, string? culture = null) { return content.Children(variationContextAccessor, x => x.ContentType.Alias.InvariantEquals(contentTypeAlias), culture); } diff --git a/src/Umbraco.Core/Models/BackOfficeTour.cs b/src/Umbraco.Core/Models/BackOfficeTour.cs index a8e6c535e1..d6a5d8971e 100644 --- a/src/Umbraco.Core/Models/BackOfficeTour.cs +++ b/src/Umbraco.Core/Models/BackOfficeTour.cs @@ -18,7 +18,7 @@ namespace Umbraco.Cms.Core.Models public string? Name { get; set; } [DataMember(Name = "alias")] - public string? Alias { get; set; } + public string Alias { get; set; } = null!; [DataMember(Name = "group")] public string? Group { get; set; } diff --git a/src/Umbraco.Core/Models/ITemplate.cs b/src/Umbraco.Core/Models/ITemplate.cs index 3f82957eb5..e20dcc55fa 100644 --- a/src/Umbraco.Core/Models/ITemplate.cs +++ b/src/Umbraco.Core/Models/ITemplate.cs @@ -31,6 +31,6 @@ namespace Umbraco.Cms.Core.Models /// Set the mastertemplate /// /// - void SetMasterTemplate(ITemplate masterTemplate); + void SetMasterTemplate(ITemplate? masterTemplate); } } diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 67fdecadfd..1ba6b4a5ff 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -63,7 +63,7 @@ namespace Umbraco.Cms.Core.Models /// public bool IsMasterTemplate { get; set; } - public void SetMasterTemplate(ITemplate masterTemplate) + public void SetMasterTemplate(ITemplate? masterTemplate) { if (masterTemplate == null) { diff --git a/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs index 42a4dfc788..a868eefe9f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs @@ -9,7 +9,7 @@ namespace Umbraco.Cms.Core.Persistence.Repositories { int[] GetMemberIds(string[] names); - IMember? GetByUsername(string username); + IMember? GetByUsername(string? username); /// /// Finds members in a given role diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index 0d65ac1c2a..c6a5e5f8e4 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -368,7 +368,7 @@ namespace Umbraco.Cms.Core.Services /// /// /// - public ITemplate CreateTemplateWithIdentity(string name, string alias, string content, ITemplate? masterTemplate = null, int userId = Constants.Security.SuperUserId) + public ITemplate CreateTemplateWithIdentity(string? name, string? alias, string? content, ITemplate? masterTemplate = null, int userId = Constants.Security.SuperUserId) { if (name == null) { diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs index b6b4772d0d..9df9a671c3 100644 --- a/src/Umbraco.Core/Services/IFileService.cs +++ b/src/Umbraco.Core/Services/IFileService.cs @@ -253,7 +253,7 @@ namespace Umbraco.Cms.Core.Services /// Attempt?> CreateTemplateForContentType(string contentTypeAlias, string contentTypeName, int userId = Constants.Security.SuperUserId); - ITemplate CreateTemplateWithIdentity(string name, string alias, string content, ITemplate? masterTemplate = null, int userId = Constants.Security.SuperUserId); + ITemplate CreateTemplateWithIdentity(string? name, string? alias, string? content, ITemplate? masterTemplate = null, int userId = Constants.Security.SuperUserId); /// /// Deletes a template by its alias diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index f183882b55..94dbbf3da9 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -99,7 +99,7 @@ namespace Umbraco.Cms.Core.Services /// An can be of type or /// Username to use for retrieval /// - T? GetByUsername(string username); + T? GetByUsername(string? username); /// /// Deletes an diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index 5478a924e3..0842d47b36 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -243,7 +243,7 @@ namespace Umbraco.Cms.Core.Services /// If null than no changes are made to the users who are assigned to this group, however if a value is passed in /// than all users will be removed from this group and only these users will be added /// - void Save(IUserGroup userGroup, int[]? userIds = null); + void Save(IUserGroup? userGroup, int[]? userIds = null); /// /// Deletes a UserGroup diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 7f7eac952a..77c6476ec9 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -392,7 +392,7 @@ namespace Umbraco.Cms.Core.Services /// /// Username to use for retrieval /// - public IMember? GetByUsername(string username) + public IMember? GetByUsername(string? username) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 680882a990..1cf1f9ede2 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -813,7 +813,7 @@ namespace Umbraco.Cms.Core.Services /// than all users will be removed from this group and only these users will be added /// /// Default is True otherwise set to False to not raise events - public void Save(IUserGroup userGroup, int[]? userIds = null) + public void Save(IUserGroup? userGroup, int[]? userIds = null) { var evtMsgs = EventMessagesFactory.Get(); @@ -826,7 +826,7 @@ namespace Umbraco.Cms.Core.Services if (userIds != null) { - var groupUsers = userGroup.HasIdentity ? _userRepository.GetAllInGroup(userGroup.Id).ToArray() : empty; + var groupUsers = userGroup?.HasIdentity ?? false ? _userRepository.GetAllInGroup(userGroup.Id).ToArray() : empty; var xGroupUsers = groupUsers.ToDictionary(x => x.Id, x => x); var groupIds = groupUsers.Select(x => x.Id).ToArray(); var addedUserIds = userIds.Except(groupIds); diff --git a/src/Umbraco.Core/Services/UserServiceExtensions.cs b/src/Umbraco.Core/Services/UserServiceExtensions.cs index 2f0d8eac12..227550e0d9 100644 --- a/src/Umbraco.Core/Services/UserServiceExtensions.cs +++ b/src/Umbraco.Core/Services/UserServiceExtensions.cs @@ -32,7 +32,7 @@ namespace Umbraco.Extensions /// /// Specifying nothing will return all permissions for all nodes /// An enumerable list of - public static EntityPermissionCollection GetPermissions(this IUserService service, IUserGroup group, bool fallbackToDefaultPermissions, params int[] nodeIds) + public static EntityPermissionCollection GetPermissions(this IUserService service, IUserGroup? group, bool fallbackToDefaultPermissions, params int[] nodeIds) { return service.GetPermissions(new[] {group}, fallbackToDefaultPermissions, nodeIds); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs index 867c90be40..17f7c0a67d 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs @@ -290,7 +290,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement ordering); } - public IMember? GetByUsername(string username) => + public IMember? GetByUsername(string? username) => _memberByUsernameCachePolicy.Get(username, PerformGetByUsername, PerformGetAllByUsername); public int[] GetMemberIds(string[] usernames) diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs index 9f948716c6..64e04f7810 100644 --- a/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs @@ -29,7 +29,7 @@ namespace Umbraco.Cms.Core.Security /// Used to construct a new instance without an identity /// /// This is allowed to be null (but would need to be filled in if trying to persist this instance) - public static BackOfficeIdentityUser CreateNew(GlobalSettings globalSettings, string username, string email, string culture, string? name = null) + public static BackOfficeIdentityUser CreateNew(GlobalSettings globalSettings, string? username, string email, string culture, string? name = null) { if (string.IsNullOrWhiteSpace(username)) { diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 2d1e9198a2..74c14ddd62 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -190,7 +190,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } else { - BackOfficeExternaLoginProviderScheme opt = await _externalAuthenticationOptions.GetAsync(authType.Name); + BackOfficeExternaLoginProviderScheme? opt = await _externalAuthenticationOptions.GetAsync(authType.Name); if (opt == null) { return BadRequest($"Could not find external authentication options registered for provider {unlinkLoginModel.LoginProvider}"); diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index e01759791d..9dce470b3e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -391,7 +391,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return RedirectToLocal(Url.Action(nameof(Default), this.GetControllerName())); } - ExternalLoginInfo info = await _signInManager.GetExternalLoginInfoAsync(await _userManager.GetUserIdAsync(user)); + ExternalLoginInfo? info = await _signInManager.GetExternalLoginInfoAsync(await _userManager.GetUserIdAsync(user)); if (info == null) { @@ -448,7 +448,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var oauthRedirectAuthProvider = _externalLogins.GetAutoLoginProvider(); if (!oauthRedirectAuthProvider.IsNullOrWhiteSpace()) { - return ExternalLogin(oauthRedirectAuthProvider); + return ExternalLogin(oauthRedirectAuthProvider!); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs index 23c955219a..65052df636 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs @@ -14,6 +14,7 @@ using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Cms.Web.Common.DependencyInjection; +using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Web.BackOffice.Controllers @@ -55,7 +56,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - public TemplateDisplay GetByAlias(string alias) + public TemplateDisplay? GetByAlias(string alias) { var template = _fileService.GetTemplate(alias); return template == null ? null : _umbracoMapper.Map(template); @@ -65,9 +66,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// Get all templates /// /// - public IEnumerable GetAll() + public IEnumerable? GetAll() { - return _fileService.GetTemplates().Select(_umbracoMapper.Map); + return _fileService.GetTemplates()?.Select(_umbracoMapper.Map).WhereNotNull(); } /// @@ -75,7 +76,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - public ActionResult GetById(int id) + public ActionResult GetById(int id) { var template = _fileService.GetTemplate(id); if (template == null) @@ -90,7 +91,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - public ActionResult GetById(Guid id) + public ActionResult GetById(Guid id) { var template = _fileService.GetTemplate(id); if (template == null) @@ -104,7 +105,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - public ActionResult GetById(Udi id) + public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; if (guidUdi == null) @@ -136,7 +137,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return Ok(); } - public TemplateDisplay GetScaffold(int id) + public TemplateDisplay? GetScaffold(int id) { //empty default var dt = new Template(_shortStringHelper, string.Empty, string.Empty); @@ -154,7 +155,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var content = _defaultViewContentProvider.GetDefaultFileContent( layoutPageAlias: dt.MasterTemplateAlias ); var scaffold = _umbracoMapper.Map(dt); - scaffold.Content = content; + if (scaffold is not null) + { + scaffold.Content = content; + } + return scaffold; } @@ -244,7 +249,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers else { //create - ITemplate master = null; + ITemplate? master = null; if (string.IsNullOrEmpty(display.MasterTemplateAlias) == false) { master = _fileService.GetTemplate(display.MasterTemplateAlias); diff --git a/src/Umbraco.Web.BackOffice/Controllers/TemplateQueryController.cs b/src/Umbraco.Web.BackOffice/Controllers/TemplateQueryController.cs index d0aadac744..86a65ac977 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/TemplateQueryController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/TemplateQueryController.cs @@ -75,11 +75,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers public QueryResultModel PostTemplateQuery(QueryModel model) { var queryExpression = new StringBuilder(); - IEnumerable contents; + IEnumerable? contents; if (model == null) { - contents = _publishedContentQuery.ContentAtRoot().FirstOrDefault().Children(_variationContextAccessor); + contents = _publishedContentQuery.ContentAtRoot().FirstOrDefault()?.Children(_variationContextAccessor); queryExpression.Append("Umbraco.ContentAtRoot().FirstOrDefault().Children()"); } else @@ -90,7 +90,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers // timing should be fairly correct, due to the fact that all the linq statements are yield returned. var timer = new Stopwatch(); timer.Start(); - var results = contents.ToList(); + var results = contents?.ToList() ?? new List(); timer.Stop(); return new QueryResultModel @@ -106,12 +106,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers }; } - private IEnumerable PostTemplateValue(QueryModel model, StringBuilder queryExpression) + private IEnumerable? PostTemplateValue(QueryModel model, StringBuilder queryExpression) { var indent = Environment.NewLine + " "; // set the source - IPublishedContent sourceDocument; + IPublishedContent? sourceDocument; if (model.Source != null && model.Source.Id > 0) { sourceDocument = _publishedContentQuery.Content(model.Source.Id); @@ -128,7 +128,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } // get children, optionally filtered by type - IEnumerable contents; + IEnumerable? contents; queryExpression.Append(indent); if (model.ContentType != null && !model.ContentType.Alias.IsNullOrWhiteSpace()) { @@ -145,25 +145,28 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers queryExpression.Append(".Children()"); } - // apply filters - foreach (var condition in model.Filters.Where(x => !x.ConstraintValue.IsNullOrWhiteSpace())) + if (model.Filters is not null) { - //x is passed in as the parameter alias for the linq where statement clause - var operation = condition.BuildCondition("x"); + // apply filters + foreach (var condition in model.Filters.Where(x => !x.ConstraintValue.IsNullOrWhiteSpace())) + { + //x is passed in as the parameter alias for the linq where statement clause + var operation = condition.BuildCondition("x"); //for review - this uses a tonized query rather then the normal linq query. - contents = contents.Where(operation.Compile()); - queryExpression.Append(indent); - queryExpression.AppendFormat(".Where({0})", operation); + contents = contents?.Where(operation.Compile()); + queryExpression.Append(indent); + queryExpression.AppendFormat(".Where({0})", operation); + } } // always add IsVisible() to the query - contents = contents.Where(x => x.IsVisible(_publishedValueFallback)); + contents = contents?.Where(x => x.IsVisible(_publishedValueFallback)); queryExpression.Append(indent); queryExpression.Append(".Where(x => x.IsVisible())"); // apply sort - if (model.Sort != null && !model.Sort.Property.Alias.IsNullOrWhiteSpace()) + if (model.Sort != null && (!model.Sort.Property?.Alias.IsNullOrWhiteSpace() ?? false)) { contents = SortByDefaultPropertyValue(contents, model.Sort); @@ -177,7 +180,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers // take if (model.Take > 0) { - contents = contents.Take(model.Take); + contents = contents?.Take(model.Take); queryExpression.Append(indent); queryExpression.AppendFormat(".Take({0})", model.Take); } @@ -199,30 +202,30 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } } - private IEnumerable SortByDefaultPropertyValue(IEnumerable contents, SortExpression sortExpression) + private IEnumerable? SortByDefaultPropertyValue(IEnumerable? contents, SortExpression sortExpression) { - switch (sortExpression.Property.Alias) + switch (sortExpression.Property?.Alias) { case "id": return sortExpression.Direction == "ascending" - ? contents.OrderBy(x => x.Id) - : contents.OrderByDescending(x => x.Id); + ? contents?.OrderBy(x => x.Id) + : contents?.OrderByDescending(x => x.Id); case "createDate": return sortExpression.Direction == "ascending" - ? contents.OrderBy(x => x.CreateDate) - : contents.OrderByDescending(x => x.CreateDate); + ? contents?.OrderBy(x => x.CreateDate) + : contents?.OrderByDescending(x => x.CreateDate); case "publishDate": return sortExpression.Direction == "ascending" - ? contents.OrderBy(x => x.UpdateDate) - : contents.OrderByDescending(x => x.UpdateDate); + ? contents?.OrderBy(x => x.UpdateDate) + : contents?.OrderByDescending(x => x.UpdateDate); case "name": return sortExpression.Direction == "ascending" - ? contents.OrderBy(x => x.Name) - : contents.OrderByDescending(x => x.Name); + ? contents?.OrderBy(x => x.Name) + : contents?.OrderByDescending(x => x.Name); default: return sortExpression.Direction == "ascending" - ? contents.OrderBy(x => x.Name) - : contents.OrderByDescending(x => x.Name); + ? contents?.OrderBy(x => x.Name) + : contents?.OrderByDescending(x => x.Name); } } @@ -233,7 +236,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers public IEnumerable GetContentTypes() { var contentTypes = _contentTypeService.GetAll() - .Select(x => new ContentTypeModel { Alias = x.Alias, Name = _localizedTextService.Localize("template", "contentOfType", tokens: new string[] { x.Name }) }) + .Select(x => new ContentTypeModel { Alias = x.Alias, Name = _localizedTextService.Localize("template", "contentOfType", tokens: new string[] { x.Name ?? string.Empty }) }) .OrderBy(x => x.Name).ToList(); contentTypes.Insert(0, new ContentTypeModel { Alias = string.Empty, Name = _localizedTextService.Localize("template", "allContent") }); diff --git a/src/Umbraco.Web.BackOffice/Controllers/TourController.cs b/src/Umbraco.Web.BackOffice/Controllers/TourController.cs index 63696a8dea..d11334f98e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/TourController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/TourController.cs @@ -14,6 +14,7 @@ using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Tour; using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Extensions; namespace Umbraco.Cms.Web.BackOffice.Controllers { @@ -48,7 +49,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (_tourSettings.EnableTours == false) return result; - var user = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser; + var user = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; if (user == null) return result; @@ -134,6 +135,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { var resourceStream = GetType().Assembly.GetManifestResourceStream(fileName); + if (resourceStream is null) + { + return string.Empty; + } using var reader = new StreamReader(resourceStream, Encoding.UTF8); return await reader.ReadToEndAsync(); } @@ -176,7 +181,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers List filters, List aliasOnlyFilters, Func> fileNameToFileContent, - string pluginName = null) + string? pluginName = null) { var fileName = Path.GetFileNameWithoutExtension(tourFile); if (fileName == null) return; @@ -198,20 +203,20 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var contents = await fileNameToFileContent(tourFile); var tours = JsonConvert.DeserializeObject(contents); - var backOfficeTours = tours.Where(x => - aliasFilters.Count == 0 || aliasFilters.All(filter => filter.IsMatch(x.Alias)) == false); + var backOfficeTours = tours?.Where(x => + aliasFilters.Count == 0 || aliasFilters.WhereNotNull().All(filter => filter.IsMatch(x.Alias)) == false); - var user = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser; + var user = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; - var localizedTours = backOfficeTours.Where(x => - string.IsNullOrWhiteSpace(x.Culture) || x.Culture.Equals(user.Language, + var localizedTours = backOfficeTours?.Where(x => + string.IsNullOrWhiteSpace(x.Culture) || x.Culture.Equals(user?.Language, StringComparison.InvariantCultureIgnoreCase)).ToList(); var tour = new BackOfficeTourFile { FileName = Path.GetFileNameWithoutExtension(tourFile), PluginName = pluginName, - Tours = localizedTours + Tours = localizedTours ?? new List(), }; //don't add if all of the tours are filtered diff --git a/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs index 5b548a146a..9e81cb196e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs @@ -63,17 +63,17 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers // creates validation problem details instance. // borrowed from netcore: https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/ControllerBase.cs#L1970 - protected ValidationProblemDetails GetValidationProblemDetails( - string detail = null, - string instance = null, + protected ValidationProblemDetails? GetValidationProblemDetails( + string? detail = null, + string? instance = null, int? statusCode = null, - string title = null, - string type = null, - [ActionResultObjectValue] ModelStateDictionary modelStateDictionary = null) + string? title = null, + string? type = null, + [ActionResultObjectValue] ModelStateDictionary? modelStateDictionary = null) { modelStateDictionary ??= ModelState; - ValidationProblemDetails validationProblem; + ValidationProblemDetails? validationProblem; if (ProblemDetailsFactory == null) { // ProblemDetailsFactory may be null in unit testing scenarios. Improvise to make this more testable. @@ -108,7 +108,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// protected virtual ActionResult ValidationProblem(string errorMessage) { - ValidationProblemDetails problemDetails = GetValidationProblemDetails(errorMessage); + ValidationProblemDetails? problemDetails = GetValidationProblemDetails(errorMessage); return new ValidationErrorResult(problemDetails); } @@ -118,7 +118,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - protected virtual ActionResult ValidationProblem(object value, int statusCode) + protected virtual ActionResult ValidationProblem(object? value, int statusCode) => new ValidationErrorResult(value, statusCode); /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/UpdateCheckController.cs b/src/Umbraco.Web.BackOffice/Controllers/UpdateCheckController.cs index dad9c1d4ef..9620848666 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UpdateCheckController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UpdateCheckController.cs @@ -41,11 +41,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } [UpdateCheckResponseFilter] - public async Task GetCheck() + public async Task GetCheck() { var updChkCookie = _cookieManager.GetCookieValue("UMB_UPDCHK"); var updateCheckCookie = updChkCookie ?? string.Empty; - if (_globalSettings.VersionCheckPeriod > 0 && string.IsNullOrEmpty(updateCheckCookie) && _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsAdmin()) + if (_globalSettings.VersionCheckPeriod > 0 && string.IsNullOrEmpty(updateCheckCookie) && (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.IsAdmin() ?? false)) { try { diff --git a/src/Umbraco.Web.BackOffice/Controllers/UserGroupEditorAuthorizationHelper.cs b/src/Umbraco.Web.BackOffice/Controllers/UserGroupEditorAuthorizationHelper.cs index 35eab84432..e71018c460 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UserGroupEditorAuthorizationHelper.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UserGroupEditorAuthorizationHelper.cs @@ -56,9 +56,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - public Attempt AuthorizeGroupAccess(IUser currentUser, params string[] groupAliases) + public Attempt AuthorizeGroupAccess(IUser? currentUser, params string[] groupAliases) { - if (currentUser.IsAdmin()) + if (currentUser?.IsAdmin() ?? false) return Attempt.Succeed(); var existingGroups = _userService.GetUserGroupsByAlias(groupAliases); @@ -67,12 +67,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { // We're dealing with new groups, // so authorization should be given to any user with access to Users section - if (currentUser.AllowedSections.Contains(Constants.Applications.Users)) + if (currentUser?.AllowedSections.Contains(Constants.Applications.Users) ?? false) return Attempt.Succeed(); } - var userGroups = currentUser.Groups.Select(x => x.Alias).ToArray(); - var missingAccess = groupAliases.Except(userGroups).ToArray(); + var userGroups = currentUser?.Groups.Select(x => x.Alias).ToArray(); + var missingAccess = groupAliases.Except(userGroups ?? Array.Empty()).ToArray(); return missingAccess.Length == 0 ? Attempt.Succeed() : Attempt.Fail("User is not a member of " + string.Join(", ", missingAccess)); @@ -82,16 +82,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// 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 existingSections, - IEnumerable proposedAllowedSections) + IUser? currentUser, + IEnumerable? existingSections, + IEnumerable? proposedAllowedSections) { - if (currentUser.IsAdmin()) + if (currentUser?.IsAdmin() ?? false) return Attempt.Succeed(); - var sectionsAdded = proposedAllowedSections.Except(existingSections).ToArray(); - var sectionAccessMissing = sectionsAdded.Except(currentUser.AllowedSections).ToArray(); - return sectionAccessMissing.Length > 0 + var sectionsAdded = proposedAllowedSections?.Except(existingSections ?? Enumerable.Empty()).ToArray(); + var sectionAccessMissing = sectionsAdded?.Except(currentUser?.AllowedSections ?? Enumerable.Empty()).ToArray(); + return sectionAccessMissing?.Length > 0 ? Attempt.Fail("Current user doesn't have access to add these sections " + string.Join(", ", sectionAccessMissing)) : Attempt.Succeed(); } @@ -105,7 +105,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - public Attempt AuthorizeStartNodeChanges(IUser currentUser, + public Attempt AuthorizeStartNodeChanges(IUser? currentUser, int? currentContentStartId, int? proposedContentStartId, int? currentMediaStartId, @@ -116,7 +116,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var content = _contentService.GetById(proposedContentStartId.Value); if (content != null) { - if (currentUser.HasPathAccess(content, _entityService, _appCaches) == false) + if (currentUser?.HasPathAccess(content, _entityService, _appCaches) == false) return Attempt.Fail("Current user doesn't have access to the content path " + content.Path); } } @@ -126,7 +126,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var media = _mediaService.GetById(proposedMediaStartId.Value); if (media != null) { - if (currentUser.HasPathAccess(media, _entityService, _appCaches) == false) + if (currentUser?.HasPathAccess(media, _entityService, _appCaches) == false) return Attempt.Fail("Current user doesn't have access to the media path " + media.Path); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/UserGroupsController.cs b/src/Umbraco.Web.BackOffice/Controllers/UserGroupsController.cs index ad8d11f95a..a6b9772d43 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UserGroupsController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UserGroupsController.cs @@ -57,7 +57,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } [UserGroupValidate] - public ActionResult PostSaveUserGroup(UserGroupSave userGroupSave) + public ActionResult PostSaveUserGroup(UserGroupSave userGroupSave) { if (userGroupSave == null) throw new ArgumentNullException(nameof(userGroupSave)); @@ -65,23 +65,23 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var authHelper = new UserGroupEditorAuthorizationHelper( _userService, _contentService, _mediaService, _entityService, _appCaches); - var isAuthorized = authHelper.AuthorizeGroupAccess(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, userGroupSave.Alias); + var isAuthorized = authHelper.AuthorizeGroupAccess(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, userGroupSave.Alias); if (isAuthorized == false) return Unauthorized(isAuthorized.Result); //if sections were added we need to check that the current user has access to that section isAuthorized = authHelper.AuthorizeSectionChanges( - _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, - userGroupSave.PersistedUserGroup.AllowedSections, + _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, + userGroupSave.PersistedUserGroup?.AllowedSections, userGroupSave.Sections); if (isAuthorized == false) return Unauthorized(isAuthorized.Result); //if start nodes were changed we need to check that the current user has access to them - isAuthorized = authHelper.AuthorizeStartNodeChanges(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, - userGroupSave.PersistedUserGroup.StartContentId, + isAuthorized = authHelper.AuthorizeStartNodeChanges(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, + userGroupSave.PersistedUserGroup?.StartContentId, userGroupSave.StartContentId, - userGroupSave.PersistedUserGroup.StartMediaId, + userGroupSave.PersistedUserGroup?.StartMediaId, userGroupSave.StartMediaId); if (isAuthorized == false) return Unauthorized(isAuthorized.Result); @@ -93,43 +93,48 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _umbracoMapper.Map(userGroupSave, userGroupSave.PersistedUserGroup); //save the group - _userService.Save(userGroupSave.PersistedUserGroup, userGroupSave.Users.ToArray()); + _userService.Save(userGroupSave.PersistedUserGroup, userGroupSave.Users?.ToArray()); //deal with permissions //remove ones that have been removed var existing = _userService.GetPermissions(userGroupSave.PersistedUserGroup, true) .ToDictionary(x => x.EntityId, x => x); - var toRemove = existing.Keys.Except(userGroupSave.AssignedPermissions.Select(x => x.Key)); - foreach (var contentId in toRemove) + if (userGroupSave.AssignedPermissions is not null) { - _userService.RemoveUserGroupPermissions(userGroupSave.PersistedUserGroup.Id, contentId); - } + var toRemove = existing.Keys.Except(userGroupSave.AssignedPermissions.Select(x => x.Key)); + foreach (var contentId in toRemove) + { + _userService.RemoveUserGroupPermissions(userGroupSave.PersistedUserGroup?.Id ?? default, contentId); + } - //update existing - foreach (var assignedPermission in userGroupSave.AssignedPermissions) - { - _userService.ReplaceUserGroupPermissions( - userGroupSave.PersistedUserGroup.Id, - assignedPermission.Value.Select(x => x[0]), - assignedPermission.Key); + //update existing + foreach (var assignedPermission in userGroupSave.AssignedPermissions) + { + _userService.ReplaceUserGroupPermissions( + userGroupSave.PersistedUserGroup?.Id ?? default, + assignedPermission.Value.Select(x => x[0]), + assignedPermission.Key); + } } var display = _umbracoMapper.Map(userGroupSave.PersistedUserGroup); - display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles","operationSavedHeader"), _localizedTextService.Localize("speechBubbles","editUserGroupSaved")); + display?.AddSuccessNotification(_localizedTextService.Localize("speechBubbles","operationSavedHeader"), _localizedTextService.Localize("speechBubbles","editUserGroupSaved")); return display; } private void EnsureNonAdminUserIsInSavedUserGroup(UserGroupSave userGroupSave) { - if (_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsAdmin()) + if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.IsAdmin() ?? false) { return; } - var userIds = userGroupSave.Users.ToList(); - if (userIds.Contains(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id)) + var userIds = userGroupSave.Users?.ToList(); + if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser is null || + userIds is null || + userIds.Contains(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id)) { return; } @@ -142,7 +147,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// Returns the scaffold for creating a new user group /// /// - public UserGroupDisplay GetEmptyUserGroup() + public UserGroupDisplay? GetEmptyUserGroup() { return _umbracoMapper.Map(new UserGroup(_shortStringHelper)); } @@ -151,24 +156,24 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// Returns all user groups /// /// - public IEnumerable GetUserGroups(bool onlyCurrentUserGroups = true) + public IEnumerable GetUserGroups(bool onlyCurrentUserGroups = true) { var allGroups = _umbracoMapper.MapEnumerable(_userService.GetAllUserGroups()) .ToList(); - var isAdmin = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsAdmin(); + var isAdmin = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.IsAdmin() ?? false; if (isAdmin) return allGroups; if (onlyCurrentUserGroups == false) { //this user is not an admin so in that case we need to exclude all admin users - allGroups.RemoveAt(allGroups.IndexOf(allGroups.Find(basic => basic.Alias == Constants.Security.AdminGroupAlias))); + allGroups.RemoveAt(allGroups.IndexOf(allGroups.Find(basic => basic?.Alias == Constants.Security.AdminGroupAlias))); return allGroups; } //we cannot return user groups that this user does not have access to - var currentUserGroups = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Groups.Select(x => x.Alias).ToArray(); - return allGroups.Where(x => currentUserGroups.Contains(x.Alias)).ToArray(); + var currentUserGroups = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Groups.Select(x => x.Alias).ToArray(); + return allGroups.WhereNotNull().Where(x => currentUserGroups?.Contains(x.Alias) ?? false).ToArray(); } /// @@ -176,7 +181,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// [Authorize(Policy = AuthorizationPolicies.UserBelongsToUserGroupInRequest)] - public ActionResult GetUserGroup(int id) + public ActionResult GetUserGroup(int id) { var found = _userService.GetUserGroupById(id); if (found == null) diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index ad766e034c..ae07b7f4d0 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -136,7 +136,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// public ActionResult GetCurrentUserAvatarUrls() { - var urls = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator); + var urls = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator); if (urls == null) return ValidationProblem("Could not access Gravatar endpoint"); @@ -223,8 +223,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (filePath.IsNullOrWhiteSpace() == false) { - if (_mediaFileManager.FileSystem.FileExists(filePath)) - _mediaFileManager.FileSystem.DeleteFile(filePath); + if (_mediaFileManager.FileSystem.FileExists(filePath!)) + _mediaFileManager.FileSystem.DeleteFile(filePath!); } return found.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator); @@ -237,7 +237,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// [OutgoingEditorModelEvent] [Authorize(Policy = AuthorizationPolicies.AdminUserEditsRequireAdmin)] - public ActionResult GetById(int id) + public ActionResult GetById(int id) { var user = _userService.GetUserById(id); if (user == null) @@ -255,7 +255,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// [OutgoingEditorModelEvent] [Authorize(Policy = AuthorizationPolicies.AdminUserEditsRequireAdmin)] - public ActionResult> GetByIds([FromJsonPath]int[] ids) + public ActionResult> GetByIds([FromJsonPath]int[] ids) { if (ids == null) { @@ -291,8 +291,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers int pageSize = 10, string orderBy = "username", Direction orderDirection = Direction.Ascending, - [FromQuery]string[] userGroups = null, - [FromQuery]UserState[] userStates = null, + [FromQuery]string[]? userGroups = null, + [FromQuery]UserState[]? userStates = null, string filter = "") { //following the same principle we had in previous versions, we would only show admins to admins, see @@ -302,7 +302,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var hideDisabledUsers = _securitySettings.HideDisabledUsersInBackOffice; var excludeUserGroups = new string[0]; - var isAdmin = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsAdmin(); + var isAdmin = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.IsAdmin(); if (isAdmin == false) { //this user is not an admin so in that case we need to exclude all admin users @@ -311,7 +311,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var filterQuery = _sqlContext.Query(); - if (!_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.IsSuper()) + if (!_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.IsSuper() ?? false) { // only super can see super - but don't use IsSuper, cannot be mapped to SQL //filterQuery.Where(x => !x.IsSuper()); @@ -320,7 +320,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (filter.IsNullOrWhiteSpace() == false) { - filterQuery.Where(x => x.Name.Contains(filter) || x.Username.Contains(filter)); + filterQuery.Where(x => x.Name!.Contains(filter) || x.Username.Contains(filter)); } if (hideDisabledUsers) @@ -337,7 +337,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var paged = new PagedUserResult(total, pageNumber, pageSize) { - Items = _umbracoMapper.MapEnumerable(result), + Items = _umbracoMapper.MapEnumerable(result).WhereNotNull(), UserStates = _userService.GetUserStates() }; @@ -349,7 +349,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - public async Task> PostCreateUser(UserInvite userSave) + public async Task> PostCreateUser(UserInvite userSave) { if (userSave == null) throw new ArgumentNullException("userSave"); @@ -376,7 +376,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } //Perform authorization here to see if the current user can actually save this user with the info being requested - var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, null, null, null, userSave.UserGroups); + var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, null, null, null, userSave.UserGroups); if (canSaveUser == false) { return Unauthorized(canSaveUser.Result); @@ -410,13 +410,21 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers //map the save info over onto the user user = _umbracoMapper.Map(userSave, user); - //since the back office user is creating this user, they will be set to approved - user.IsApproved = true; + if (user is not null) + { + // Since the back office user is creating this user, they will be set to approved + user.IsApproved = true; - _userService.Save(user); + _userService.Save(user); + } var display = _umbracoMapper.Map(user); - display.ResetPasswordValue = resetPassword; + + if (display is not null) + { + display.ResetPasswordValue = resetPassword; + } + return display; } @@ -428,7 +436,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// This will email the user an invite and generate a token that will be validated in the email /// - public async Task> PostInviteUser(UserInvite userSave) + public async Task> PostInviteUser(UserInvite userSave) { if (userSave == null) { @@ -448,14 +456,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers else { // first validate the username if we're showing it - ActionResult userResult = CheckUniqueUsername(userSave.Username, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); + ActionResult userResult = CheckUniqueUsername(userSave.Username, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); if (userResult.Result is not null) { return userResult.Result; } } - IUser user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); + IUser? user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); if (ModelState.IsValid == false) { @@ -468,7 +476,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } // Perform authorization here to see if the current user can actually save this user with the info being requested - var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, user, null, null, userSave.UserGroups); + var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, user, null, null, userSave.UserGroups); if (canSaveUser == false) { return ValidationProblem(canSaveUser.Result, StatusCodes.Status401Unauthorized); @@ -494,21 +502,25 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers // map the save info over onto the user user = _umbracoMapper.Map(userSave, user); - // ensure the invited date is set - user.InvitedDate = DateTime.Now; + if (user is not null) + { + // ensure the invited date is set + user.InvitedDate = DateTime.Now; + + // Save the updated user (which will process the user groups too) + _userService.Save(user); + } - // Save the updated user (which will process the user groups too) - _userService.Save(user); var display = _umbracoMapper.Map(user); // send the email - await SendUserInviteEmailAsync(display, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Email, user, userSave.Message); + await SendUserInviteEmailAsync(display, _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Name, _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Email, user, userSave.Message); - display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles","resendInviteHeader"), _localizedTextService.Localize("speechBubbles","resendInviteSuccess", new[] { user.Name })); + display?.AddSuccessNotification(_localizedTextService.Localize("speechBubbles","resendInviteHeader"), _localizedTextService.Localize("speechBubbles","resendInviteSuccess", new[] { user?.Name })); return display; } - private IUser CheckUniqueEmail(string email, Func extraCheck) + private IUser? CheckUniqueEmail(string email, Func? extraCheck) { var user = _userService.GetByEmail(email); if (user != null && (extraCheck == null || extraCheck(user))) @@ -518,7 +530,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return user; } - private ActionResult CheckUniqueUsername(string username, Func extraCheck) + private ActionResult CheckUniqueUsername(string? username, Func? extraCheck) { var user = _userService.GetByUsername(username); if (user != null && (extraCheck == null || extraCheck(user))) @@ -529,19 +541,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return ValidationProblem(ModelState); } - return new ActionResult(user); + return new ActionResult(user); } - private async Task SendUserInviteEmailAsync(UserBasic userDisplay, string from, string fromEmail, IUser to, string message) + private async Task SendUserInviteEmailAsync(UserBasic? userDisplay, string? from, string? fromEmail, IUser? to, string? message) { - var user = await _userManager.FindByIdAsync(((int) userDisplay.Id).ToString()); + var user = await _userManager.FindByIdAsync(((int?) userDisplay?.Id).ToString()); var token = await _userManager.GenerateEmailConfirmationTokenAsync(user); // Use info from SMTP Settings if configured, otherwise set fromEmail as fallback var senderEmail = !string.IsNullOrEmpty(_globalSettings.Smtp?.From) ? _globalSettings.Smtp.From : fromEmail; var inviteToken = string.Format("{0}{1}{2}", - (int)userDisplay.Id, + (int?)userDisplay?.Id, WebUtility.UrlEncode("|"), token.ToUrlBase64()); @@ -561,16 +573,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var emailSubject = _localizedTextService.Localize("user","inviteEmailCopySubject", // Ensure the culture of the found user is used for the email! - UmbracoUserExtensions.GetUserCulture(to.Language, _localizedTextService, _globalSettings)); + UmbracoUserExtensions.GetUserCulture(to?.Language, _localizedTextService, _globalSettings)); var emailBody = _localizedTextService.Localize("user","inviteEmailCopyFormat", // Ensure the culture of the found user is used for the email! - UmbracoUserExtensions.GetUserCulture(to.Language, _localizedTextService, _globalSettings), - new[] { userDisplay.Name, from, message, inviteUri.ToString(), senderEmail }); + UmbracoUserExtensions.GetUserCulture(to?.Language, _localizedTextService, _globalSettings), + new[] { userDisplay?.Name, from, message, inviteUri.ToString(), senderEmail }); // This needs to be in the correct mailto format including the name, else // the name cannot be captured in the email sending notification. // i.e. "Some Person" - var toMailBoxAddress = new MailboxAddress(to.Name, to.Email); + var toMailBoxAddress = new MailboxAddress(to?.Name, to?.Email); var mailMessage = new EmailMessage(senderEmail, toMailBoxAddress.ToString(), emailSubject, emailBody, true); @@ -583,7 +595,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// [OutgoingEditorModelEvent] - public ActionResult PostSaveUser(UserSave userSave) + public ActionResult PostSaveUser(UserSave userSave) { if (userSave == null) throw new ArgumentNullException(nameof(userSave)); @@ -597,7 +609,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return NotFound(); //Perform authorization here to see if the current user can actually save this user with the info being requested - var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, found, userSave.StartContentIds, userSave.StartMediaIds, userSave.UserGroups); + var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, found, userSave.StartContentIds, userSave.StartMediaIds, userSave.UserGroups); if (canSaveUser == false) { return Unauthorized(canSaveUser.Result); @@ -657,15 +669,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var display = _umbracoMapper.Map(user); // determine if the user has changed their own language; - var currentUser = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser; + var currentUser = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; var userHasChangedOwnLanguage = - user.Id == currentUser.Id && currentUser.Language != user.Language; + user.Id == currentUser?.Id && currentUser.Language != user.Language; var textToLocalise = userHasChangedOwnLanguage ? "operationSavedHeaderReloadUser" : "operationSavedHeader"; var culture = userHasChangedOwnLanguage - ? CultureInfo.GetCultureInfo(user.Language) + ? CultureInfo.GetCultureInfo(user.Language!) : Thread.CurrentThread.CurrentUICulture; - display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles", textToLocalise, culture), _localizedTextService.Localize("speechBubbles","editUserSaved", culture)); + display?.AddSuccessNotification(_localizedTextService.Localize("speechBubbles", textToLocalise, culture), _localizedTextService.Localize("speechBubbles","editUserSaved", culture)); return display; } @@ -674,7 +686,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - public async Task>> PostChangePassword(ChangingPasswordModel changingPasswordModel) + public async Task>> PostChangePassword(ChangingPasswordModel changingPasswordModel) { changingPasswordModel = changingPasswordModel ?? throw new ArgumentNullException(nameof(changingPasswordModel)); @@ -683,37 +695,40 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return ValidationProblem(ModelState); } - IUser found = _userService.GetUserById(changingPasswordModel.Id); + IUser? found = _userService.GetUserById(changingPasswordModel.Id); if (found == null) { return NotFound(); } - IUser currentUser = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser; + IUser? currentUser = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; // if it's the current user, the current user cannot reset their own password without providing their old password - if (currentUser.Username == found.Username && string.IsNullOrEmpty(changingPasswordModel.OldPassword)) + if (currentUser?.Username == found.Username && string.IsNullOrEmpty(changingPasswordModel.OldPassword)) { return ValidationProblem("Password reset is not allowed without providing old password"); } - if (!currentUser.IsAdmin() && found.IsAdmin()) + if ((!currentUser?.IsAdmin() ?? false) && found.IsAdmin()) { return ValidationProblem("The current user cannot change the password for the specified user"); } - Attempt passwordChangeResult = await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _userManager); + Attempt passwordChangeResult = await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _userManager); if (passwordChangeResult.Success) { - var result = new ModelWithNotifications(passwordChangeResult.Result.ResetPassword); + var result = new ModelWithNotifications(passwordChangeResult.Result?.ResetPassword); result.AddSuccessNotification(_localizedTextService.Localize("general","success"), _localizedTextService.Localize("user","passwordChangedGeneric")); return result; } - foreach (string memberName in passwordChangeResult.Result.ChangeError.MemberNames) + if (passwordChangeResult.Result?.ChangeError is not null) { - ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage); + foreach (string memberName in passwordChangeResult.Result.ChangeError.MemberNames) + { + ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage ?? string.Empty); + } } return ValidationProblem(ModelState); @@ -727,8 +742,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [Authorize(Policy = AuthorizationPolicies.AdminUserEditsRequireAdmin)] public IActionResult PostDisableUsers([FromQuery]int[] userIds) { - var tryGetCurrentUserId = _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId(); - if (tryGetCurrentUserId.Success && userIds.Contains(tryGetCurrentUserId.Result.Value)) + var tryGetCurrentUserId = _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId() ?? Attempt.Fail(); + if (tryGetCurrentUserId.Success && userIds.Contains(tryGetCurrentUserId.Result!.Value)) { return ValidationProblem("The current user cannot disable itself"); } diff --git a/src/Umbraco.Web.Common/ActionsResults/ValidationErrorResult.cs b/src/Umbraco.Web.Common/ActionsResults/ValidationErrorResult.cs index 378be18440..3207272b66 100644 --- a/src/Umbraco.Web.Common/ActionsResults/ValidationErrorResult.cs +++ b/src/Umbraco.Web.Common/ActionsResults/ValidationErrorResult.cs @@ -40,12 +40,12 @@ namespace Umbraco.Cms.Web.Common.ActionsResults public ValidationErrorResult(ModelStateDictionary modelState) : this(new SimpleValidationModel(modelState.ToErrorDictionary())) { } - public ValidationErrorResult(object value, int statusCode) : base(value) + public ValidationErrorResult(object? value, int statusCode) : base(value) { StatusCode = statusCode; } - public ValidationErrorResult(object value) : this(value, StatusCodes.Status400BadRequest) + public ValidationErrorResult(object? value) : this(value, StatusCodes.Status400BadRequest) { }