From c478945373e88aed3b51cc03a7a2d794d4be438e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 14 Aug 2020 12:14:35 +0200 Subject: [PATCH 01/63] Ability to put property label on top of editor --- .../PropertyEditors/DataValueEditor.cs | 6 ++++++ .../PropertyEditors/IDataValueEditor.cs | 5 +++++ src/Umbraco.Web.UI.Client/src/less/main.less | 20 ++++++++++++++++++- .../components/property/umb-property.html | 2 +- .../ContentEditing/ContentPropertyDisplay.cs | 3 +++ .../Mapping/ContentPropertyDisplayMapper.cs | 1 + .../BlockEditorPropertyEditor.cs | 19 +++++++++++++++++- .../PropertyEditors/BlockListConfiguration.cs | 3 +++ 8 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs index c4380f032c..51b6fbde76 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs @@ -133,6 +133,12 @@ namespace Umbraco.Core.PropertyEditors [JsonProperty("hideLabel")] public bool HideLabel { get; set; } + /// + /// If this is true the associated label will be placed on top to provide full width for the editor. + /// + [JsonProperty("labelOnTop")] + public bool LabelOnTop { get; set; } + /// /// Set this to true if the property editor is for display purposes only /// diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs index a02fa71ec7..f9e82d8bd0 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs @@ -35,6 +35,11 @@ namespace Umbraco.Core.PropertyEditors /// bool HideLabel { get; } + /// + /// Gets a value indicating whether to display the associated label on top of the controls. + /// + bool LabelOnTop { get; } + /// /// Validates a property value. /// diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 2354e96d38..1791c26d5b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -238,9 +238,27 @@ umb-property:last-of-type .umb-control-group { .form-horizontal .umb-control-group .control-header { float: none; width: 100%; + &::after { + content: ""; + display: table; + clear: both; + } } - +} +.form-horizontal .umb-control-group.--label-on-top > .umb-el-wrap { + & > .control-header { + float: none; + width: 100%; + &::after { + content: ""; + display: table; + clear: both; + } + } + & > .controls { + margin-left: 0; + } } /* LABELS*/ diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index 14ca023046..45107748f0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -1,7 +1,7 @@
+ ng-class="{'hidelabel':vm.property.hideLabel, '--label-on-top':vm.property.labelOnTop, 'umb-control-group__listview': vm.property.alias === '_umb_containerView'}"> diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs index 39a4718dd0..9a07d29a02 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs @@ -33,6 +33,9 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "hideLabel")] public bool HideLabel { get; set; } + [DataMember(Name = "labelOnTop")] + public bool LabelOnTop { get; set; } + [DataMember(Name = "validation")] public PropertyTypeValidation Validation { get; set; } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs index d81f81abe2..22d85c1aca 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs @@ -39,6 +39,7 @@ namespace Umbraco.Web.Models.Mapping dest.Description = originalProp.PropertyType.Description; dest.Label = originalProp.PropertyType.Name; dest.HideLabel = valEditor.HideLabel; + dest.LabelOnTop = valEditor.LabelOnTop; //add the validation information dest.Validation.Mandatory = originalProp.PropertyType.Mandatory; diff --git a/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs index 18ea3e1e9d..b99ff06223 100644 --- a/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs @@ -46,7 +46,7 @@ namespace Umbraco.Web.PropertyEditors internal class BlockEditorPropertyValueEditor : DataValueEditor, IDataValueReference { private readonly PropertyEditorCollection _propertyEditors; - private readonly IDataTypeService _dataTypeService; + private readonly IDataTypeService _dataTypeService; private readonly ILogger _logger; private readonly BlockEditorValues _blockEditorValues; @@ -91,6 +91,23 @@ namespace Umbraco.Web.PropertyEditors return result; } + + /// + public override object Configuration + { + get => base.Configuration; + set + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + if (!(value is BlockListConfiguration configuration)) + throw new ArgumentException($"Expected a {typeof(BlockListConfiguration).Name}, but got {value.GetType().Name}.", nameof(value)); + base.Configuration = value; + + LabelOnTop = configuration.LabelOnTop.TryConvertTo().Result; + } + } + #region Convert database // editor // note: there is NO variant support here diff --git a/src/Umbraco.Web/PropertyEditors/BlockListConfiguration.cs b/src/Umbraco.Web/PropertyEditors/BlockListConfiguration.cs index e461da40dc..11abca529d 100644 --- a/src/Umbraco.Web/PropertyEditors/BlockListConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/BlockListConfiguration.cs @@ -70,6 +70,9 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("maxPropertyWidth", "Property editor width", "textstring", Description = "optional css overwrite, example: 800px or 100%")] public string MaxPropertyWidth { get; set; } + [ConfigurationField("labelOnTop", "Label on top", "boolean", Description = "Move the property label on top to provide move space for the editor.")] + public bool LabelOnTop { get; set; } + } } From 41359a6544424656ef12c9525640c5b454ae7b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 14 Aug 2020 14:55:31 +0200 Subject: [PATCH 02/63] adjust spacing --- src/Umbraco.Web.UI.Client/src/less/main.less | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 1791c26d5b..45d61c167e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -221,6 +221,7 @@ umb-property:last-of-type .umb-control-group { padding-top: 5px; padding-bottom: 0; text-align: left; + margin-bottom: 5px; .control-label { width: auto; @@ -230,7 +231,7 @@ umb-property:last-of-type .umb-control-group { .control-description { max-width:480px;// avoiding description becoming too wide when its placed on top of property. - margin-bottom: 10px; + margin-bottom: 5px; } } @media (max-width: 767px) { From d14e9ee3b422d3a52773c44324f448d3510c8952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 1 Oct 2020 17:29:52 +0200 Subject: [PATCH 03/63] revert POC approach --- .../PropertyEditors/DataValueEditor.cs | 8 +------- .../PropertyEditors/IDataValueEditor.cs | 5 ----- .../BlockEditorPropertyEditor.cs | 17 ----------------- 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs index 51b6fbde76..eebe5f5722 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs @@ -133,12 +133,6 @@ namespace Umbraco.Core.PropertyEditors [JsonProperty("hideLabel")] public bool HideLabel { get; set; } - /// - /// If this is true the associated label will be placed on top to provide full width for the editor. - /// - [JsonProperty("labelOnTop")] - public bool LabelOnTop { get; set; } - /// /// Set this to true if the property editor is for display purposes only /// @@ -210,7 +204,7 @@ namespace Umbraco.Core.PropertyEditors /// /// /// By default this will attempt to automatically convert the string value to the value type supplied by ValueType. - /// + /// /// If overridden then the object returned must match the type supplied in the ValueType, otherwise persisting the /// value to the DB will fail when it tries to validate the value type. /// diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs index f9e82d8bd0..a02fa71ec7 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs @@ -35,11 +35,6 @@ namespace Umbraco.Core.PropertyEditors /// bool HideLabel { get; } - /// - /// Gets a value indicating whether to display the associated label on top of the controls. - /// - bool LabelOnTop { get; } - /// /// Validates a property value. /// diff --git a/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs index b99ff06223..8c4ebf49c3 100644 --- a/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs @@ -91,23 +91,6 @@ namespace Umbraco.Web.PropertyEditors return result; } - - /// - public override object Configuration - { - get => base.Configuration; - set - { - if (value == null) - throw new ArgumentNullException(nameof(value)); - if (!(value is BlockListConfiguration configuration)) - throw new ArgumentException($"Expected a {typeof(BlockListConfiguration).Name}, but got {value.GetType().Name}.", nameof(value)); - base.Configuration = value; - - LabelOnTop = configuration.LabelOnTop.TryConvertTo().Result; - } - } - #region Convert database // editor // note: there is NO variant support here From c264cc9691e637f13db9b1835343e67ab81a7b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 1 Oct 2020 17:30:03 +0200 Subject: [PATCH 04/63] configure label position on Property level --- src/Umbraco.Core/Models/PropertyType.cs | 13 ++++++- .../components/umbgroupsbuilder.directive.js | 7 ++-- .../services/umbdataformatter.service.js | 6 ++-- .../propertysettings.controller.js | 36 +++++++++++-------- .../propertysettings/propertysettings.html | 15 ++++++++ src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 2 ++ src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 4 ++- .../Umbraco/config/lang/en_us.xml | 2 ++ .../ContentEditing/PropertyTypeBasic.cs | 3 ++ .../Mapping/ContentPropertyDisplayMapper.cs | 2 +- .../Mapping/ContentTypeMapDefinition.cs | 3 ++ 11 files changed, 69 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 75c2a7cc00..abdbbfd8db 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -31,6 +31,7 @@ namespace Umbraco.Core.Models private string _validationRegExp; private string _validationRegExpMessage; private ContentVariation _variations; + private bool _labelOnTop; /// /// Initializes a new instance of the class. @@ -205,6 +206,16 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _mandatoryMessage, nameof(MandatoryMessage)); } + /// + /// Gets or sets a value indicating whether the label of this property type should be displayed on top. + /// + [DataMember] + public bool LabelOnTop + { + get => _labelOnTop; + set => SetPropertyValueAndDetectChanges(value, ref _labelOnTop, nameof(LabelOnTop)); + } + /// /// Gets of sets the sort order of the property type. /// @@ -438,7 +449,7 @@ namespace Umbraco.Core.Models base.PerformDeepClone(clone); var clonedEntity = (PropertyType)clone; - + //need to manually assign the Lazy value as it will not be automatically mapped if (PropertyGroupId != null) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index 92d2df43e5..5b3468bcfe 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -323,12 +323,12 @@ filterAvailableCompositions(selectedContentType, newSelection).then(function () { deferred.resolve({ selectedContentType, newSelection }); // TODO: Here we could probably re-enable selection if we previously showed a throbber or something - }, function () { + }, function () { deferred.reject(); }); } - return deferred.promise; + return deferred.promise; } }; @@ -353,7 +353,7 @@ }), //get where used document types whereUsedContentTypeResource(scope.model.id).then(function (whereUsed) { - //pass to the dialog model the content type eg documentType or mediaType + //pass to the dialog model the content type eg documentType or mediaType scope.compositionsDialogModel.section = scope.contentType; //pass the list of 'where used' document types scope.compositionsDialogModel.whereCompositionUsed = whereUsed; @@ -566,6 +566,7 @@ property.isSensitiveValue = propertyModel.isSensitiveValue; property.allowCultureVariant = propertyModel.allowCultureVariant; property.allowSegmentVariant = propertyModel.allowSegmentVariant; + property.labelOnTop = propertyModel.labelOnTop; // update existing data types if (model.updateSameDataTypes) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index 109fff0919..c646c4833f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -83,7 +83,7 @@ }); var saveProperties = _.map(realProperties, function (p) { - var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData', 'allowCultureVariant', 'allowSegmentVariant'); + var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData', 'allowCultureVariant', 'allowSegmentVariant', 'labelOnTop'); return saveProperty; }); @@ -404,7 +404,7 @@ if (displayModel.variants && displayModel.variants.length > 1) { // Collect all invariant properties from the variants that are either the // default language variant or the default segment variant. - var defaultVariants = _.filter(displayModel.variants, function (variant) { + var defaultVariants = _.filter(displayModel.variants, function (variant) { var isDefaultLanguage = variant.language && variant.language.isDefault; var isDefaultSegment = variant.segment == null; @@ -433,7 +433,7 @@ return variant !== defaultVariant; }); - // now assign this same invariant property instance to the same index of the other variants property array + // now assign this same invariant property instance to the same index of the other variants property array _.each(otherVariants, function (variant) { _.each(invariantProps, function (invProp) { var tab = variant.tabs[invProp.tabIndex]; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js index 6310545b20..3c09745f15 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js @@ -35,6 +35,7 @@ vm.toggleShowOnMemberProfile = toggleShowOnMemberProfile; vm.toggleMemberCanEdit = toggleMemberCanEdit; vm.toggleIsSensitiveData = toggleIsSensitiveData; + vm.toggleLabelOnTop = toggleLabelOnTop; function onInit() { @@ -42,23 +43,24 @@ vm.showSensitiveData = user.userGroups.indexOf("sensitiveData") != -1; }); - //make the default the same as the content type + //make the default the same as the content type if (!$scope.model.property.dataTypeId) { $scope.model.property.allowCultureVariant = $scope.model.contentTypeAllowCultureVariant; } - + loadValidationTypes(); - + } function loadValidationTypes() { var labels = [ - "validation_validateAsEmail", - "validation_validateAsNumber", - "validation_validateAsUrl", + "validation_validateAsEmail", + "validation_validateAsNumber", + "validation_validateAsUrl", "validation_enterCustomValidation", - "validation_fieldIsMandatory" + "validation_fieldIsMandatory", + "contentTypeEditor_displaySettingsLabelOnTop" ]; localizationService.localizeMany(labels) @@ -69,6 +71,7 @@ vm.labels.validateAsUrl = data[2]; vm.labels.customValidation = data[3]; vm.labels.fieldIsMandatory = data[4]; + vm.labels.displaySettingsLabelOnTop = data[5]; vm.validationTypes = [ { @@ -121,7 +124,7 @@ $scope.model.updateSameDataTypes = model.updateSameDataTypes; vm.focusOnMandatoryField = true; - + // update property property.config = model.property.config; property.editor = model.property.editor; @@ -179,7 +182,7 @@ if(event && event.keyCode === 13) { submit(); } - } + } function submit() { if($scope.model.submit) { @@ -245,28 +248,31 @@ return !settingValue; } - function toggleAllowCultureVariants() { + function toggleAllowCultureVariants() { $scope.model.property.allowCultureVariant = toggleValue($scope.model.property.allowCultureVariant); } - function toggleAllowSegmentVariants() { + function toggleAllowSegmentVariants() { $scope.model.property.allowSegmentVariant = toggleValue($scope.model.property.allowSegmentVariant); } function toggleValidation() { - $scope.model.property.validation.mandatory = toggleValue($scope.model.property.validation.mandatory); + $scope.model.property.validation.mandatory = toggleValue($scope.model.property.validation.mandatory); } function toggleShowOnMemberProfile() { - $scope.model.property.showOnMemberProfile = toggleValue($scope.model.property.showOnMemberProfile); + $scope.model.property.showOnMemberProfile = toggleValue($scope.model.property.showOnMemberProfile); } function toggleMemberCanEdit() { - $scope.model.property.memberCanEdit = toggleValue($scope.model.property.memberCanEdit); + $scope.model.property.memberCanEdit = toggleValue($scope.model.property.memberCanEdit); } function toggleIsSensitiveData() { - $scope.model.property.isSensitiveData = toggleValue($scope.model.property.isSensitiveData); + $scope.model.property.isSensitiveData = toggleValue($scope.model.property.isSensitiveData); + } + function toggleLabelOnTop() { + $scope.model.property.labelOnTop = toggleValue($scope.model.property.labelOnTop); } onInit(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index af9295f1ed..482345c3b3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -135,6 +135,21 @@ ng-if="vm.showValidationPattern" ng-keypress="vm.submitOnEnter($event)" /> +
+
+ +
+ + + +
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 52f4dc1392..c85399e091 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1437,6 +1437,8 @@ Mange hilsner fra Umbraco robotten En Element-type er tiltænkt brug i f.eks. Nested Content, ikke i indholdstræet. Dette benyttes ikke for en Element-type Du har lavet ændringer til denne egenskab. Er du sikker på at du vil kassere dem? + Visning + Flyt label over editoren Tilføj sprog diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 07cec3dff7..020b256c60 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -515,7 +515,7 @@ Select snippet This will delete the node and all its languages. If you only want to delete one language, you should unpublish the node in that language instead. %0%.]]> - %0% from the %1% group]]> + %0% from the %1% group]]> Yes, remove @@ -1695,6 +1695,8 @@ To manage your website, simply open the Umbraco back office and start adding con A document type cannot be changed to an Element type once it has been used to create one or more content items. This is not applicable for an Element type You have made changes to this property. Are you sure you want to discard them? + Appearance + Display label on top of editor. Add language diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index cdb2137e7b..d8833558e8 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1715,6 +1715,8 @@ To manage your website, simply open the Umbraco back office and start adding con A document type cannot be changed to an element type once it has been used to create one or more content items. This is not applicable for an element type You have made changes to this property. Are you sure you want to discard them? + Appearance + Display label on top of editor. Add language diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs index 4252a29567..793e4e391d 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs @@ -65,5 +65,8 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "allowSegmentVariant")] public bool AllowSegmentVariant { get; set; } + + [DataMember(Name = "labelOnTop")] + public bool LabelOnTop { get; set; } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs index 22d85c1aca..37fd2b2e28 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs @@ -39,7 +39,7 @@ namespace Umbraco.Web.Models.Mapping dest.Description = originalProp.PropertyType.Description; dest.Label = originalProp.PropertyType.Name; dest.HideLabel = valEditor.HideLabel; - dest.LabelOnTop = valEditor.LabelOnTop; + dest.LabelOnTop = originalProp.PropertyType.LabelOnTop; //add the validation information dest.Validation.Mandatory = originalProp.PropertyType.Mandatory; diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs index b0d4c6b2e0..f67a5df56c 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs @@ -242,6 +242,7 @@ namespace Umbraco.Web.Models.Mapping target.Alias = source.Alias; target.Description = source.Description; target.SortOrder = source.SortOrder; + target.LabelOnTop = source.LabelOnTop; } // no MapAll - take care @@ -353,6 +354,7 @@ namespace Umbraco.Web.Models.Mapping target.Label = source.Label; target.SortOrder = source.SortOrder; target.Validation = source.Validation; + target.LabelOnTop = source.LabelOnTop; } // Umbraco.Code.MapAll -Editor -View -Config -ContentTypeId -ContentTypeName -Locked -DataTypeIcon -DataTypeName @@ -373,6 +375,7 @@ namespace Umbraco.Web.Models.Mapping target.MemberCanViewProperty = source.MemberCanViewProperty; target.SortOrder = source.SortOrder; target.Validation = source.Validation; + target.LabelOnTop = source.LabelOnTop; } // Umbraco.Code.MapAll -CreatorId -Level -SortOrder -Variations From 433d9ac186cc2bbceb6af1ad85bc07ee60cb382f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 2 Oct 2020 09:49:25 +0200 Subject: [PATCH 05/63] init labelOnTop --- .../common/directives/components/umbgroupsbuilder.directive.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index 5b3468bcfe..06e1c61f1e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -648,7 +648,8 @@ mandatoryMessage: null, pattern: null, patternMessage: null - } + }, + labelOnTop: false }; // check if there already is an init property From e5ba2ca78cbad73e192f92965a5e0b7115846a2c Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 2 Oct 2020 11:36:50 +0100 Subject: [PATCH 06/63] Migration to add new DB column to PropertyTypeDto for LabelOnTop --- .../Migrations/Upgrade/UmbracoPlan.cs | 5 ++++- .../AddPropertyTypeLabelOnTopColumn.cs | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 03ba58d15e..f48622b8da 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -193,7 +193,10 @@ namespace Umbraco.Core.Migrations.Upgrade // to 8.7.0... To("{a78e3369-8ea3-40ec-ad3f-5f76929d2b20}"); - + + // to 8.10.0... + To("{D6A8D863-38EC-44FB-91EC-ACD6A668BD18}"); + //FINAL } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs new file mode 100644 index 0000000000..a9fcdd8f98 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs @@ -0,0 +1,20 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 +{ + + public class AddPropertyTypeLabelOnTopColumn : MigrationBase + { + public AddPropertyTypeLabelOnTopColumn(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList(); + + AddColumnIfNotExists(columns, "labelOnTop"); + } + } +} From 1b7f095048f0abac7f0d0ef3f47da8c8123f3234 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 2 Oct 2020 13:32:23 +0100 Subject: [PATCH 07/63] Trying to get the model mapping done correctly - but still stuggling. This can be persisited to DB correctly but does not hydrate model correctly back to a model --- .../Persistence/Dtos/PropertyTypeDto.cs | 4 ++++ .../Factories/PropertyGroupFactory.cs | 3 ++- .../Persistence/Mappers/PropertyTypeMapper.cs | 1 + src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Mapping/ContentTypeModelMappingTests.cs | 17 ++++++++++++----- .../Repositories/ContentTypeRepositoryTest.cs | 14 ++++++++++++-- .../TestHelpers/Entities/MockedContentTypes.cs | 6 +++--- .../Mapping/ContentPropertyMapDefinition.cs | 2 +- .../Models/Mapping/PropertyTypeGroupMapper.cs | 1 + 9 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs index 3e8d6e7496..572201c94a 100644 --- a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs @@ -62,6 +62,10 @@ namespace Umbraco.Core.Persistence.Dtos [Length(2000)] public string Description { get; set; } + [Column("labelOnTop")] + [Constraint(Default = "0")] + public bool LabelOnTop { get; set; } + [Column("variations")] [Constraint(Default = "1" /*ContentVariation.InvariantNeutral*/)] public byte Variations { get; set; } diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs index dc1629e8f7..f139619302 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -132,7 +132,8 @@ namespace Umbraco.Core.Persistence.Factories ValidationRegExp = propertyType.ValidationRegExp, ValidationRegExpMessage = propertyType.ValidationRegExpMessage, UniqueId = propertyType.Key, - Variations = (byte)propertyType.Variations + Variations = (byte)propertyType.Variations, + LabelOnTop = propertyType.LabelOnTop }; if (tabId != default) diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs index 6f22b61f9a..d44618cc96 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs @@ -29,6 +29,7 @@ namespace Umbraco.Core.Persistence.Mappers DefineMap(nameof(PropertyType.SortOrder), nameof(PropertyTypeDto.SortOrder)); DefineMap(nameof(PropertyType.ValidationRegExp), nameof(PropertyTypeDto.ValidationRegExp)); DefineMap(nameof(PropertyType.ValidationRegExpMessage), nameof(PropertyTypeDto.ValidationRegExpMessage)); + DefineMap(nameof(PropertyType.LabelOnTop), nameof(PropertyTypeDto.LabelOnTop)); DefineMap(nameof(PropertyType.PropertyEditorAlias), nameof(DataTypeDto.EditorAlias)); DefineMap(nameof(PropertyType.ValueStorageType), nameof(DataTypeDto.DbType)); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index b7b9867618..e9d6d31955 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -132,6 +132,7 @@ + diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs index eee7446823..679f053c5c 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs @@ -215,6 +215,7 @@ namespace Umbraco.Tests.Models.Mapping { Assert.AreEqual(propTypes.ElementAt(j).Id, result.PropertyTypes.ElementAt(j).Id); Assert.AreEqual(propTypes.ElementAt(j).DataTypeId, result.PropertyTypes.ElementAt(j).DataTypeId); + Assert.AreEqual(propTypes.ElementAt(j).LabelOnTop, result.PropertyTypes.ElementAt(j).LabelOnTop); } } @@ -449,6 +450,7 @@ namespace Umbraco.Tests.Models.Mapping { Assert.AreEqual(propTypes[j].Id, result.Groups.ElementAt(i).Properties.ElementAt(j).Id); Assert.AreEqual(propTypes[j].DataTypeId, result.Groups.ElementAt(i).Properties.ElementAt(j).DataTypeId); + Assert.AreEqual(propTypes[j].LabelOnTop, result.Groups.ElementAt(i).Properties.ElementAt(j).LabelOnTop); } } @@ -1074,7 +1076,8 @@ namespace Umbraco.Tests.Models.Mapping Pattern = string.Empty }, SortOrder = 0, - DataTypeId = 555 + DataTypeId = 555, + LabelOnTop = true } } } @@ -1120,7 +1123,8 @@ namespace Umbraco.Tests.Models.Mapping Pattern = string.Empty }, SortOrder = 0, - DataTypeId = 555 + DataTypeId = 555, + LabelOnTop = true } } }, @@ -1144,7 +1148,8 @@ namespace Umbraco.Tests.Models.Mapping Pattern = string.Empty }, SortOrder = 0, - DataTypeId = 555 + DataTypeId = 555, + LabelOnTop = false } } @@ -1198,7 +1203,8 @@ namespace Umbraco.Tests.Models.Mapping Pattern = string.Empty }, SortOrder = 0, - DataTypeId = 555 + DataTypeId = 555, + LabelOnTop = true } } }, @@ -1222,7 +1228,8 @@ namespace Umbraco.Tests.Models.Mapping Pattern = string.Empty }, SortOrder = 0, - DataTypeId = 555 + DataTypeId = 555, + LabelOnTop = false } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index e592c5171a..970201bc62 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -348,6 +348,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(contentType.Path.Contains(","), Is.True); Assert.That(contentType.SortOrder, Is.GreaterThan(0)); + Assert.That(contentType.PropertyGroups.ElementAt(0).Name == "testGroup", Is.True); var groupId = contentType.PropertyGroups.ElementAt(0).Id; @@ -355,6 +356,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual("gen", propertyTypes[0].Alias); // just to be sure Assert.IsNull(propertyTypes[0].PropertyGroupId); Assert.IsTrue(propertyTypes.Skip(1).All((x => x.PropertyGroupId.Value == groupId))); + Assert.That(propertyTypes[0].LabelOnTop, Is.True); } } @@ -377,7 +379,8 @@ namespace Umbraco.Tests.Persistence.Repositories Description = "Optional Subtitle", Mandatory = false, SortOrder = 1, - DataTypeId = -88 + DataTypeId = -88, + LabelOnTop = true }); repository.Save(contentType); @@ -389,6 +392,8 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(dirty, Is.False); Assert.That(contentType.Thumbnail, Is.EqualTo("Doc2.png")); Assert.That(contentType.PropertyTypes.Any(x => x.Alias == "subtitle"), Is.True); + Assert.That(contentType.PropertyTypes.Single(x => x.Alias == "subtitle").LabelOnTop, Is.True); + } @@ -467,7 +472,8 @@ namespace Umbraco.Tests.Persistence.Repositories Pattern = "" }, SortOrder = 1, - DataTypeId = -88 + DataTypeId = -88, + LabelOnTop = true } }); @@ -476,6 +482,7 @@ namespace Umbraco.Tests.Persistence.Repositories // just making sure Assert.AreEqual(mapped.Thumbnail, "Doc2.png"); Assert.IsTrue(mapped.PropertyTypes.Any(x => x.Alias == "subtitle")); + Assert.IsTrue(mapped.PropertyTypes.Single(x => x.Alias == "subtitle").LabelOnTop); repository.Save(mapped); @@ -490,6 +497,9 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(dirty, Is.False); Assert.That(contentType.Thumbnail, Is.EqualTo("Doc2.png")); Assert.That(contentType.PropertyTypes.Any(x => x.Alias == "subtitle"), Is.True); + + Assert.That(contentType.PropertyTypes.Single(x => x.Alias == "subtitle").LabelOnTop, Is.True); + foreach (var propertyType in contentType.PropertyTypes) { Assert.IsTrue(propertyType.HasIdentity); diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index b4cd4ab05e..e3bb012dae 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -41,8 +41,8 @@ namespace Umbraco.Tests.TestHelpers.Entities }; var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = Constants.DataTypes.Textbox }); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.DataTypes.RichtextEditor }); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = Constants.DataTypes.Textbox, LabelOnTop = true }); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.DataTypes.RichtextEditor, LabelOnTop = false }); var metaCollection = new PropertyTypeCollection(true); metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "keywords", Name = "Meta Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = Constants.DataTypes.Textbox }); @@ -213,7 +213,7 @@ namespace Umbraco.Tests.TestHelpers.Entities contentType.Trashed = false; var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("title", randomizeAliases), Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("title", randomizeAliases), Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88, LabelOnTop = true }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TinyMce, ValueStorageType.Ntext) { Alias = RandomAlias("bodyText", randomizeAliases), Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("author", randomizeAliases) , Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeId = -88 }); diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs index e6290cc19e..baa9b7be69 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs @@ -38,7 +38,7 @@ namespace Umbraco.Web.Models.Mapping target.Id = source.Id; target.IsActive = true; target.Label = source.Name; -} + } private void Map(Property source, ContentPropertyBasic target, MapperContext context) { diff --git a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs index adee6c6928..33fce221b4 100644 --- a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs @@ -222,6 +222,7 @@ namespace Umbraco.Web.Models.Mapping Id = p.Id, Alias = p.Alias, Description = p.Description, + LabelOnTop = p.LabelOnTop, Editor = p.PropertyEditorAlias, Validation = new PropertyTypeValidation { From 7e34dda1311ce027117f85b29802dcfad67a64c9 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 2 Oct 2020 15:08:24 +0100 Subject: [PATCH 08/63] Trying more things & prep for dealing with package XML for Label On Top --- src/Umbraco.Core/Packaging/PackageDataInstallation.cs | 5 ++++- src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs | 3 +++ src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs | 1 + src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs | 2 ++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs index 6a5acb0dc7..fac1a1d88f 100644 --- a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs @@ -798,7 +798,10 @@ namespace Umbraco.Core.Packaging SortOrder = sortOrder, Variations = property.Element("Variations") != null ? (ContentVariation)Enum.Parse(typeof(ContentVariation), property.Element("Variations").Value) - : ContentVariation.Nothing + : ContentVariation.Nothing, + LabelOnTop = property.Element("LabelOnTop") != null + ? property.Element("LabelOnTop").Value.ToLowerInvariant().Equals("true") + : false }; var tab = (string)property.Element("Tab"); diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs index 4c352a0134..d2001c00d5 100644 --- a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs @@ -44,6 +44,9 @@ namespace Umbraco.Core.Persistence.Dtos [Column("Description")] public string Description { get; set; } + [Column("labelOnTop")] + public bool LabelOnTop { get; set; } + /* cmsMemberType */ [Column("memberCanEdit")] public bool CanEdit { get; set; } diff --git a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs index 5189b3422e..cc329802ee 100644 --- a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs @@ -363,6 +363,7 @@ namespace Umbraco.Core.Services.Implement new XElement("MandatoryMessage", propertyType.MandatoryMessage), new XElement("Validation", propertyType.ValidationRegExp), new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage), + new XElement("LabelOnTop", propertyType.LabelOnTop), new XElement("Description", new XCData(propertyType.Description))); genericProperties.Add(genericProperty); } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs index 83e5e2a9b2..9702202294 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs @@ -18,6 +18,8 @@ namespace Umbraco.Web.Models.ContentEditing public bool IsRequired { get; set; } + public bool LabelOnTop { get; set; } + public string IsRequiredMessage { get; set; } public string ValidationRegExp { get; set; } From cf7b7a7ed1bb155e67a384d1320a3c897e6ebb8d Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 7 Oct 2020 20:20:13 +0100 Subject: [PATCH 09/63] More mappings - this seems to have fixed it. Got to update tests & verify export/import doctype to UDA & package.xml export/import --- .../Repositories/Implement/ContentTypeCommonRepository.cs | 3 ++- src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs index 7781e2e38a..90774e4c0b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -303,7 +303,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement SortOrder = dto.SortOrder, ValidationRegExp = dto.ValidationRegExp, ValidationRegExpMessage = dto.ValidationRegExpMessage, - Variations = (ContentVariation)dto.Variations + Variations = (ContentVariation)dto.Variations, + LabelOnTop = dto.LabelOnTop }; } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs index fd57e4dd2e..64e2ea22a3 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs @@ -27,6 +27,7 @@ namespace Umbraco.Web.Models.Mapping dest.Description = property.PropertyType.Description; dest.Label = property.PropertyType.Name; dest.DataType = DataTypeService.GetDataType(property.PropertyType.DataTypeId); + dest.LabelOnTop = property.PropertyType.LabelOnTop; } } } From fe929b95fdaaf9895b1b6e5998dbb2ae8c5a7dbc Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 7 Oct 2020 20:21:12 +0100 Subject: [PATCH 10/63] When generating XML for UDA & or package.xml ensure LabelOnTop prop is added --- src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs index cc329802ee..441b9b25f3 100644 --- a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs @@ -490,6 +490,7 @@ namespace Umbraco.Core.Services.Implement new XElement("Tab", propertyGroup == null ? "" : propertyGroup.Name), new XElement("SortOrder", propertyType.SortOrder), new XElement("Mandatory", propertyType.Mandatory.ToString()), + new XElement("LabelOnTop", propertyType.LabelOnTop.ToString()), propertyType.MandatoryMessage != null ? new XElement("MandatoryMessage", propertyType.MandatoryMessage) : null, propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : null, propertyType.ValidationRegExpMessage != null ? new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage) : null, From f569200272235352abb8283c5770436a64225f60 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 7 Oct 2020 21:20:53 +0100 Subject: [PATCH 11/63] Fix & update tests --- .../Models/Mapping/ContentTypeModelMappingTests.cs | 2 ++ .../Persistence/Repositories/ContentTypeRepositoryTest.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs index 679f053c5c..1480253958 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs @@ -641,6 +641,7 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(basic.Validation.MandatoryMessage, result.MandatoryMessage); Assert.AreEqual(basic.Validation.Pattern, result.ValidationRegExp); Assert.AreEqual(basic.Validation.PatternMessage, result.ValidationRegExpMessage); + Assert.AreEqual(basic.LabelOnTop, result.LabelOnTop); } [Test] @@ -679,6 +680,7 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(basic.Validation.MandatoryMessage, result.MandatoryMessage); Assert.AreEqual(basic.Validation.Pattern, result.ValidationRegExp); Assert.AreEqual(basic.Validation.PatternMessage, result.ValidationRegExpMessage); + Assert.AreEqual(basic.LabelOnTop, result.LabelOnTop); } [Test] diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 970201bc62..e8fc94e962 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -356,7 +356,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual("gen", propertyTypes[0].Alias); // just to be sure Assert.IsNull(propertyTypes[0].PropertyGroupId); Assert.IsTrue(propertyTypes.Skip(1).All((x => x.PropertyGroupId.Value == groupId))); - Assert.That(propertyTypes[0].LabelOnTop, Is.True); + Assert.That(propertyTypes.Single(x=> x.Alias == "title").LabelOnTop, Is.True); } } From 8ac0122e203d6e473900b1d66e44518a3105b71c Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 23 Oct 2020 15:52:40 +0100 Subject: [PATCH 12/63] Add migration to marker file & background reporter component to POST to telemetry service --- src/Umbraco.Core/IO/SystemFiles.cs | 2 + .../Migrations/Upgrade/UmbracoPlan.cs | 6 +- .../V_8_10_0/SetupAnonInstallTracker.cs | 49 ++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 109 ++++++++++++++++++ .../Telemetry/TelemetryComponent.cs | 35 ++++++ .../Telemetry/TelemetryComposer.cs | 10 ++ src/Umbraco.Web/Umbraco.Web.csproj | 3 + 8 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/SetupAnonInstallTracker.cs create mode 100644 src/Umbraco.Web/Telemetry/ReportSiteTask.cs create mode 100644 src/Umbraco.Web/Telemetry/TelemetryComponent.cs create mode 100644 src/Umbraco.Web/Telemetry/TelemetryComposer.cs diff --git a/src/Umbraco.Core/IO/SystemFiles.cs b/src/Umbraco.Core/IO/SystemFiles.cs index 12e3f57d99..5e38737dd2 100644 --- a/src/Umbraco.Core/IO/SystemFiles.cs +++ b/src/Umbraco.Core/IO/SystemFiles.cs @@ -7,6 +7,8 @@ namespace Umbraco.Core.IO { public static string TinyMceConfig => SystemDirectories.Config + "/tinyMceConfig.config"; + public static string TelemetricsIdentifier => SystemDirectories.Umbraco + "/telemetrics-id.umb"; + // TODO: Kill this off we don't have umbraco.config XML cache we now have NuCache public static string GetContentCacheXml(IGlobalSettings globalSettings) { diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 723675ca1a..f97fcda49c 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Migrations.Upgrade.Common; using Umbraco.Core.Migrations.Upgrade.V_8_0_0; using Umbraco.Core.Migrations.Upgrade.V_8_0_1; using Umbraco.Core.Migrations.Upgrade.V_8_1_0; +using Umbraco.Core.Migrations.Upgrade.V_8_10_0; using Umbraco.Core.Migrations.Upgrade.V_8_6_0; using Umbraco.Core.Migrations.Upgrade.V_8_8_0; @@ -197,7 +198,10 @@ namespace Umbraco.Core.Migrations.Upgrade // to 8.8.0 To("{B5838FF5-1D22-4F6C-BCEB-F83ACB14B575}"); - + + // to 8.10.0 + To("{DCBA2C6A-01B3-411E-9CDE-0AB9C69EFF33}"); + //FINAL } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/SetupAnonInstallTracker.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/SetupAnonInstallTracker.cs new file mode 100644 index 0000000000..6eded30312 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/SetupAnonInstallTracker.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; + + +namespace Umbraco.Core.Migrations.Upgrade.V_8_10_0 +{ + + public class SetupAnonInstallTracker : MigrationBase + { + public SetupAnonInstallTracker(IMigrationContext context) + : base(context) + { + } + + /// + /// Adds a new file 'telemetrics-id.umb' at /umbraco + /// Which will add a GUID inside the file as JSON + /// + public override void Migrate() + { + var telemetricsFilePath = IOHelper.MapPath(SystemFiles.TelemetricsIdentifier); + + // Verify file does not exist already + if (File.Exists(telemetricsFilePath)) + { + Logger.Warn("When migrating to 8.10.0 the anonymous telemetry file already existsed on disk at {filePath}", telemetricsFilePath); + return; + } + + // Generate GUID + var telemetrySiteIdentifier = Guid.NewGuid(); + + // Write file contents + try + { + File.WriteAllText(telemetricsFilePath, telemetrySiteIdentifier.ToString()); + } + catch (Exception ex) + { + Logger.Error(ex, "Unable to create telemetry file at {filePath}", telemetricsFilePath); + } + + Logger.Info("This site has been identified with an anynomous id {telemetrySiteId} for telemetrics and written to {filePath}", telemetrySiteIdentifier, telemetricsFilePath); + + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 71b2a7be4a..821ddb3dc9 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -132,6 +132,7 @@ + diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs new file mode 100644 index 0000000000..8d777b73db --- /dev/null +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -0,0 +1,109 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Threading; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Web.Scheduling; + +namespace Umbraco.Web.Telemetry +{ + public class ReportSiteTask : RecurringTaskBase + { + private IRuntimeState _runtime; + private IProfilingLogger _logger; + private static HttpClient _httpClient; + + public ReportSiteTask(IBackgroundTaskRunner runner, int delayBeforeWeStart, int howOftenWeRepeat, IRuntimeState runtime, IProfilingLogger logger) + : base(runner, delayBeforeWeStart, howOftenWeRepeat) + { + _runtime = runtime; + _logger = logger; + + if (_httpClient == null) + _httpClient = new HttpClient(); + } + + /// + /// Runs the background task to send the anynomous ID + /// to telemetry service + /// + /// A value indicating whether to repeat the task. + public override async Task PerformRunAsync(CancellationToken token) + { + // Try & find file at '/umbraco/telemetrics-id.umb' + var telemetricsFilePath = IOHelper.MapPath(SystemFiles.TelemetricsIdentifier); + + if (File.Exists(telemetricsFilePath) == false) + { + // Some users may have decided to not be tracked by deleting/removing the marker file + _logger.Warn("No telemetry marker file found at '{filePath}' and will not report site to telemetry service", telemetricsFilePath); + + // Stop repeating this task (no need to keep checking) + // The only time it will recheck when the site is recycled + return false; + } + + + var telemetricsFileContents = string.Empty; + try + { + // Open file & read its contents + // It may throw due to file permissions or file locking + telemetricsFileContents = File.ReadAllText(telemetricsFilePath); + } + catch (Exception ex) + { + // Silently swallow ex - but lets log it (ReadAllText throws a ton of different types of ex) + // Hence the use of general exception type + _logger.Error(ex, "Error in reading file contents of telemetry marker file found at '{filePath}'", telemetricsFilePath); + } + + + // Parse as a GUID & verify its a GUID and not some random string + // In case of users may have messed or decided to empty the file contents or put in something random + if (Guid.TryParse(telemetricsFileContents, out var telemetrySiteIdentifier) == false) + { + // Some users may have decided to mess with file contents + _logger.Warn("The telemetry marker file found at '{filePath}' with '{telemetrySiteId}' is not a valid identifier for the telemetry service", telemetricsFilePath, telemetrySiteIdentifier); + + // Stop repeating this task (no need to keep checking) + // The only time it will recheck when the site is recycled + return false; + } + + + try + { + // Make a HTTP Post to telemetry service + // https://telemetry.umbraco.com/installs + // Fire & Forget, do not need to know if its a 200, 500 etc + var postData = new TelemetryReportData { Id = telemetrySiteIdentifier, Version = UmbracoVersion.SemanticVersion.ToSemanticString() }; + var result = await _httpClient.PostAsync("https://webhook.site/9c38527a-eca4-4ad6-9847-202f2b37c07d", postData, new JsonMediaTypeFormatter()); + } + catch (Exception ex) + { + // Silently swallow + // The user does need logs being polluted if our service has fallen over or is down etc + } + + // Keep recurring this task & pinging the telemetry service + return true; + } + + public override bool IsAsync => true; + + + private class TelemetryReportData + { + public Guid Id { get; set; } + + public string Version { get; set; } + } + } +} + diff --git a/src/Umbraco.Web/Telemetry/TelemetryComponent.cs b/src/Umbraco.Web/Telemetry/TelemetryComponent.cs new file mode 100644 index 0000000000..c8113df58b --- /dev/null +++ b/src/Umbraco.Web/Telemetry/TelemetryComponent.cs @@ -0,0 +1,35 @@ +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Web.Scheduling; + +namespace Umbraco.Web.Telemetry +{ + public class TelemetryComponent : IComponent + { + private IProfilingLogger _logger; + private IRuntimeState _runtime; + private BackgroundTaskRunner _telemetryReporterRunner; + + public TelemetryComponent(IProfilingLogger logger, IRuntimeState runtime) + { + _logger = logger; + _runtime = runtime; + _telemetryReporterRunner = new BackgroundTaskRunner("TelemetryReporter", _logger); + } + + public void Initialize() + { + int delayBeforeWeStart = 60 * 1000; // 60 * 1000ms = 1min (60,000) + int howOftenWeRepeat = 60 * 1000 * 60 * 24; // 60 * 1000 * 60 * 24 = 24hrs (86400000) + + // As soon as we add our task to the runner it will start to run (after its delay period) + var task = new ReportSiteTask(_telemetryReporterRunner, delayBeforeWeStart, howOftenWeRepeat, _runtime, _logger); + _telemetryReporterRunner.TryAdd(task); + } + + public void Terminate() + { + } + } +} diff --git a/src/Umbraco.Web/Telemetry/TelemetryComposer.cs b/src/Umbraco.Web/Telemetry/TelemetryComposer.cs new file mode 100644 index 0000000000..933b302e1c --- /dev/null +++ b/src/Umbraco.Web/Telemetry/TelemetryComposer.cs @@ -0,0 +1,10 @@ +using Umbraco.Core; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Telemetry +{ + + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public class TelemetryComposer : ComponentComposer, ICoreComposer + { } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 1b48b9ca0d..90088fe552 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -296,6 +296,9 @@ + + + From c93a5a5cd1a455908a4a498feb271e5d25fd95ad Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 4 Nov 2020 14:07:56 +0000 Subject: [PATCH 13/63] Remove as an upgrade migration step as new installs will never get the marker file --- .../Migrations/Upgrade/UmbracoPlan.cs | 4 -- .../V_8_10_0/SetupAnonInstallTracker.cs | 49 ------------------- src/Umbraco.Core/Umbraco.Core.csproj | 1 - 3 files changed, 54 deletions(-) delete mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/SetupAnonInstallTracker.cs diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index f97fcda49c..ef1f038f04 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -6,7 +6,6 @@ using Umbraco.Core.Migrations.Upgrade.Common; using Umbraco.Core.Migrations.Upgrade.V_8_0_0; using Umbraco.Core.Migrations.Upgrade.V_8_0_1; using Umbraco.Core.Migrations.Upgrade.V_8_1_0; -using Umbraco.Core.Migrations.Upgrade.V_8_10_0; using Umbraco.Core.Migrations.Upgrade.V_8_6_0; using Umbraco.Core.Migrations.Upgrade.V_8_8_0; @@ -199,9 +198,6 @@ namespace Umbraco.Core.Migrations.Upgrade // to 8.8.0 To("{B5838FF5-1D22-4F6C-BCEB-F83ACB14B575}"); - // to 8.10.0 - To("{DCBA2C6A-01B3-411E-9CDE-0AB9C69EFF33}"); - //FINAL } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/SetupAnonInstallTracker.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/SetupAnonInstallTracker.cs deleted file mode 100644 index 6eded30312..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/SetupAnonInstallTracker.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.IO; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; - - -namespace Umbraco.Core.Migrations.Upgrade.V_8_10_0 -{ - - public class SetupAnonInstallTracker : MigrationBase - { - public SetupAnonInstallTracker(IMigrationContext context) - : base(context) - { - } - - /// - /// Adds a new file 'telemetrics-id.umb' at /umbraco - /// Which will add a GUID inside the file as JSON - /// - public override void Migrate() - { - var telemetricsFilePath = IOHelper.MapPath(SystemFiles.TelemetricsIdentifier); - - // Verify file does not exist already - if (File.Exists(telemetricsFilePath)) - { - Logger.Warn("When migrating to 8.10.0 the anonymous telemetry file already existsed on disk at {filePath}", telemetricsFilePath); - return; - } - - // Generate GUID - var telemetrySiteIdentifier = Guid.NewGuid(); - - // Write file contents - try - { - File.WriteAllText(telemetricsFilePath, telemetrySiteIdentifier.ToString()); - } - catch (Exception ex) - { - Logger.Error(ex, "Unable to create telemetry file at {filePath}", telemetricsFilePath); - } - - Logger.Info("This site has been identified with an anynomous id {telemetrySiteId} for telemetrics and written to {filePath}", telemetrySiteIdentifier, telemetricsFilePath); - - } - } -} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 821ddb3dc9..71b2a7be4a 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -132,7 +132,6 @@ - From 17249d226f3e1ed55085279cb312353cb4db1261 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 4 Nov 2020 14:24:48 +0000 Subject: [PATCH 14/63] Add new composer/component to add marker file only when clean install or an upgrade runtime state --- .../Telemetry/TelemetryMarkerComponent.cs | 52 +++++++++++++++++++ .../Telemetry/TelemetryMarkerComposer.cs | 9 ++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 + 3 files changed, 63 insertions(+) create mode 100644 src/Umbraco.Web/Telemetry/TelemetryMarkerComponent.cs create mode 100644 src/Umbraco.Web/Telemetry/TelemetryMarkerComposer.cs diff --git a/src/Umbraco.Web/Telemetry/TelemetryMarkerComponent.cs b/src/Umbraco.Web/Telemetry/TelemetryMarkerComponent.cs new file mode 100644 index 0000000000..0426fe777e --- /dev/null +++ b/src/Umbraco.Web/Telemetry/TelemetryMarkerComponent.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; + +namespace Umbraco.Web.Telemetry +{ + public class TelemetryMarkerComponent : IComponent + { + private IProfilingLogger _logger; + private IRuntimeState _runtime; + + public TelemetryMarkerComponent(IProfilingLogger logger, IRuntimeState runtime) + { + _logger = logger; + _runtime = runtime; + } + + public void Initialize() + { + var telemetricsFilePath = IOHelper.MapPath(SystemFiles.TelemetricsIdentifier); + + // Verify file does not exist already + // If the site is upgraded and the file was removed it would re-create one + if (File.Exists(telemetricsFilePath)) + { + _logger.Warn("When installing or upgrading the anonymous telemetry file already existsed on disk at {filePath} with the runtime state {runtimeStateLevel}", telemetricsFilePath, _runtime.Level); + return; + } + + // Generate GUID + var telemetrySiteIdentifier = Guid.NewGuid(); + + // Write file contents + try + { + File.WriteAllText(telemetricsFilePath, telemetrySiteIdentifier.ToString()); + } + catch (Exception ex) + { + _logger.Error(ex, "Unable to create telemetry file at {filePath}", telemetricsFilePath); + } + + } + + public void Terminate() + { + } + } +} diff --git a/src/Umbraco.Web/Telemetry/TelemetryMarkerComposer.cs b/src/Umbraco.Web/Telemetry/TelemetryMarkerComposer.cs new file mode 100644 index 0000000000..e01b4a7f10 --- /dev/null +++ b/src/Umbraco.Web/Telemetry/TelemetryMarkerComposer.cs @@ -0,0 +1,9 @@ +using Umbraco.Core; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Telemetry +{ + [RuntimeLevel(MinLevel = RuntimeLevel.Install, MaxLevel = RuntimeLevel.Upgrade)] + public class TelemetryMarkerComposer : ComponentComposer, ICoreComposer + { } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 90088fe552..1f7158b2b1 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -297,8 +297,10 @@ + + From b1051a882d8196d5f9b15b0b363e4eb8d64a80dd Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 5 Nov 2020 11:52:50 +0000 Subject: [PATCH 15/63] git ignore telemetry file when doing local dev --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index eee2a46a44..3ae099a82f 100644 --- a/.gitignore +++ b/.gitignore @@ -175,3 +175,4 @@ cypress.env.json /src/Umbraco.Tests.AcceptanceTest/package-lock.json /src/Umbraco.Tests.AcceptanceTest/cypress/videos/ /src/Umbraco.Tests.AcceptanceTest/cypress/screenshots/ +src/Umbraco.Web.UI/Umbraco/telemetrics-id.umb From a28f3475eb7032772cec9ddc3a1ca5649f1d98ab Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 5 Nov 2020 11:55:18 +0000 Subject: [PATCH 16/63] Only add something to the log when upgrading that the file is missing - during install it would loop over this component serveral times --- src/Umbraco.Web/Telemetry/TelemetryMarkerComponent.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Telemetry/TelemetryMarkerComponent.cs b/src/Umbraco.Web/Telemetry/TelemetryMarkerComponent.cs index 0426fe777e..660ed18fba 100644 --- a/src/Umbraco.Web/Telemetry/TelemetryMarkerComponent.cs +++ b/src/Umbraco.Web/Telemetry/TelemetryMarkerComponent.cs @@ -22,14 +22,17 @@ namespace Umbraco.Web.Telemetry { var telemetricsFilePath = IOHelper.MapPath(SystemFiles.TelemetricsIdentifier); - // Verify file does not exist already + // Verify file does not exist already (if we are upgrading) + // In a clean install we know it would not exist // If the site is upgraded and the file was removed it would re-create one - if (File.Exists(telemetricsFilePath)) + // NOTE: If user removed the marker file to opt out it would re-create a new guid marker file & potentially skew + if (_runtime.Level == RuntimeLevel.Upgrade && File.Exists(telemetricsFilePath)) { - _logger.Warn("When installing or upgrading the anonymous telemetry file already existsed on disk at {filePath} with the runtime state {runtimeStateLevel}", telemetricsFilePath, _runtime.Level); + _logger.Warn("When upgrading the anonymous telemetry file already existsed on disk at {filePath}", telemetricsFilePath); return; } + // We are a clean install or an upgrade without the marker file // Generate GUID var telemetrySiteIdentifier = Guid.NewGuid(); From 918529748494461e0ebb0d20054ad2b3d48a19ed Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 5 Nov 2020 11:55:30 +0000 Subject: [PATCH 17/63] Post to dev env --- src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index 8d777b73db..a3f9e0991d 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -83,7 +83,7 @@ namespace Umbraco.Web.Telemetry // https://telemetry.umbraco.com/installs // Fire & Forget, do not need to know if its a 200, 500 etc var postData = new TelemetryReportData { Id = telemetrySiteIdentifier, Version = UmbracoVersion.SemanticVersion.ToSemanticString() }; - var result = await _httpClient.PostAsync("https://webhook.site/9c38527a-eca4-4ad6-9847-202f2b37c07d", postData, new JsonMediaTypeFormatter()); + var result = await _httpClient.PostAsync("https://telemetry.rainbowsrock.net/installs", postData, new JsonMediaTypeFormatter()); } catch (Exception ex) { From 69b3feb0ea575a184e49843b35c1561a122d9fc9 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 5 Nov 2020 12:17:46 +0000 Subject: [PATCH 18/63] Correct dev URL note the trailing slash --- src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index a3f9e0991d..df0bed0830 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -83,7 +83,7 @@ namespace Umbraco.Web.Telemetry // https://telemetry.umbraco.com/installs // Fire & Forget, do not need to know if its a 200, 500 etc var postData = new TelemetryReportData { Id = telemetrySiteIdentifier, Version = UmbracoVersion.SemanticVersion.ToSemanticString() }; - var result = await _httpClient.PostAsync("https://telemetry.rainbowsrock.net/installs", postData, new JsonMediaTypeFormatter()); + var result = await _httpClient.PostAsync("https://telemetry.rainbowsrock.net/installs/", postData, new JsonMediaTypeFormatter()); } catch (Exception ex) { From 191d86e27cd258aca12d0e7b641a88dc34b4e48e Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 10 Nov 2020 08:25:32 +0000 Subject: [PATCH 19/63] Do not log that marker file exists already during the serval install boot cycles --- src/Umbraco.Web/Telemetry/TelemetryMarkerComponent.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Web/Telemetry/TelemetryMarkerComponent.cs b/src/Umbraco.Web/Telemetry/TelemetryMarkerComponent.cs index 660ed18fba..36cae322ce 100644 --- a/src/Umbraco.Web/Telemetry/TelemetryMarkerComponent.cs +++ b/src/Umbraco.Web/Telemetry/TelemetryMarkerComponent.cs @@ -31,6 +31,11 @@ namespace Umbraco.Web.Telemetry _logger.Warn("When upgrading the anonymous telemetry file already existsed on disk at {filePath}", telemetricsFilePath); return; } + else if (_runtime.Level == RuntimeLevel.Install && File.Exists(telemetricsFilePath)) + { + // No need to log for when level is install if file exists (As this component hit several times during install process) + return; + } // We are a clean install or an upgrade without the marker file // Generate GUID From 59a03115ea808f0fd4730f53ccfc7d803433201b Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 10 Nov 2020 09:58:15 +0000 Subject: [PATCH 20/63] Posting to service needed lowercase properties --- src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 29 ++++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index df0bed0830..1905e51540 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -1,7 +1,8 @@ -using System; +using Newtonsoft.Json; +using System; using System.IO; using System.Net.Http; -using System.Net.Http.Formatting; +using System.Text; using System.Threading; using System.Threading.Tasks; using Umbraco.Core; @@ -76,14 +77,26 @@ namespace Umbraco.Web.Telemetry return false; } - try { - // Make a HTTP Post to telemetry service - // https://telemetry.umbraco.com/installs - // Fire & Forget, do not need to know if its a 200, 500 etc + // Send data to LIVE telemetry + _httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); + +//#if DEBUG +// // Send data to DEBUG telemetry service +// _httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); +//#endif + + _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); var postData = new TelemetryReportData { Id = telemetrySiteIdentifier, Version = UmbracoVersion.SemanticVersion.ToSemanticString() }; - var result = await _httpClient.PostAsync("https://telemetry.rainbowsrock.net/installs/", postData, new JsonMediaTypeFormatter()); + var request = new HttpRequestMessage(HttpMethod.Post, "installs/"); + request.Content = new StringContent(JsonConvert.SerializeObject(postData), Encoding.UTF8, "application/json"); //CONTENT-TYPE header + + // Make a HTTP Post to telemetry service + // https://telemetry.umbraco.com/installs/ + // Fire & Forget, do not need to know if its a 200, 500 etc + var result = await _httpClient.SendAsync(request); + } catch (Exception ex) { @@ -100,8 +113,10 @@ namespace Umbraco.Web.Telemetry private class TelemetryReportData { + [JsonProperty("id")] public Guid Id { get; set; } + [JsonProperty("version")] public string Version { get; set; } } } From b4c65fe12a05c21814c6964fe4ab475f8957d6ad Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 27 Nov 2020 11:19:37 +0000 Subject: [PATCH 21/63] Update src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs Co-authored-by: Bjarke Berg --- .../Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs index a9fcdd8f98..206ea2be02 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs @@ -1,7 +1,7 @@ using System.Linq; using Umbraco.Core.Persistence.Dtos; -namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 +namespace Umbraco.Core.Migrations.Upgrade.V_8_10_0 { public class AddPropertyTypeLabelOnTopColumn : MigrationBase From 855bef8c59a3849bcc1ea75f6b66b431de62f33d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 30 Nov 2020 09:56:01 +0100 Subject: [PATCH 22/63] remove this class, should not have been added here. --- src/Umbraco.Web.UI.Client/src/views/membergroups/edit.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.html b/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.html index aafaaa3bd4..63089de20c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.html @@ -19,7 +19,7 @@ setpagetitle="header.setPageTitle"> - + From 8a6db7f71f930f423f76baeefaead17ca5d2990d Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 30 Nov 2020 09:05:03 +0000 Subject: [PATCH 23/63] Uncomment out the DEBUG lines --- src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index 1905e51540..074322e126 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -83,8 +83,8 @@ namespace Umbraco.Web.Telemetry _httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); //#if DEBUG -// // Send data to DEBUG telemetry service -// _httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); + // Send data to DEBUG telemetry service + _httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); //#endif _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); From 36cd06b952e12d7fc6cc79501c50314b27102b2d Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 30 Nov 2020 09:05:38 +0000 Subject: [PATCH 24/63] Add Debug logging and dont set type for ex as we are not using it --- src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index 074322e126..373a1765c8 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -92,16 +92,19 @@ namespace Umbraco.Web.Telemetry var request = new HttpRequestMessage(HttpMethod.Post, "installs/"); request.Content = new StringContent(JsonConvert.SerializeObject(postData), Encoding.UTF8, "application/json"); //CONTENT-TYPE header + // Make a HTTP Post to telemetry service // https://telemetry.umbraco.com/installs/ // Fire & Forget, do not need to know if its a 200, 500 etc var result = await _httpClient.SendAsync(request); } - catch (Exception ex) + catch { // Silently swallow - // The user does need logs being polluted if our service has fallen over or is down etc + // The user does not need the logs being polluted if our service has fallen over or is down etc + // Hence only loggigng this at a more verbose level (Which users should not be using in prod) + _logger.Debug("There was a problem sending a request to the Umbraco telemetry service"); } // Keep recurring this task & pinging the telemetry service From c5afcf1c9c394c156e791cb7b6d03ea37ac571f3 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 30 Nov 2020 09:08:24 +0000 Subject: [PATCH 25/63] Move marker file into App_Data a folder that can never be served --- src/Umbraco.Core/IO/SystemFiles.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/IO/SystemFiles.cs b/src/Umbraco.Core/IO/SystemFiles.cs index 5e38737dd2..d33e9dfdfc 100644 --- a/src/Umbraco.Core/IO/SystemFiles.cs +++ b/src/Umbraco.Core/IO/SystemFiles.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.IO { public static string TinyMceConfig => SystemDirectories.Config + "/tinyMceConfig.config"; - public static string TelemetricsIdentifier => SystemDirectories.Umbraco + "/telemetrics-id.umb"; + public static string TelemetricsIdentifier => SystemDirectories.Data + "/telemetrics-id.umb"; // TODO: Kill this off we don't have umbraco.config XML cache we now have NuCache public static string GetContentCacheXml(IGlobalSettings globalSettings) From 5dcb488d2cc5bb6214936d53548f163cf6a68995 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 30 Nov 2020 09:12:27 +0000 Subject: [PATCH 26/63] Update src/Umbraco.Web/Telemetry/ReportSiteTask.cs Co-authored-by: Bjarke Berg --- src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index 373a1765c8..0613e37bab 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -114,14 +114,14 @@ namespace Umbraco.Web.Telemetry public override bool IsAsync => true; + [DataContract] private class TelemetryReportData { - [JsonProperty("id")] + [DataMember(Name = "id")] public Guid Id { get; set; } - [JsonProperty("version")] + [DataMember(Name = "version")] public string Version { get; set; } } } } - From f747e2a2dcc3a2a91083034de7e10973b98fa273 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 30 Nov 2020 09:17:39 +0000 Subject: [PATCH 27/63] Missing namespace from Bjarkes suggestion to move to DataMember --- src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index 0613e37bab..7c2913a465 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Net.Http; +using System.Runtime.Serialization; using System.Text; using System.Threading; using System.Threading.Tasks; From 1394387d4360452de29f154320f0c0555538fbad Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 30 Nov 2020 09:18:13 +0000 Subject: [PATCH 28/63] Move the backgroundtask runner into Initialize method to follow same practise as SchedulerComponent --- src/Umbraco.Web/Telemetry/TelemetryComponent.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Telemetry/TelemetryComponent.cs b/src/Umbraco.Web/Telemetry/TelemetryComponent.cs index c8113df58b..bb26ae5f8d 100644 --- a/src/Umbraco.Web/Telemetry/TelemetryComponent.cs +++ b/src/Umbraco.Web/Telemetry/TelemetryComponent.cs @@ -15,11 +15,13 @@ namespace Umbraco.Web.Telemetry { _logger = logger; _runtime = runtime; - _telemetryReporterRunner = new BackgroundTaskRunner("TelemetryReporter", _logger); } public void Initialize() { + // backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly + _telemetryReporterRunner = new BackgroundTaskRunner("TelemetryReporter", _logger); + int delayBeforeWeStart = 60 * 1000; // 60 * 1000ms = 1min (60,000) int howOftenWeRepeat = 60 * 1000 * 60 * 24; // 60 * 1000 * 60 * 24 = 24hrs (86400000) From b29ed4c9c36e6e8ac6acc99a6002ddd11a7a82f2 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 30 Nov 2020 10:04:21 +0000 Subject: [PATCH 29/63] Set a low 1 second timeout, no need to be a BIG timeout on this request --- src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index 7c2913a465..5f3dbcb70c 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -93,6 +93,8 @@ namespace Umbraco.Web.Telemetry var request = new HttpRequestMessage(HttpMethod.Post, "installs/"); request.Content = new StringContent(JsonConvert.SerializeObject(postData), Encoding.UTF8, "application/json"); //CONTENT-TYPE header + // Set a low timeout - no need to use a larger default timeout for this POST request + _httpClient.Timeout = new TimeSpan(0,0,1); // Make a HTTP Post to telemetry service // https://telemetry.umbraco.com/installs/ From 53282686450418d55b0c26f6e985b20c06d64628 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 30 Nov 2020 10:08:48 +0000 Subject: [PATCH 30/63] Uncomment #ifdef --- src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index 5f3dbcb70c..34c5932081 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -83,10 +83,10 @@ namespace Umbraco.Web.Telemetry // Send data to LIVE telemetry _httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); -//#if DEBUG +#if DEBUG // Send data to DEBUG telemetry service _httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); -//#endif +#endif _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); var postData = new TelemetryReportData { Id = telemetrySiteIdentifier, Version = UmbracoVersion.SemanticVersion.ToSemanticString() }; From fc2ff2525d23dbd2bbe6b4977dc5a1eafd405c84 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 30 Nov 2020 10:15:06 +0000 Subject: [PATCH 31/63] No need for the if null check as it will always be null in the ctor --- src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index 34c5932081..a9c6ea7bde 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -25,9 +25,7 @@ namespace Umbraco.Web.Telemetry { _runtime = runtime; _logger = logger; - - if (_httpClient == null) - _httpClient = new HttpClient(); + _httpClient = new HttpClient(); } /// From 3e6830e1736a68d2b0a71a21eaf84e6726170424 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 30 Nov 2020 10:18:11 +0000 Subject: [PATCH 32/63] Exit out of function but set to repeat if we are unable to read the file (perhaps a file lock) --- src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index a9c6ea7bde..2f6c348414 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -61,6 +61,9 @@ namespace Umbraco.Web.Telemetry // Silently swallow ex - but lets log it (ReadAllText throws a ton of different types of ex) // Hence the use of general exception type _logger.Error(ex, "Error in reading file contents of telemetry marker file found at '{filePath}'", telemetricsFilePath); + + // Exit out early, but mark this task to be repeated in case its a file lock so it can be rechecked the next time round + return true; } From 4de3c05e83aa4cbb0f877a691039828bc05c39e7 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 30 Nov 2020 10:33:07 +0000 Subject: [PATCH 33/63] Wrap use of httpClient in a usings due to it being IDisposable --- src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 24 +++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index 2f6c348414..725f0e431f 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -89,19 +89,21 @@ namespace Umbraco.Web.Telemetry _httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); #endif - _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); - var postData = new TelemetryReportData { Id = telemetrySiteIdentifier, Version = UmbracoVersion.SemanticVersion.ToSemanticString() }; - var request = new HttpRequestMessage(HttpMethod.Post, "installs/"); - request.Content = new StringContent(JsonConvert.SerializeObject(postData), Encoding.UTF8, "application/json"); //CONTENT-TYPE header + using (_httpClient) + { + _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); + var postData = new TelemetryReportData { Id = telemetrySiteIdentifier, Version = UmbracoVersion.SemanticVersion.ToSemanticString() }; + var request = new HttpRequestMessage(HttpMethod.Post, "installs/"); + request.Content = new StringContent(JsonConvert.SerializeObject(postData), Encoding.UTF8, "application/json"); //CONTENT-TYPE header - // Set a low timeout - no need to use a larger default timeout for this POST request - _httpClient.Timeout = new TimeSpan(0,0,1); - - // Make a HTTP Post to telemetry service - // https://telemetry.umbraco.com/installs/ - // Fire & Forget, do not need to know if its a 200, 500 etc - var result = await _httpClient.SendAsync(request); + // Set a low timeout - no need to use a larger default timeout for this POST request + _httpClient.Timeout = new TimeSpan(0, 0, 1); + // Make a HTTP Post to telemetry service + // https://telemetry.umbraco.com/installs/ + // Fire & Forget, do not need to know if its a 200, 500 etc + var result = await _httpClient.SendAsync(request); + } } catch { From fa931416a459de1cdea9eea0c3c746c2ea27d6e5 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 30 Nov 2020 10:34:10 +0000 Subject: [PATCH 34/63] Forgot to move the baseaddress set inside the usings --- src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index 725f0e431f..74342a2bf3 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -81,16 +81,16 @@ namespace Umbraco.Web.Telemetry try { - // Send data to LIVE telemetry - _httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); - -#if DEBUG - // Send data to DEBUG telemetry service - _httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); -#endif - using (_httpClient) { + // Send data to LIVE telemetry + _httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); + +#if DEBUG + // Send data to DEBUG telemetry service + _httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); +#endif + _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); var postData = new TelemetryReportData { Id = telemetrySiteIdentifier, Version = UmbracoVersion.SemanticVersion.ToSemanticString() }; var request = new HttpRequestMessage(HttpMethod.Post, "installs/"); From ee89610559e77c86716e17a7dea8e489bc2446c8 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 30 Nov 2020 12:23:28 +0000 Subject: [PATCH 35/63] Remove unused RuntimeState --- src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 4 +--- src/Umbraco.Web/Telemetry/TelemetryComponent.cs | 9 +++------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index 74342a2bf3..aa5aefe218 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -16,14 +16,12 @@ namespace Umbraco.Web.Telemetry { public class ReportSiteTask : RecurringTaskBase { - private IRuntimeState _runtime; private IProfilingLogger _logger; private static HttpClient _httpClient; - public ReportSiteTask(IBackgroundTaskRunner runner, int delayBeforeWeStart, int howOftenWeRepeat, IRuntimeState runtime, IProfilingLogger logger) + public ReportSiteTask(IBackgroundTaskRunner runner, int delayBeforeWeStart, int howOftenWeRepeat, IProfilingLogger logger) : base(runner, delayBeforeWeStart, howOftenWeRepeat) { - _runtime = runtime; _logger = logger; _httpClient = new HttpClient(); } diff --git a/src/Umbraco.Web/Telemetry/TelemetryComponent.cs b/src/Umbraco.Web/Telemetry/TelemetryComponent.cs index bb26ae5f8d..4ea4c6573a 100644 --- a/src/Umbraco.Web/Telemetry/TelemetryComponent.cs +++ b/src/Umbraco.Web/Telemetry/TelemetryComponent.cs @@ -1,5 +1,4 @@ -using Umbraco.Core; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Web.Scheduling; @@ -8,13 +7,11 @@ namespace Umbraco.Web.Telemetry public class TelemetryComponent : IComponent { private IProfilingLogger _logger; - private IRuntimeState _runtime; private BackgroundTaskRunner _telemetryReporterRunner; - public TelemetryComponent(IProfilingLogger logger, IRuntimeState runtime) + public TelemetryComponent(IProfilingLogger logger) { _logger = logger; - _runtime = runtime; } public void Initialize() @@ -26,7 +23,7 @@ namespace Umbraco.Web.Telemetry int howOftenWeRepeat = 60 * 1000 * 60 * 24; // 60 * 1000 * 60 * 24 = 24hrs (86400000) // As soon as we add our task to the runner it will start to run (after its delay period) - var task = new ReportSiteTask(_telemetryReporterRunner, delayBeforeWeStart, howOftenWeRepeat, _runtime, _logger); + var task = new ReportSiteTask(_telemetryReporterRunner, delayBeforeWeStart, howOftenWeRepeat, _logger); _telemetryReporterRunner.TryAdd(task); } From 6d120322e5154bd60bf7a37e8c9e3f5d9aaebdb4 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 30 Nov 2020 13:35:55 +0100 Subject: [PATCH 36/63] Do not dispose the static httpClient, but the HttpRequestMessage --- src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs index aa5aefe218..fcac8570d2 100644 --- a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -79,19 +79,20 @@ namespace Umbraco.Web.Telemetry try { - using (_httpClient) - { - // Send data to LIVE telemetry - _httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); + + // Send data to LIVE telemetry + _httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/"); #if DEBUG - // Send data to DEBUG telemetry service - _httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); + // Send data to DEBUG telemetry service + _httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/"); #endif - _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); + _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); + + using (var request = new HttpRequestMessage(HttpMethod.Post, "installs/")) + { var postData = new TelemetryReportData { Id = telemetrySiteIdentifier, Version = UmbracoVersion.SemanticVersion.ToSemanticString() }; - var request = new HttpRequestMessage(HttpMethod.Post, "installs/"); request.Content = new StringContent(JsonConvert.SerializeObject(postData), Encoding.UTF8, "application/json"); //CONTENT-TYPE header // Set a low timeout - no need to use a larger default timeout for this POST request From 7ff0f9ee2098914f5fa68252a3e6846c62997bcb Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 30 Nov 2020 13:27:33 +0000 Subject: [PATCH 37/63] Bump version to 8.10.0-rc .\build SetUmbracoVersion 8.10.0-rc --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 1c59137aac..89997c0a01 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.9.1")] -[assembly: AssemblyInformationalVersion("8.9.1")] +[assembly: AssemblyFileVersion("8.10.0")] +[assembly: AssemblyInformationalVersion("8.10.0-rc")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index aece40a4c4..6e4a4b780e 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -347,9 +347,9 @@ False True - 8910 + 8100 / - http://localhost:8910 + http://localhost:8100 False False From 2f9e92eee78219ee698e4a123da0e7a72c38d721 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 3 Dec 2020 20:30:35 +1100 Subject: [PATCH 38/63] Make BackOfficeClaimsPrincipalFactory not be generic, doesn't need to be, cleans up code as per rules --- .../BackOfficeClaimsPrincipalFactory.cs | 39 +++-- ...kOfficeServiceCollectionExtensionsTests.cs | 4 +- .../BackOfficeClaimsPrincipalFactoryTests.cs | 64 +++---- .../Security/BackOfficeCookieManagerTests.cs | 39 +---- .../BackOfficeServiceCollectionExtensions.cs | 4 +- .../Security/BackOfficeUserManager.cs | 165 ++++++++++-------- .../ConfigureBackOfficeCookieOptions.cs | 100 ++++++----- 7 files changed, 216 insertions(+), 199 deletions(-) diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs b/src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs index 568c028e67..22ea4423d2 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs +++ b/src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; @@ -7,27 +7,43 @@ using Microsoft.Extensions.Options; namespace Umbraco.Core.BackOffice { - public class BackOfficeClaimsPrincipalFactory : UserClaimsPrincipalFactory - where TUser : BackOfficeIdentityUser + /// + /// A + /// + public class BackOfficeClaimsPrincipalFactory : UserClaimsPrincipalFactory { - public BackOfficeClaimsPrincipalFactory(UserManager userManager, IOptions optionsAccessor) + + /// + /// Initializes a new instance of the class. + /// + /// The user manager + /// The + public BackOfficeClaimsPrincipalFactory(UserManager userManager, IOptions optionsAccessor) : base(userManager, optionsAccessor) { } - public override async Task CreateAsync(TUser user) + /// + /// + /// Returns a custom and allows flowing claims from the external identity + /// + public override async Task CreateAsync(BackOfficeIdentityUser user) { - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } - var baseIdentity = await base.GenerateClaimsAsync(user); + ClaimsIdentity baseIdentity = await base.GenerateClaimsAsync(user); // now we can flow any custom claims that the actual user has currently assigned which could be done in the OnExternalLogin callback - foreach (var claim in user.Claims) + foreach (Models.Identity.IdentityUserClaim claim in user.Claims) { baseIdentity.AddClaim(new Claim(claim.ClaimType, claim.ClaimValue)); } - + // TODO: We want to remove UmbracoBackOfficeIdentity and only rely on ClaimsIdentity, once + // that is done then we'll create a ClaimsIdentity with all of the requirements here instead var umbracoIdentity = new UmbracoBackOfficeIdentity( baseIdentity, user.Id, @@ -43,7 +59,8 @@ namespace Umbraco.Core.BackOffice return new ClaimsPrincipal(umbracoIdentity); } - protected override async Task GenerateClaimsAsync(TUser user) + /// + protected override async Task GenerateClaimsAsync(BackOfficeIdentityUser user) { // TODO: Have a look at the base implementation https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/UserClaimsPrincipalFactory.cs#L79 // since it's setting an authentication type that is probably not what we want. @@ -51,7 +68,7 @@ namespace Umbraco.Core.BackOffice // the method above just returns a principal that wraps the identity and we dont use a custom principal, // see https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/UserClaimsPrincipalFactory.cs#L66 - var identity = await base.GenerateClaimsAsync(user); + ClaimsIdentity identity = await base.GenerateClaimsAsync(user); return identity; } diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs index 450b3a341a..26c3f7875c 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; @@ -26,7 +26,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Web.BackOffice var principalFactory = Services.GetService>(); Assert.IsNotNull(principalFactory); - Assert.AreEqual(typeof(BackOfficeClaimsPrincipalFactory), principalFactory.GetType()); + Assert.AreEqual(typeof(BackOfficeClaimsPrincipalFactory), principalFactory.GetType()); } [Test] diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs index 13c73dfa96..5291c1b12e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; @@ -8,50 +8,41 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Membership; using Umbraco.Extensions; -using Umbraco.Tests.Common.Builders; namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice { [TestFixture] public class BackOfficeClaimsPrincipalFactoryTests { - private const int _testUserId = 2; - private const string _testUserName = "bob"; - private const string _testUserGivenName = "Bob"; - private const string _testUserCulture = "en-US"; - private const string _testUserSecurityStamp = "B6937738-9C17-4C7D-A25A-628A875F5177"; + private const int TestUserId = 2; + private const string TestUserName = "bob"; + private const string TestUserGivenName = "Bob"; + private const string TestUserCulture = "en-US"; + private const string TestUserSecurityStamp = "B6937738-9C17-4C7D-A25A-628A875F5177"; private BackOfficeIdentityUser _testUser; private Mock> _mockUserManager; + private static Mock> GetMockedUserManager() + => new Mock>(new Mock>().Object, null, null, null, null, null, null, null, null); + [Test] public void Ctor_When_UserManager_Is_Null_Expect_ArgumentNullException() - { - Assert.Throws(() => new BackOfficeClaimsPrincipalFactory( - null, - new OptionsWrapper(new BackOfficeIdentityOptions()))); - } + => Assert.Throws(() => new BackOfficeClaimsPrincipalFactory( + null, + new OptionsWrapper(new BackOfficeIdentityOptions()))); [Test] public void Ctor_When_Options_Are_Null_Expect_ArgumentNullException() - { - Assert.Throws(() => new BackOfficeClaimsPrincipalFactory( - new Mock>(new Mock>().Object, - null, null, null, null, null, null, null, null).Object, - null)); - } + => Assert.Throws(() => new BackOfficeClaimsPrincipalFactory(GetMockedUserManager().Object, null)); [Test] public void Ctor_When_Options_Value_Is_Null_Expect_ArgumentNullException() - { - Assert.Throws(() => new BackOfficeClaimsPrincipalFactory( - new Mock>(new Mock>().Object, - null, null, null, null, null, null, null, null).Object, - new OptionsWrapper(null))); - } + => Assert.Throws(() => new BackOfficeClaimsPrincipalFactory( + GetMockedUserManager().Object, + new OptionsWrapper(null))); [Test] public void CreateAsync_When_User_Is_Null_Expect_ArgumentNullException() @@ -72,8 +63,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice Assert.IsNotNull(umbracoBackOfficeIdentity); } - [TestCase(ClaimTypes.NameIdentifier, _testUserId)] - [TestCase(ClaimTypes.Name, _testUserName)] + [TestCase(ClaimTypes.NameIdentifier, TestUserId)] + [TestCase(ClaimTypes.Name, TestUserName)] public async Task CreateAsync_Should_Include_Claim(string expectedClaimType, object expectedClaimValue) { var sut = CreateSut(); @@ -141,17 +132,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice { var globalSettings = new GlobalSettings { DefaultUILanguage = "test" }; - _testUser = new BackOfficeIdentityUser(globalSettings, _testUserId, new List()) + _testUser = new BackOfficeIdentityUser(globalSettings, TestUserId, new List()) { - UserName = _testUserName, - Name = _testUserGivenName, + UserName = TestUserName, + Name = TestUserGivenName, Email = "bob@umbraco.test", - SecurityStamp = _testUserSecurityStamp, - Culture = _testUserCulture + SecurityStamp = TestUserSecurityStamp, + Culture = TestUserCulture }; - _mockUserManager = new Mock>(new Mock>().Object, - null, null, null, null, null, null, null, null); + _mockUserManager = GetMockedUserManager(); _mockUserManager.Setup(x => x.GetUserIdAsync(_testUser)).ReturnsAsync(_testUser.Id.ToString); _mockUserManager.Setup(x => x.GetUserNameAsync(_testUser)).ReturnsAsync(_testUser.UserName); _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(false); @@ -159,10 +149,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice _mockUserManager.Setup(x => x.SupportsUserRole).Returns(false); } - private BackOfficeClaimsPrincipalFactory CreateSut() - { - return new BackOfficeClaimsPrincipalFactory(_mockUserManager.Object, + private BackOfficeClaimsPrincipalFactory CreateSut() => new BackOfficeClaimsPrincipalFactory( + _mockUserManager.Object, new OptionsWrapper(new BackOfficeIdentityOptions())); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs index e1a8ff9c58..3270a003f5 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Moq; @@ -28,8 +28,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security runtime, Mock.Of(), globalSettings, - Mock.Of(), - Mock.Of()); + Mock.Of()); var result = mgr.ShouldAuthenticateRequest(new Uri("http://localhost/umbraco")); @@ -47,8 +46,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security runtime, Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco"), globalSettings, - Mock.Of(), - Mock.Of()); + Mock.Of()); var result = mgr.ShouldAuthenticateRequest(new Uri("http://localhost/umbraco")); @@ -67,8 +65,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security runtime, Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"), globalSettings, - Mock.Of(), - GetMockLinkGenerator(out var remainingTimeoutSecondsPath, out var isAuthPath)); + Mock.Of()); + + GenerateAuthPaths(out var remainingTimeoutSecondsPath, out var isAuthPath); var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost{remainingTimeoutSecondsPath}")); Assert.IsTrue(result); @@ -89,8 +88,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security runtime, Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"), globalSettings, - Mock.Of(x => x.IsAvailable == true && x.Get(Constants.Security.ForceReAuthFlag) == "not null"), - GetMockLinkGenerator(out var remainingTimeoutSecondsPath, out var isAuthPath)); + Mock.Of(x => x.IsAvailable && x.Get(Constants.Security.ForceReAuthFlag) == "not null")); var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/notbackoffice")); Assert.IsTrue(result); @@ -108,8 +106,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security runtime, Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"), globalSettings, - Mock.Of(), - GetMockLinkGenerator(out var remainingTimeoutSecondsPath, out var isAuthPath)); + Mock.Of()); var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/notbackoffice")); Assert.IsFalse(result); @@ -119,7 +116,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security Assert.IsFalse(result); } - private LinkGenerator GetMockLinkGenerator(out string remainingTimeoutSecondsPath, out string isAuthPath) + private void GenerateAuthPaths(out string remainingTimeoutSecondsPath, out string isAuthPath) { var controllerName = ControllerExtensions.GetControllerName(); @@ -129,24 +126,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security // this is on the same controller but is considered a back office request var aPath = isAuthPath = $"/umbraco/{Constants.Web.Mvc.BackOfficePathSegment}/{Constants.Web.Mvc.BackOfficeApiArea}/{controllerName}/{nameof(AuthenticationController.IsAuthenticated)}".ToLower(); - var linkGenerator = new Mock(); - linkGenerator.Setup(x => x.GetPathByAddress( - //It.IsAny(), - It.IsAny(), - //It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())).Returns((RouteValuesAddress address, RouteValueDictionary routeVals1, PathString path, FragmentString fragment, LinkOptions options) => - { - if (routeVals1["action"].ToString() == nameof(AuthenticationController.GetRemainingTimeoutSeconds)) - return rPath; - if (routeVals1["action"].ToString() == nameof(AuthenticationController.IsAuthenticated).ToLower()) - return aPath; - return null; - }); - - return linkGenerator.Object; } } } diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs index 413a54a28b..74953b19be 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc.Filters; @@ -36,7 +36,7 @@ namespace Umbraco.Extensions .AddUserStore() .AddUserManager() .AddSignInManager() - .AddClaimsPrincipalFactory>(); + .AddClaimsPrincipalFactory(); // Configure the options specifically for the UmbracoBackOfficeIdentityOptions instance services.ConfigureOptions(); diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs index 464f2a38aa..2906d9d87a 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs @@ -67,8 +67,6 @@ namespace Umbraco.Web.Common.Security PasswordConfiguration = passwordConfiguration.Value ?? throw new ArgumentNullException(nameof(passwordConfiguration)); } - #region What we do not currently support - // We don't support an IUserClaimStore and don't need to (at least currently) public override bool SupportsUserClaim => false; @@ -83,8 +81,6 @@ namespace Umbraco.Web.Common.Security // We haven't needed to support this yet, though might be necessary for 2FA public override bool SupportsUserPhoneNumber => false; - #endregion - /// /// Replace the underlying options property with our own strongly typed version /// @@ -97,14 +93,18 @@ namespace Umbraco.Web.Common.Security /// /// Used to validate a user's session /// - /// - /// - /// + /// The user id + /// The sesion id + /// True if the sesion is valid, else false public virtual async Task ValidateSessionIdAsync(string userId, string sessionId) { var userSessionStore = Store as IUserSessionStore; - //if this is not set, for backwards compat (which would be super rare), we'll just approve it - if (userSessionStore == null) return true; + + // if this is not set, for backwards compat (which would be super rare), we'll just approve it + if (userSessionStore == null) + { + return true; + } return await userSessionStore.ValidateSessionIdAsync(userId, sessionId); } @@ -112,12 +112,9 @@ namespace Umbraco.Web.Common.Security /// /// This will determine which password hasher to use based on what is defined in config /// - /// - protected virtual IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) - { - // we can use the user aware password hasher (which will be the default and preferred way) - return new PasswordHasher(); - } + /// The + /// An + protected virtual IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) => new PasswordHasher(); /// /// Gets/sets the default back office user password checker @@ -129,10 +126,14 @@ namespace Umbraco.Web.Common.Security /// /// Helper method to generate a password for a user based on the current password validator /// - /// + /// The generated password public string GeneratePassword() { - if (_passwordGenerator == null) _passwordGenerator = new PasswordGenerator(PasswordConfiguration); + if (_passwordGenerator == null) + { + _passwordGenerator = new PasswordGenerator(PasswordConfiguration); + } + var password = _passwordGenerator.GeneratePassword(); return password; } @@ -160,14 +161,10 @@ namespace Umbraco.Web.Common.Security return await base.IsLockedOutAsync(user); } - #region Overrides for password logic - /// /// Logic used to validate a username and password /// - /// - /// - /// + /// /// /// By default this uses the standard ASP.Net Identity approach which is: /// * Get password store @@ -186,55 +183,61 @@ namespace Umbraco.Web.Common.Security { if (BackOfficeUserPasswordChecker != null) { - var result = await BackOfficeUserPasswordChecker.CheckPasswordAsync(user, password); + BackOfficeUserPasswordCheckerResult result = await BackOfficeUserPasswordChecker.CheckPasswordAsync(user, password); if (user.HasIdentity == false) { return false; } - //if the result indicates to not fallback to the default, then return true if the credentials are valid + // if the result indicates to not fallback to the default, then return true if the credentials are valid if (result != BackOfficeUserPasswordCheckerResult.FallbackToDefaultChecker) { return result == BackOfficeUserPasswordCheckerResult.ValidCredentials; } } - //we cannot proceed if the user passed in does not have an identity + // we cannot proceed if the user passed in does not have an identity if (user.HasIdentity == false) + { return false; + } - //use the default behavior + // use the default behavior return await base.CheckPasswordAsync(user, password); } /// /// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event /// - /// - /// - /// - /// + /// The userId + /// The reset password token + /// The new password to set it to + /// The /// /// We use this because in the back office the only way an admin can change another user's password without first knowing their password /// is to generate a token and reset it, however, when we do this we want to track a password change, not a password reset /// public async Task ChangePasswordWithResetAsync(int userId, string token, string newPassword) { - var user = await base.FindByIdAsync(userId.ToString()); - if (user == null) throw new InvalidOperationException("Could not find user"); + T user = await FindByIdAsync(userId.ToString()); + if (user == null) + { + throw new InvalidOperationException("Could not find user"); + } - var result = await base.ResetPasswordAsync(user, token, newPassword); + IdentityResult result = await base.ResetPasswordAsync(user, token, newPassword); if (result.Succeeded) { RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, userId); } + return result; } public override async Task ChangePasswordAsync(T user, string currentPassword, string newPassword) { - var result = await base.ChangePasswordAsync(user, currentPassword, newPassword); + IdentityResult result = await base.ChangePasswordAsync(user, currentPassword, newPassword); if (result.Succeeded) { RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, user.Id); @@ -245,20 +248,14 @@ namespace Umbraco.Web.Common.Security /// /// Override to determine how to hash the password /// - /// - /// - /// - /// - /// - /// This method is called anytime the password needs to be hashed for storage (i.e. including when reset password is used) - /// + /// protected override async Task UpdatePasswordHash(T user, string newPassword, bool validatePassword) { user.LastPasswordChangeDateUtc = DateTime.UtcNow; if (validatePassword) { - var validate = await ValidatePasswordAsync(user, newPassword); + IdentityResult validate = await ValidatePasswordAsync(user, newPassword); if (!validate.Succeeded) { return validate; @@ -266,7 +263,10 @@ namespace Umbraco.Web.Common.Security } var passwordStore = Store as IUserPasswordStore; - if (passwordStore == null) throw new NotSupportedException("The current user store does not implement " + typeof(IUserPasswordStore<>)); + if (passwordStore == null) + { + throw new NotSupportedException("The current user store does not implement " + typeof(IUserPasswordStore<>)); + } var hash = newPassword != null ? PasswordHasher.HashPassword(user, newPassword) : null; await passwordStore.SetPasswordHashAsync(user, hash, CancellationToken); @@ -277,41 +277,44 @@ namespace Umbraco.Web.Common.Security /// /// This is copied from the underlying .NET base class since they decided to not expose it /// - /// - /// private async Task UpdateSecurityStampInternal(T user) { - if (SupportsUserSecurityStamp == false) return; + if (SupportsUserSecurityStamp == false) + { + return; + } + await GetSecurityStore().SetSecurityStampAsync(user, NewSecurityStamp(), CancellationToken.None); } /// /// This is copied from the underlying .NET base class since they decided to not expose it /// - /// private IUserSecurityStampStore GetSecurityStore() { var store = Store as IUserSecurityStampStore; - if (store == null) throw new NotSupportedException("The current user store does not implement " + typeof(IUserSecurityStampStore<>)); + if (store == null) + { + throw new NotSupportedException("The current user store does not implement " + typeof(IUserSecurityStampStore<>)); + } + return store; } /// /// This is copied from the underlying .NET base class since they decided to not expose it /// - /// - private static string NewSecurityStamp() - { - return Guid.NewGuid().ToString(); - } - - #endregion + private static string NewSecurityStamp() => Guid.NewGuid().ToString(); + /// public override async Task SetLockoutEndDateAsync(T user, DateTimeOffset? lockoutEnd) { - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } - var result = await base.SetLockoutEndDateAsync(user, lockoutEnd); + IdentityResult result = await base.SetLockoutEndDateAsync(user, lockoutEnd); // The way we unlock is by setting the lockoutEnd date to the current datetime if (result.Succeeded && lockoutEnd >= DateTimeOffset.UtcNow) @@ -321,25 +324,33 @@ namespace Umbraco.Web.Common.Security else { RaiseAccountUnlockedEvent(_httpContextAccessor.HttpContext?.User, user.Id); - //Resets the login attempt fails back to 0 when unlock is clicked + + // Resets the login attempt fails back to 0 when unlock is clicked await ResetAccessFailedCountAsync(user); } return result; } + /// public override async Task ResetAccessFailedCountAsync(T user) { - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } var lockoutStore = (IUserLockoutStore)Store; var accessFailedCount = await GetAccessFailedCountAsync(user); if (accessFailedCount == 0) + { return IdentityResult.Success; + } await lockoutStore.ResetAccessFailedCountAsync(user, CancellationToken.None); - //raise the event now that it's reset + + // raise the event now that it's reset RaiseResetAccessFailedCountEvent(_httpContextAccessor.HttpContext?.User, user.Id); return await UpdateAsync(user); } @@ -347,33 +358,33 @@ namespace Umbraco.Web.Common.Security /// /// Overrides the Microsoft ASP.NET user management method /// - /// - /// - /// returns a Async Task - /// - /// - /// Doesn't set fail attempts back to 0 - /// + /// public override async Task AccessFailedAsync(T user) { - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } var lockoutStore = Store as IUserLockoutStore; - if (lockoutStore == null) throw new NotSupportedException("The current user store does not implement " + typeof(IUserLockoutStore<>)); + if (lockoutStore == null) + { + throw new NotSupportedException("The current user store does not implement " + typeof(IUserLockoutStore<>)); + } var count = await lockoutStore.IncrementAccessFailedCountAsync(user, CancellationToken.None); if (count >= Options.Lockout.MaxFailedAccessAttempts) { - await lockoutStore.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan), - CancellationToken.None); - //NOTE: in normal aspnet identity this would do set the number of failed attempts back to 0 - //here we are persisting the value for the back office + await lockoutStore.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan), CancellationToken.None); + + // NOTE: in normal aspnet identity this would do set the number of failed attempts back to 0 + // here we are persisting the value for the back office } - var result = await UpdateAsync(user); + IdentityResult result = await UpdateAsync(user); - //Slightly confusing: this will return a Success if we successfully update the AccessFailed count + // Slightly confusing: this will return a Success if we successfully update the AccessFailed count if (result.Succeeded) { RaiseLoginFailedEvent(_httpContextAccessor.HttpContext?.User, user.Id); @@ -384,16 +395,18 @@ namespace Umbraco.Web.Common.Security private int GetCurrentUserId(IPrincipal currentUser) { - var umbIdentity = currentUser?.GetUmbracoIdentity(); + UmbracoBackOfficeIdentity umbIdentity = currentUser?.GetUmbracoIdentity(); var currentUserId = umbIdentity?.GetUserId() ?? Core.Constants.Security.SuperUserId; return currentUserId; } + private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, IPrincipal currentUser, int affectedUserId, string affectedUsername) { var currentUserId = GetCurrentUserId(currentUser); var ip = IpResolver.GetCurrentRequestIpAddress(); return new IdentityAuditEventArgs(auditEvent, ip, currentUserId, string.Empty, affectedUserId, affectedUsername); } + private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, BackOfficeIdentityUser currentUser, int affectedUserId, string affectedUsername) { var currentUserId = currentUser.Id; diff --git a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs index 4a9ebcaf46..590edf397a 100644 --- a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; @@ -38,8 +38,21 @@ namespace Umbraco.Web.BackOffice.Security private readonly IUserService _userService; private readonly IIpResolver _ipResolver; private readonly ISystemClock _systemClock; - private readonly LinkGenerator _linkGenerator; + /// + /// Initializes a new instance of the class. + /// + /// The + /// The + /// The options + /// The options + /// The + /// The + /// The + /// The + /// The + /// The + /// The public ConfigureBackOfficeCookieOptions( IServiceProvider serviceProvider, IUmbracoContextAccessor umbracoContextAccessor, @@ -51,8 +64,7 @@ namespace Umbraco.Web.BackOffice.Security IRequestCache requestCache, IUserService userService, IIpResolver ipResolver, - ISystemClock systemClock, - LinkGenerator linkGenerator) + ISystemClock systemClock) { _serviceProvider = serviceProvider; _umbracoContextAccessor = umbracoContextAccessor; @@ -65,15 +77,20 @@ namespace Umbraco.Web.BackOffice.Security _userService = userService; _ipResolver = ipResolver; _systemClock = systemClock; - _linkGenerator = linkGenerator; } + /// public void Configure(string name, CookieAuthenticationOptions options) { - if (name != Constants.Security.BackOfficeAuthenticationType) return; + if (name != Constants.Security.BackOfficeAuthenticationType) + { + return; + } + Configure(options); } + /// public void Configure(CookieAuthenticationOptions options) { options.SlidingExpiration = true; @@ -94,21 +111,18 @@ namespace Umbraco.Web.BackOffice.Security // NOTE: This is borrowed directly from aspnetcore source // Note: the purpose for the data protector must remain fixed for interop to work. - var dataProtector = options.DataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", Constants.Security.BackOfficeAuthenticationType, "v2"); + IDataProtector dataProtector = options.DataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", Constants.Security.BackOfficeAuthenticationType, "v2"); var ticketDataFormat = new TicketDataFormat(dataProtector); options.TicketDataFormat = new BackOfficeSecureDataFormat(_globalSettings.TimeOutInMinutes, ticketDataFormat); - //Custom cookie manager so we can filter requests + // Custom cookie manager so we can filter requests options.CookieManager = new BackOfficeCookieManager( _umbracoContextAccessor, _runtimeState, _hostingEnvironment, _globalSettings, - _requestCache, - _linkGenerator); - // _explicitPaths); TODO: Implement this once we do OAuth somehow - + _requestCache); // _explicitPaths); TODO: Implement this once we do OAuth somehow options.Events = new CookieAuthenticationEvents { @@ -119,22 +133,22 @@ namespace Umbraco.Web.BackOffice.Security // It would be possible to re-use the default behavior if any of these need to be set but that must be taken into account else // our back office requests will not function correctly. For now we don't need to set/configure any of these callbacks because // the defaults work fine with our setup. - OnValidatePrincipal = async ctx => { // We need to resolve the BackOfficeSecurityStampValidator per request as a requirement (even in aspnetcore they do this) - var securityStampValidator = ctx.HttpContext.RequestServices.GetRequiredService(); - // Same goes for the signinmanager - var signInManager = ctx.HttpContext.RequestServices.GetRequiredService(); + BackOfficeSecurityStampValidator securityStampValidator = ctx.HttpContext.RequestServices.GetRequiredService(); - var backOfficeIdentity = ctx.Principal.GetUmbracoIdentity(); + // Same goes for the signinmanager + IBackOfficeSignInManager signInManager = ctx.HttpContext.RequestServices.GetRequiredService(); + + UmbracoBackOfficeIdentity backOfficeIdentity = ctx.Principal.GetUmbracoIdentity(); if (backOfficeIdentity == null) { ctx.RejectPrincipal(); await signInManager.SignOutAsync(); } - //ensure the thread culture is set + // ensure the thread culture is set backOfficeIdentity.EnsureCulture(); await EnsureValidSessionId(ctx); @@ -154,19 +168,19 @@ namespace Umbraco.Web.BackOffice.Security OnSigningIn = ctx => { // occurs when sign in is successful but before the ticket is written to the outbound cookie - - var backOfficeIdentity = ctx.Principal.GetUmbracoIdentity(); + UmbracoBackOfficeIdentity backOfficeIdentity = ctx.Principal.GetUmbracoIdentity(); if (backOfficeIdentity != null) { - //generate a session id and assign it - //create a session token - if we are configured and not in an upgrade state then use the db, otherwise just generate one - var session = _runtimeState.Level == RuntimeLevel.Run + // generate a session id and assign it + // create a session token - if we are configured and not in an upgrade state then use the db, otherwise just generate one + Guid session = _runtimeState.Level == RuntimeLevel.Run ? _userService.CreateLoginSession(backOfficeIdentity.Id, _ipResolver.GetCurrentRequestIpAddress()) : Guid.NewGuid(); - //add our session claim + // add our session claim backOfficeIdentity.AddClaim(new Claim(Constants.Security.SessionIdClaimType, session.ToString(), ClaimValueTypes.String, UmbracoBackOfficeIdentity.Issuer, UmbracoBackOfficeIdentity.Issuer, backOfficeIdentity)); - //since it is a cookie-based authentication add that claim + + // since it is a cookie-based authentication add that claim backOfficeIdentity.AddClaim(new Claim(ClaimTypes.CookiePath, "/", ClaimValueTypes.String, UmbracoBackOfficeIdentity.Issuer, UmbracoBackOfficeIdentity.Issuer, backOfficeIdentity)); } @@ -177,18 +191,18 @@ namespace Umbraco.Web.BackOffice.Security // occurs when sign in is successful and after the ticket is written to the outbound cookie // When we are signed in with the cookie, assign the principal to the current HttpContext - ctx.HttpContext.User = ctx.Principal; + ctx.HttpContext.User = ctx.Principal; return Task.CompletedTask; }, OnSigningOut = ctx => { - //Clear the user's session on sign out + // Clear the user's session on sign out if (ctx.HttpContext?.User?.Identity != null) { var claimsIdentity = ctx.HttpContext.User.Identity as ClaimsIdentity; var sessionId = claimsIdentity.FindFirstValue(Constants.Security.SessionIdClaimType); - if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out var guidSession)) + if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out Guid guidSession)) { _userService.ClearLoginSession(guidSession); } @@ -225,10 +239,13 @@ namespace Umbraco.Web.BackOffice.Security /// private async Task EnsureValidSessionId(CookieValidatePrincipalContext context) { - if (_runtimeState.Level != RuntimeLevel.Run) return; - - using var scope = _serviceProvider.CreateScope(); - var validator = scope.ServiceProvider.GetRequiredService(); + if (_runtimeState.Level != RuntimeLevel.Run) + { + return; + } + + using IServiceScope scope = _serviceProvider.CreateScope(); + BackOfficeSessionIdValidator validator = scope.ServiceProvider.GetRequiredService(); await validator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context); } @@ -236,21 +253,24 @@ namespace Umbraco.Web.BackOffice.Security /// Ensures the ticket is renewed if the is set to true /// and the current request is for the get user seconds endpoint /// - /// + /// The private void EnsureTicketRenewalIfKeepUserLoggedIn(CookieValidatePrincipalContext context) { - if (!_securitySettings.KeepUserLoggedIn) return; + if (!_securitySettings.KeepUserLoggedIn) + { + return; + } - var currentUtc = _systemClock.UtcNow; - var issuedUtc = context.Properties.IssuedUtc; - var expiresUtc = context.Properties.ExpiresUtc; + DateTimeOffset currentUtc = _systemClock.UtcNow; + DateTimeOffset? issuedUtc = context.Properties.IssuedUtc; + DateTimeOffset? expiresUtc = context.Properties.ExpiresUtc; if (expiresUtc.HasValue && issuedUtc.HasValue) { - var timeElapsed = currentUtc.Subtract(issuedUtc.Value); - var timeRemaining = expiresUtc.Value.Subtract(currentUtc); + TimeSpan timeElapsed = currentUtc.Subtract(issuedUtc.Value); + TimeSpan timeRemaining = expiresUtc.Value.Subtract(currentUtc); - //if it's time to renew, then do it + // if it's time to renew, then do it if (timeRemaining < timeElapsed) { context.ShouldRenew = true; From de03dae46f9edcee7016163c20fd07b266faf026 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 3 Dec 2020 23:49:32 +1100 Subject: [PATCH 39/63] Moving namespaces, cleaning up some stuff on the underlying base identity classes --- .../Models/Identity/IIdentityUser.cs | 26 ++ .../Models/Identity/IIdentityUserLogin.cs | 15 +- .../Models/Identity/IdentityUser.cs | 71 ++-- .../Models/Identity/IdentityUserClaim.cs | 26 +- .../Models/Identity/IdentityUserLogin.cs | 2 +- .../Models/Identity/IdentityUserRole.cs | 19 +- .../Security/AuthenticationExtensions.cs | 1 - .../BackOfficeIdentityUser.cs | 51 +-- .../BackOfficeUserPasswordCheckerResult.cs | 2 +- .../ClaimsPrincipalExtensions.cs | 4 +- .../IBackOfficeUserPasswordChecker.cs | 4 +- .../IdentityAuditEventArgs.cs | 6 +- .../IdentityMapDefinition.cs | 6 +- .../UmbracoBackOfficeIdentity.cs | 38 +- .../BackOfficeClaimsPrincipalFactory.cs | 11 +- .../BackOffice/BackOfficeIdentityBuilder.cs | 1 + .../BackOffice/BackOfficeUserStore.cs | 5 +- .../BackOffice/BackOfficeUserValidator.cs | 1 + .../BackOffice/IBackOfficeUserManager.cs | 323 +---------------- .../BackOffice/IUmbracoUserManager.cs | 326 ++++++++++++++++++ .../CoreMappingProfiles.cs | 2 +- .../Security/SignOutAuditEventArgs.cs | 4 +- .../Security/UserInviteEventArgs.cs | 1 + .../TestServerTest/TestAuthHandler.cs | 2 +- ...kOfficeServiceCollectionExtensionsTests.cs | 1 + .../AutoFixture/AutoMoqDataAttribute.cs | 2 +- .../BackOfficeClaimsPrincipalFactoryTests.cs | 5 +- .../UmbracoBackOfficeIdentityTests.cs | 2 +- .../ClaimsPrincipalExtensionsTests.cs | 2 +- .../Controllers/UsersControllerUnitTests.cs | 1 + .../Security/BackOfficeAntiforgeryTests.cs | 28 +- .../OwinDataProtectorTokenProviderTests.cs | 2 +- .../AuthenticateEverythingMiddleware.cs | 4 +- .../TestControllerActivatorBase.cs | 1 - .../Extensions/IdentityBuilderExtensions.cs | 2 +- .../CheckIfUserTicketDataIsStaleAttribute.cs | 2 +- .../Security/BackOfficePasswordHasher.cs | 1 - .../Security/BackOfficeSecureDataFormat.cs | 2 +- .../BackOfficeSecurityStampValidator.cs | 2 +- .../Security/BackOfficeSignInManager.cs | 1 - .../Security/BackOfficeUserManager.cs | 1 + .../Security/BackOfficeUserManagerAuditer.cs | 1 + .../ConfigureBackOfficeCookieOptions.cs | 1 - .../Security/ExternalSignInAutoLinkOptions.cs | 2 +- .../Security/IBackOfficeSignInManager.cs | 2 +- .../Extensions/HttpContextExtensions.cs | 2 +- ...eDirectoryBackOfficeUserPasswordChecker.cs | 3 +- .../Security/AuthenticationExtensions.cs | 2 +- .../Security/BackOfficeSignInManager.cs | 1 + .../Security/FixWindowsAuthMiddlware.cs | 2 +- .../IBackOfficeUserPasswordChecker.cs | 2 +- .../OwinDataProtectorTokenProvider.cs | 2 +- .../Security/UmbracoSecureDataFormat.cs | 2 +- 53 files changed, 550 insertions(+), 478 deletions(-) create mode 100644 src/Umbraco.Core/Models/Identity/IIdentityUser.cs rename src/Umbraco.Core/{BackOffice => Security}/BackOfficeIdentityUser.cs (90%) rename src/Umbraco.Core/{BackOffice => Security}/BackOfficeUserPasswordCheckerResult.cs (87%) rename src/Umbraco.Core/{BackOffice => Security}/ClaimsPrincipalExtensions.cs (98%) rename src/Umbraco.Core/{BackOffice => Security}/IBackOfficeUserPasswordChecker.cs (93%) rename src/Umbraco.Core/{BackOffice => Security}/IdentityAuditEventArgs.cs (96%) rename src/Umbraco.Core/{BackOffice => Security}/IdentityMapDefinition.cs (97%) rename src/Umbraco.Core/{BackOffice => Security}/UmbracoBackOfficeIdentity.cs (85%) create mode 100644 src/Umbraco.Infrastructure/BackOffice/IUmbracoUserManager.cs diff --git a/src/Umbraco.Core/Models/Identity/IIdentityUser.cs b/src/Umbraco.Core/Models/Identity/IIdentityUser.cs new file mode 100644 index 0000000000..fa7f52c710 --- /dev/null +++ b/src/Umbraco.Core/Models/Identity/IIdentityUser.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Identity +{ + public interface IIdentityUser + { + int AccessFailedCount { get; set; } + //ICollection Claims { get; } + string Email { get; set; } + bool EmailConfirmed { get; set; } + TKey Id { get; set; } + DateTime? LastLoginDateUtc { get; set; } + DateTime? LastPasswordChangeDateUtc { get; set; } + bool LockoutEnabled { get; set; } + DateTime? LockoutEndDateUtc { get; set; } + //ICollection Logins { get; } + string PasswordHash { get; set; } + string PhoneNumber { get; set; } + bool PhoneNumberConfirmed { get; set; } + //ICollection Roles { get; } + string SecurityStamp { get; set; } + bool TwoFactorEnabled { get; set; } + string UserName { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs index cbe5b47b38..62c92de16d 100644 --- a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs +++ b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs @@ -1,27 +1,30 @@ -using Umbraco.Core.Models.Entities; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Identity { - + /// + /// An external login provider linked to a user + /// + /// The PK type for the user public interface IIdentityUserLogin : IEntity, IRememberBeingDirty { /// - /// The login provider for the login (i.e. Facebook, Google) + /// Gets or sets the login provider for the login (i.e. Facebook, Google) /// string LoginProvider { get; set; } /// - /// Key representing the login for the provider + /// Gets or sets key representing the login for the provider /// string ProviderKey { get; set; } /// - /// User Id for the user who owns this login + /// Gets or sets user Id for the user who owns this login /// int UserId { get; set; } /// - /// Used to store any arbitrary data for the user and external provider - like user tokens returned from the provider + /// Gets or sets any arbitrary data for the user and external provider - like user tokens returned from the provider /// string UserData { get; set; } } diff --git a/src/Umbraco.Core/Models/Identity/IdentityUser.cs b/src/Umbraco.Core/Models/Identity/IdentityUser.cs index 093e42c1e7..dd3841d2c8 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUser.cs @@ -1,26 +1,30 @@ -using System; +using System; using System.Collections.Generic; namespace Umbraco.Core.Models.Identity { /// - /// Default IUser implementation + /// Abstract class for use in Umbraco Identity /// - /// + /// The type of user login + /// The type of user role + /// The type of user claims /// - /// This class normally exists inside of the EntityFramework library, not sure why MS chose to explicitly put it there but we don't want - /// references to that so we will create our own here + /// This class was originally borrowed from the EF implementation in Identity prior to netcore. + /// The new IdentityUser in netcore does not have properties such as Claims, Roles and Logins and those are instead + /// by default managed with their default user store backed by EF which utilizes EF's change tracking to track these values + /// to a user. We will continue using this approach since it works fine for what we need which does the change tracking of + /// claims, roles and logins directly on the user model. /// - public class IdentityUser + public abstract class IdentityUser where TLogin : IIdentityUserLogin - //NOTE: Making our role id a string - where TRole : IdentityUserRole - where TClaim : IdentityUserClaim + where TRole : IdentityUserRole + where TClaim : IdentityUserClaim { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public IdentityUser() + protected IdentityUser() { Claims = new List(); Roles = new List(); @@ -28,87 +32,96 @@ namespace Umbraco.Core.Models.Identity } /// - /// Last login date + /// Gets or sets last login date /// public virtual DateTime? LastLoginDateUtc { get; set; } /// - /// Email + /// Gets or sets email /// public virtual string Email { get; set; } /// - /// True if the email is confirmed, default is false + /// Gets or sets a value indicating whether the email is confirmed, default is false /// public virtual bool EmailConfirmed { get; set; } /// - /// The salted/hashed form of the user password + /// Gets or sets the salted/hashed form of the user password /// public virtual string PasswordHash { get; set; } /// - /// A random value that should change whenever a users credentials have changed (password changed, login removed) + /// Gets or sets a random value that should change whenever a users credentials have changed (password changed, login removed) /// public virtual string SecurityStamp { get; set; } /// - /// PhoneNumber for the user + /// Gets or sets a phone Number for the user /// + /// + /// This is unused until we or an end-user requires this value for 2FA + /// public virtual string PhoneNumber { get; set; } /// - /// True if the phone number is confirmed, default is false + /// Gets or sets a value indicating whether true if the phone number is confirmed, default is false /// + /// + /// This is unused until we or an end-user requires this value for 2FA + /// public virtual bool PhoneNumberConfirmed { get; set; } /// - /// Is two factor enabled for the user + /// Gets or sets a value indicating whether is two factor enabled for the user /// + /// + /// This is unused until we or an end-user requires this value for 2FA + /// public virtual bool TwoFactorEnabled { get; set; } /// - /// DateTime in UTC when lockout ends, any time in the past is considered not locked out. + /// Gets or sets dateTime in UTC when lockout ends, any time in the past is considered not locked out. /// public virtual DateTime? LockoutEndDateUtc { get; set; } /// - /// DateTime in UTC when the password was last changed. + /// Gets or sets dateTime in UTC when the password was last changed. /// public virtual DateTime? LastPasswordChangeDateUtc { get; set; } /// - /// Is lockout enabled for this user + /// Gets or sets a value indicating whether is lockout enabled for this user /// public virtual bool LockoutEnabled { get; set; } /// - /// Used to record failures for the purposes of lockout + /// Gets or sets the value to record failures for the purposes of lockout /// public virtual int AccessFailedCount { get; set; } /// - /// Navigation property for user roles + /// Gets the user roles collection /// public virtual ICollection Roles { get; } /// - /// Navigation property for user claims + /// Gets navigation the user claims collection /// public virtual ICollection Claims { get; } /// - /// Navigation property for user logins + /// Gets the user logins collection /// public virtual ICollection Logins { get; } /// - /// User ID (Primary Key) + /// Gets or sets user ID (Primary Key) /// - public virtual TKey Id { get; set; } + public virtual int Id { get; set; } /// - /// User name + /// Gets or sets user name /// public virtual string UserName { get; set; } } diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs b/src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs index e117d2fd13..2524463284 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs @@ -1,37 +1,27 @@ -namespace Umbraco.Core.Models.Identity +namespace Umbraco.Core.Models.Identity { /// /// EntityType that represents one specific user claim - /// /// - /// - /// - /// This class normally exists inside of the EntityFramework library, not sure why MS chose to explicitly put it there but we don't want - /// references to that so we will create our own here - /// - public class IdentityUserClaim + public class IdentityUserClaim { /// - /// Primary key - /// + /// Gets or sets primary key /// - public virtual int Id { get; set; } + public virtual string Id { get; set; } // TODO: Not used /// - /// User Id for the user who owns this login - /// + /// Gets or sets user Id for the user who owns this login /// - public virtual TKey UserId { get; set; } + public virtual string UserId { get; set; } /// - /// Claim type - /// + /// Gets or sets claim type /// public virtual string ClaimType { get; set; } /// - /// Claim value - /// + /// Gets or sets claim value /// public virtual string ClaimValue { get; set; } } diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs index c13b28461d..18e8d4694b 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Identity diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs b/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs index ba9e87e46c..39ed65112d 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs @@ -1,26 +1,19 @@ -namespace Umbraco.Core.Models.Identity +namespace Umbraco.Core.Models.Identity { /// /// EntityType that represents a user belonging to a role - /// /// /// - /// - /// This class normally exists inside of the EntityFramework library, not sure why MS chose to explicitly put it there but we don't want - /// references to that so we will create our own here - /// - public class IdentityUserRole + public class IdentityUserRole { /// - /// UserId for the user that is in the role - /// + /// Gets or sets userId for the user that is in the role /// - public virtual TKey UserId { get; set; } + public virtual string UserId { get; set; } /// - /// RoleId for the role - /// + /// Gets or sets roleId for the role /// - public virtual TKey RoleId { get; set; } + public virtual string RoleId { get; set; } } } diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs index edc11bcac2..607c4748cc 100644 --- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -5,7 +5,6 @@ using System.Globalization; using System.Security.Principal; using System.Text; using System.Threading; -using Umbraco.Core.BackOffice; namespace Umbraco.Core.Security { diff --git a/src/Umbraco.Core/BackOffice/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs similarity index 90% rename from src/Umbraco.Core/BackOffice/BackOfficeIdentityUser.cs rename to src/Umbraco.Core/Security/BackOfficeIdentityUser.cs index 027e7c0904..e8e036b51b 100644 --- a/src/Umbraco.Core/BackOffice/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; @@ -10,14 +10,13 @@ using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { - public class BackOfficeIdentityUser : IdentityUser, IdentityUserClaim>, IRememberBeingDirty + public class BackOfficeIdentityUser : IdentityUser, IRememberBeingDirty { private string _email; private string _userName; private int _id; - private bool _hasIdentity; private DateTime? _lastLoginDateUtc; private bool _emailConfirmed; private string _name; @@ -36,23 +35,32 @@ namespace Umbraco.Core.BackOffice /// /// 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) { - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); + if (string.IsNullOrWhiteSpace(username)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); + } + + if (string.IsNullOrWhiteSpace(culture)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); + } var user = new BackOfficeIdentityUser(globalSettings, Array.Empty()); user.DisableChangeTracking(); user._userName = username; user._email = email; - //we are setting minvalue here because the default is "0" which is the id of the admin user - //which we cannot allow because the admin user will always exist + + // we are setting minvalue here because the default is "0" which is the id of the admin user + // which we cannot allow because the admin user will always exist user._id = int.MinValue; - user._hasIdentity = false; + user.HasIdentity = false; user._culture = culture; user._name = name; user.EnableChangeTracking(); @@ -67,7 +75,7 @@ namespace Umbraco.Core.BackOffice _culture = globalSettings.DefaultUILanguage; // must initialize before setting groups - _roles = new ObservableCollection>(); + _roles = new ObservableCollection(); _roles.CollectionChanged += _roles_CollectionChanged; // use the property setters - they do more than just setting a field @@ -75,7 +83,7 @@ namespace Umbraco.Core.BackOffice } /// - /// Creates an existing user with the specified groups + /// Initializes a new instance of the class. /// /// /// @@ -90,7 +98,7 @@ namespace Umbraco.Core.BackOffice /// /// Returns true if an Id has been set on this object this will be false if the object is new and not persisted to the database /// - public bool HasIdentity => _hasIdentity; + public bool HasIdentity { get; private set; } public int[] CalculatedMediaStartNodeIds { get; set; } public int[] CalculatedContentStartNodeIds { get; set; } @@ -101,7 +109,7 @@ namespace Umbraco.Core.BackOffice set { _id = value; - _hasIdentity = true; + HasIdentity = true; } } @@ -192,7 +200,8 @@ namespace Umbraco.Core.BackOffice get => _startContentIds; set { - if (value == null) value = new int[0]; + if (value == null) + value = new int[0]; _beingDirty.SetPropertyValueAndDetectChanges(value, ref _startContentIds, nameof(StartContentIds), StartIdsComparer); } } @@ -205,7 +214,8 @@ namespace Umbraco.Core.BackOffice get => _startMediaIds; set { - if (value == null) value = new int[0]; + if (value == null) + value = new int[0]; _beingDirty.SetPropertyValueAndDetectChanges(value, ref _startMediaIds, nameof(StartMediaIds), StartIdsComparer); } } @@ -237,7 +247,7 @@ namespace Umbraco.Core.BackOffice //now clear all roles and re-add them _roles.CollectionChanged -= _roles_CollectionChanged; _roles.Clear(); - foreach (var identityUserRole in _groups.Select(x => new IdentityUserRole + foreach (var identityUserRole in _groups.Select(x => new IdentityUserRole { RoleId = x.Alias, UserId = Id.ToString() @@ -288,7 +298,8 @@ namespace Umbraco.Core.BackOffice get { // return if it exists - if (_logins != null) return _logins; + if (_logins != null) + return _logins; _logins = new ObservableCollection(); @@ -318,7 +329,7 @@ namespace Umbraco.Core.BackOffice _beingDirty.OnPropertyChanged(nameof(Roles)); } - private readonly ObservableCollection> _roles; + private readonly ObservableCollection _roles; /// /// helper method to easily add a role without having to deal with IdentityUserRole{T} @@ -329,7 +340,7 @@ namespace Umbraco.Core.BackOffice /// public void AddRole(string role) { - Roles.Add(new IdentityUserRole + Roles.Add(new IdentityUserRole { UserId = Id.ToString(), RoleId = role @@ -339,7 +350,7 @@ namespace Umbraco.Core.BackOffice /// /// Override Roles because the value of these are the user's group aliases /// - public override ICollection> Roles => _roles; + public override ICollection Roles => _roles; /// /// Used to set a lazy call back to populate the user's Login list diff --git a/src/Umbraco.Core/BackOffice/BackOfficeUserPasswordCheckerResult.cs b/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs similarity index 87% rename from src/Umbraco.Core/BackOffice/BackOfficeUserPasswordCheckerResult.cs rename to src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs index 7936fab682..c640c85d0c 100644 --- a/src/Umbraco.Core/BackOffice/BackOfficeUserPasswordCheckerResult.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// /// The result returned from the IBackOfficeUserPasswordChecker diff --git a/src/Umbraco.Core/BackOffice/ClaimsPrincipalExtensions.cs b/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs similarity index 98% rename from src/Umbraco.Core/BackOffice/ClaimsPrincipalExtensions.cs rename to src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs index 7cbca0428a..395465cfb7 100644 --- a/src/Umbraco.Core/BackOffice/ClaimsPrincipalExtensions.cs +++ b/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.Globalization; using System.Linq; using System.Security.Claims; using System.Security.Principal; using Umbraco.Core; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Extensions { diff --git a/src/Umbraco.Core/BackOffice/IBackOfficeUserPasswordChecker.cs b/src/Umbraco.Core/Security/IBackOfficeUserPasswordChecker.cs similarity index 93% rename from src/Umbraco.Core/BackOffice/IBackOfficeUserPasswordChecker.cs rename to src/Umbraco.Core/Security/IBackOfficeUserPasswordChecker.cs index 5874337f4a..45f5ea44e2 100644 --- a/src/Umbraco.Core/BackOffice/IBackOfficeUserPasswordChecker.cs +++ b/src/Umbraco.Core/Security/IBackOfficeUserPasswordChecker.cs @@ -1,6 +1,6 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// /// Used by the BackOfficeUserManager to check the username/password which allows for developers to more easily diff --git a/src/Umbraco.Core/BackOffice/IdentityAuditEventArgs.cs b/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs similarity index 96% rename from src/Umbraco.Core/BackOffice/IdentityAuditEventArgs.cs rename to src/Umbraco.Core/Security/IdentityAuditEventArgs.cs index 1d51c45074..454d651944 100644 --- a/src/Umbraco.Core/BackOffice/IdentityAuditEventArgs.cs +++ b/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs @@ -1,7 +1,7 @@ -using System; +using System; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// @@ -58,7 +58,7 @@ namespace Umbraco.Core.BackOffice DateTimeUtc = DateTime.UtcNow; Action = action; IpAddress = ipAddress; - Comment = comment; + Comment = comment; PerformingUser = performingUser; AffectedUsername = affectedUsername; AffectedUser = affectedUser; diff --git a/src/Umbraco.Core/BackOffice/IdentityMapDefinition.cs b/src/Umbraco.Core/Security/IdentityMapDefinition.cs similarity index 97% rename from src/Umbraco.Core/BackOffice/IdentityMapDefinition.cs rename to src/Umbraco.Core/Security/IdentityMapDefinition.cs index 61fdf82d19..26a5d11f6e 100644 --- a/src/Umbraco.Core/BackOffice/IdentityMapDefinition.cs +++ b/src/Umbraco.Core/Security/IdentityMapDefinition.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.Extensions.Options; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; @@ -7,7 +7,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { public class IdentityMapDefinition : IMapDefinition { @@ -65,7 +65,7 @@ namespace Umbraco.Core.BackOffice target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); // project CultureInfo to string target.IsApproved = source.IsApproved; target.SecurityStamp = source.SecurityStamp; - target.LockoutEndDateUtc = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?) null; + target.LockoutEndDateUtc = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null; // this was in AutoMapper but does not have a setter anyways //target.AllowedSections = source.AllowedSections.ToArray(), diff --git a/src/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentity.cs b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs similarity index 85% rename from src/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentity.cs rename to src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs index 9a60c5d64f..3430814f83 100644 --- a/src/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentity.cs +++ b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs @@ -1,9 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// @@ -15,13 +15,12 @@ namespace Umbraco.Core.BackOffice // TODO: Ideally we remove this class and only deal with ClaimsIdentity as a best practice. All things relevant to our own // identity are part of claims. This class would essentially become extension methods on a ClaimsIdentity for resolving // values from it. - public static bool FromClaimsIdentity(ClaimsIdentity identity, out UmbracoBackOfficeIdentity backOfficeIdentity) { - //validate that all claims exist + // validate that all claims exist foreach (var t in RequiredBackOfficeIdentityClaimTypes) { - //if the identity doesn't have the claim, or the claim value is null + // if the identity doesn't have the claim, or the claim value is null if (identity.HasClaim(x => x.Type == t) == false || identity.HasClaim(x => x.Type == t && x.Value.IsNullOrWhiteSpace())) { backOfficeIdentity = null; @@ -59,11 +58,16 @@ namespace Umbraco.Core.BackOffice string securityStamp, IEnumerable allowedApps, IEnumerable roles) : base(Enumerable.Empty(), Constants.Security.BackOfficeAuthenticationType) //this ctor is used to ensure the IsAuthenticated property is true { - if (allowedApps == null) throw new ArgumentNullException(nameof(allowedApps)); - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); - if (string.IsNullOrWhiteSpace(realName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName)); - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); - if (string.IsNullOrWhiteSpace(securityStamp)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp)); + if (allowedApps == null) + throw new ArgumentNullException(nameof(allowedApps)); + if (string.IsNullOrWhiteSpace(username)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); + if (string.IsNullOrWhiteSpace(realName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName)); + if (string.IsNullOrWhiteSpace(culture)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); + if (string.IsNullOrWhiteSpace(securityStamp)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp)); AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, securityStamp, allowedApps, roles); } @@ -88,10 +92,14 @@ namespace Umbraco.Core.BackOffice string securityStamp, IEnumerable allowedApps, IEnumerable roles) : base(childIdentity.Claims, Constants.Security.BackOfficeAuthenticationType) { - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); - if (string.IsNullOrWhiteSpace(realName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName)); - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); - if (string.IsNullOrWhiteSpace(securityStamp)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp)); + if (string.IsNullOrWhiteSpace(username)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); + if (string.IsNullOrWhiteSpace(realName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName)); + if (string.IsNullOrWhiteSpace(culture)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture)); + if (string.IsNullOrWhiteSpace(securityStamp)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp)); AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, securityStamp, allowedApps, roles); } @@ -205,7 +213,7 @@ namespace Umbraco.Core.BackOffice public string SecurityStamp => this.FindFirstValue(Constants.Security.SecurityStampClaimType); - public string[] Roles => this.FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToArray(); + public string[] Roles => FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToArray(); /// /// Overridden to remove any temporary claims that shouldn't be copied diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs b/src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs index 22ea4423d2..380ed452d0 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs +++ b/src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs @@ -4,6 +4,7 @@ using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; +using Umbraco.Core.Security; namespace Umbraco.Core.BackOffice { @@ -36,11 +37,13 @@ namespace Umbraco.Core.BackOffice ClaimsIdentity baseIdentity = await base.GenerateClaimsAsync(user); + // TODO: How to flow claims then? This is most likely built into aspnetcore now and this is not the way + // now we can flow any custom claims that the actual user has currently assigned which could be done in the OnExternalLogin callback - foreach (Models.Identity.IdentityUserClaim claim in user.Claims) - { - baseIdentity.AddClaim(new Claim(claim.ClaimType, claim.ClaimValue)); - } + //foreach (Models.Identity.IdentityUserClaim claim in user.Claims) + //{ + // baseIdentity.AddClaim(new Claim(claim.ClaimType, claim.ClaimValue)); + //} // TODO: We want to remove UmbracoBackOfficeIdentity and only rely on ClaimsIdentity, once // that is done then we'll create a ClaimsIdentity with all of the requirements here instead diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityBuilder.cs b/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityBuilder.cs index 5bae03cad6..90c2823122 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityBuilder.cs +++ b/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityBuilder.cs @@ -3,6 +3,7 @@ using System.Reflection; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Infrastructure.BackOffice { diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs index b271f5aa41..e297eca86d 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs +++ b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data; using System.Linq; @@ -13,6 +13,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Scoping; +using Umbraco.Core.Security; using Umbraco.Core.Services; namespace Umbraco.Core.BackOffice @@ -23,7 +24,7 @@ namespace Umbraco.Core.BackOffice IUserLoginStore, IUserRoleStore, IUserSecurityStampStore, - IUserLockoutStore, + IUserLockoutStore, IUserSessionStore // TODO: This would require additional columns/tables and then a lot of extra coding support to make this happen natively within umbraco diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserValidator.cs b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserValidator.cs index 131bd08ac9..b7cbb7555d 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserValidator.cs +++ b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserValidator.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; +using Umbraco.Core.Security; namespace Umbraco.Core.BackOffice { diff --git a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs b/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs index c026c256f5..be4bd194f9 100644 --- a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs +++ b/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs @@ -1,324 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Security.Claims; -using System.Security.Principal; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.Models.ContentEditing; +using Umbraco.Core.Security; namespace Umbraco.Core.BackOffice { - public interface IBackOfficeUserManager : IBackOfficeUserManager + /// + /// The user manager for the back office + /// + public interface IBackOfficeUserManager : IUmbracoUserManager { - - } - public interface IBackOfficeUserManager: IDisposable - where TUser : BackOfficeIdentityUser - { - Task GetUserIdAsync(TUser user); - - Task GetUserAsync(ClaimsPrincipal principal); - - string GetUserId(ClaimsPrincipal principal); - - Task> GetLoginsAsync(TUser user); - - Task DeleteAsync(TUser user); - - Task FindByLoginAsync(string loginProvider, string providerKey); - - /// - /// Finds and returns a user, if any, who has the specified . - /// - /// The user ID to search for. - /// - /// The that represents the asynchronous operation, containing the user matching the specified if it exists. - /// - Task FindByIdAsync(string userId); - - /// - /// Generates a password reset token for the specified , using - /// the configured password reset token provider. - /// - /// The user to generate a password reset token for. - /// The that represents the asynchronous operation, - /// containing a password reset token for the specified . - Task GeneratePasswordResetTokenAsync(TUser user); - - /// - /// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event - /// - /// - /// - /// - /// - /// - /// We use this because in the back office the only way an admin can change another user's password without first knowing their password - /// is to generate a token and reset it, however, when we do this we want to track a password change, not a password reset - /// - Task ChangePasswordWithResetAsync(int userId, string token, string newPassword); - - /// - /// Validates that an email confirmation token matches the specified . - /// - /// The user to validate the token against. - /// The email confirmation token to validate. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task ConfirmEmailAsync(TUser user, string token); - - /// - /// Gets the user, if any, associated with the normalized value of the specified email address. - /// Note: Its recommended that identityOptions.User.RequireUniqueEmail be set to true when using this method, otherwise - /// the store may throw if there are users with duplicate emails. - /// - /// The email address to return the user for. - /// - /// The task object containing the results of the asynchronous lookup operation, the user, if any, associated with a normalized value of the specified email address. - /// - Task FindByEmailAsync(string email); - - /// - /// Resets the 's password to the specified after - /// validating the given password reset . - /// - /// The user whose password should be reset. - /// The password reset token to verify. - /// The new password to set if reset token verification succeeds. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task ResetPasswordAsync(TUser user, string token, string newPassword); - - /// - /// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date - /// - /// - /// - /// - /// In the ASP.NET Identity world, there is only one value for being locked out, in Umbraco we have 2 so when checking this for Umbraco we need to check both values - /// - Task IsLockedOutAsync(TUser user); - - /// - /// Locks out a user until the specified end date has passed. Setting a end date in the past immediately unlocks a user. - /// - /// The user whose lockout date should be set. - /// The after which the 's lockout should end. - /// The that represents the asynchronous operation, containing the of the operation. - Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd); - - /// - /// Gets a flag indicating whether the email address for the specified has been verified, true if the email address is verified otherwise - /// false. - /// - /// The user whose email confirmation status should be returned. - /// - /// The task object containing the results of the asynchronous operation, a flag indicating whether the email address for the specified - /// has been confirmed or not. - /// - Task IsEmailConfirmedAsync(TUser user); - - /// - /// Updates the specified in the backing store. - /// - /// The user to update. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task UpdateAsync(TUser user); - - /// - /// Returns a flag indicating whether the specified is valid for - /// the given and . - /// - /// The user to validate the token against. - /// The token provider used to generate the token. - /// The purpose the token should be generated for. - /// The token to validate - /// - /// The that represents the asynchronous operation, returning true if the - /// is valid, otherwise false. - /// - Task VerifyUserTokenAsync(TUser user, string tokenProvider, string purpose, - string token); - - /// - /// Adds the to the specified only if the user - /// does not already have a password. - /// - /// The user whose password should be set. - /// The password to set. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task AddPasswordAsync(TUser user, string password); - - - /// - /// Returns a flag indicating whether the given is valid for the - /// specified . - /// - /// The user whose password should be validated. - /// The password to validate - /// The that represents the asynchronous operation, containing true if - /// the specified matches the one store for the , - /// otherwise false. - Task CheckPasswordAsync(TUser user, string password); - - /// - /// Changes a user's password after confirming the specified is correct, - /// as an asynchronous operation. - /// - /// The user whose password should be set. - /// The current password to validate before changing. - /// The new password to set for the specified . - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task ChangePasswordAsync(TUser user, string currentPassword, - string newPassword); - - /// - /// Used to validate a user's session - /// - /// - /// - /// - Task ValidateSessionIdAsync(string userId, string sessionId); - - /// - /// Creates the specified in the backing store with no password, - /// as an asynchronous operation. - /// - /// The user to create. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task CreateAsync(TUser user); - - /// - /// Helper method to generate a password for a user based on the current password validator - /// - /// - string GeneratePassword(); - - - /// - /// Generates an email confirmation token for the specified user. - /// - /// The user to generate an email confirmation token for. - /// - /// The that represents the asynchronous operation, an email confirmation token. - /// - Task GenerateEmailConfirmationTokenAsync(TUser user); - - /// - /// Finds and returns a user, if any, who has the specified user name. - /// - /// The user name to search for. - /// - /// The that represents the asynchronous operation, containing the user matching the specified if it exists. - /// - Task FindByNameAsync(string userName); - - /// - /// Increments the access failed count for the user as an asynchronous operation. - /// If the failed access account is greater than or equal to the configured maximum number of attempts, - /// the user will be locked out for the configured lockout time span. - /// - /// The user whose failed access count to increment. - /// The that represents the asynchronous operation, containing the of the operation. - Task AccessFailedAsync(TUser user); - - /// - /// Returns a flag indicating whether the specified has two factor authentication enabled or not, - /// as an asynchronous operation. - /// - /// The user whose two factor authentication enabled status should be retrieved. - /// - /// The that represents the asynchronous operation, true if the specified - /// has two factor authentication enabled, otherwise false. - /// - Task GetTwoFactorEnabledAsync(TUser user); - - /// - /// Gets a list of valid two factor token providers for the specified , - /// as an asynchronous operation. - /// - /// The user the whose two factor authentication providers will be returned. - /// - /// The that represents result of the asynchronous operation, a list of two - /// factor authentication providers for the specified user. - /// - Task> GetValidTwoFactorProvidersAsync(TUser user); - - /// - /// Verifies the specified two factor authentication against the . - /// - /// The user the token is supposed to be for. - /// The provider which will verify the token. - /// The token to verify. - /// - /// The that represents result of the asynchronous operation, true if the token is valid, - /// otherwise false. - /// - Task VerifyTwoFactorTokenAsync(TUser user, string tokenProvider, string token); - - /// - /// Adds an external Microsoft.AspNetCore.Identity.UserLoginInfo to the specified user. - /// - /// The user to add the login to. - /// The external Microsoft.AspNetCore.Identity.UserLoginInfo to add to the specified user. - /// The System.Threading.Tasks.Task that represents the asynchronous operation, containing the Microsoft.AspNetCore.Identity.IdentityResult of the operation. - Task AddLoginAsync(TUser user, UserLoginInfo login); - - /// - /// Attempts to remove the provided external login information from the specified user. and returns a flag indicating whether the removal succeed or not. - /// - /// The user to remove the login information from. - /// The login provide whose information should be removed. - /// The key given by the external login provider for the specified user. - /// The System.Threading.Tasks.Task that represents the asynchronous operation, containing the Microsoft.AspNetCore.Identity.IdentityResult of the operation. - Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey); - - Task ResetAccessFailedCountAsync(TUser user); - - Task GenerateTwoFactorTokenAsync(TUser user, string tokenProvider); - - /// - /// Gets the email address for the specified user. - /// - /// The user whose email should be returned. - /// The task object containing the results of the asynchronous operation, the email address for the specified user. - Task GetEmailAsync(TUser user); - - /// - /// Gets the telephone number, if any, for the specified user. - /// - /// The user whose telephone number should be retrieved. - /// The System.Threading.Tasks.Task that represents the asynchronous operation, containing the user's telephone number, if any. - /// - /// A user can only support a phone number if the BackOfficeUserStore is replaced with another that implements IUserPhoneNumberStore - /// - Task GetPhoneNumberAsync(TUser user); - - // TODO: These are raised from outside the signinmanager and usermanager in the auth and user controllers, - // let's see if there's a way to avoid that and only have these called within signinmanager and usermanager - // which means we can remove these from the interface (things like invite seems like they cannot be moved) - void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, int userId); - void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, int userId); - SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId); - UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser); - bool HasSendingUserInviteEventHandler { get; } - } } diff --git a/src/Umbraco.Infrastructure/BackOffice/IUmbracoUserManager.cs b/src/Umbraco.Infrastructure/BackOffice/IUmbracoUserManager.cs new file mode 100644 index 0000000000..8f8e0ffc50 --- /dev/null +++ b/src/Umbraco.Infrastructure/BackOffice/IUmbracoUserManager.cs @@ -0,0 +1,326 @@ +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Core.BackOffice +{ + + /// + /// A user manager for Umbraco (either back office users or front-end members) + /// + /// The type of user + public interface IUmbracoUserManager : IDisposable + where TUser : BackOfficeIdentityUser + { + Task GetUserIdAsync(TUser user); + + Task GetUserAsync(ClaimsPrincipal principal); + + string GetUserId(ClaimsPrincipal principal); + + Task> GetLoginsAsync(TUser user); + + Task DeleteAsync(TUser user); + + Task FindByLoginAsync(string loginProvider, string providerKey); + + /// + /// Finds and returns a user, if any, who has the specified . + /// + /// The user ID to search for. + /// + /// The that represents the asynchronous operation, containing the user matching the specified if it exists. + /// + Task FindByIdAsync(string userId); + + /// + /// Generates a password reset token for the specified , using + /// the configured password reset token provider. + /// + /// The user to generate a password reset token for. + /// The that represents the asynchronous operation, + /// containing a password reset token for the specified . + Task GeneratePasswordResetTokenAsync(TUser user); + + /// + /// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event + /// + /// + /// + /// + /// + /// + /// We use this because in the back office the only way an admin can change another user's password without first knowing their password + /// is to generate a token and reset it, however, when we do this we want to track a password change, not a password reset + /// + Task ChangePasswordWithResetAsync(int userId, string token, string newPassword); + + /// + /// Validates that an email confirmation token matches the specified . + /// + /// The user to validate the token against. + /// The email confirmation token to validate. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task ConfirmEmailAsync(TUser user, string token); + + /// + /// Gets the user, if any, associated with the normalized value of the specified email address. + /// Note: Its recommended that identityOptions.User.RequireUniqueEmail be set to true when using this method, otherwise + /// the store may throw if there are users with duplicate emails. + /// + /// The email address to return the user for. + /// + /// The task object containing the results of the asynchronous lookup operation, the user, if any, associated with a normalized value of the specified email address. + /// + Task FindByEmailAsync(string email); + + /// + /// Resets the 's password to the specified after + /// validating the given password reset . + /// + /// The user whose password should be reset. + /// The password reset token to verify. + /// The new password to set if reset token verification succeeds. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task ResetPasswordAsync(TUser user, string token, string newPassword); + + /// + /// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date + /// + /// + /// + /// + /// In the ASP.NET Identity world, there is only one value for being locked out, in Umbraco we have 2 so when checking this for Umbraco we need to check both values + /// + Task IsLockedOutAsync(TUser user); + + /// + /// Locks out a user until the specified end date has passed. Setting a end date in the past immediately unlocks a user. + /// + /// The user whose lockout date should be set. + /// The after which the 's lockout should end. + /// The that represents the asynchronous operation, containing the of the operation. + Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd); + + /// + /// Gets a flag indicating whether the email address for the specified has been verified, true if the email address is verified otherwise + /// false. + /// + /// The user whose email confirmation status should be returned. + /// + /// The task object containing the results of the asynchronous operation, a flag indicating whether the email address for the specified + /// has been confirmed or not. + /// + Task IsEmailConfirmedAsync(TUser user); + + /// + /// Updates the specified in the backing store. + /// + /// The user to update. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task UpdateAsync(TUser user); + + /// + /// Returns a flag indicating whether the specified is valid for + /// the given and . + /// + /// The user to validate the token against. + /// The token provider used to generate the token. + /// The purpose the token should be generated for. + /// The token to validate + /// + /// The that represents the asynchronous operation, returning true if the + /// is valid, otherwise false. + /// + Task VerifyUserTokenAsync(TUser user, string tokenProvider, string purpose, + string token); + + /// + /// Adds the to the specified only if the user + /// does not already have a password. + /// + /// The user whose password should be set. + /// The password to set. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task AddPasswordAsync(TUser user, string password); + + + /// + /// Returns a flag indicating whether the given is valid for the + /// specified . + /// + /// The user whose password should be validated. + /// The password to validate + /// The that represents the asynchronous operation, containing true if + /// the specified matches the one store for the , + /// otherwise false. + Task CheckPasswordAsync(TUser user, string password); + + /// + /// Changes a user's password after confirming the specified is correct, + /// as an asynchronous operation. + /// + /// The user whose password should be set. + /// The current password to validate before changing. + /// The new password to set for the specified . + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task ChangePasswordAsync(TUser user, string currentPassword, + string newPassword); + + /// + /// Used to validate a user's session + /// + /// + /// + /// + Task ValidateSessionIdAsync(string userId, string sessionId); + + /// + /// Creates the specified in the backing store with no password, + /// as an asynchronous operation. + /// + /// The user to create. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task CreateAsync(TUser user); + + /// + /// Helper method to generate a password for a user based on the current password validator + /// + /// + string GeneratePassword(); + + + /// + /// Generates an email confirmation token for the specified user. + /// + /// The user to generate an email confirmation token for. + /// + /// The that represents the asynchronous operation, an email confirmation token. + /// + Task GenerateEmailConfirmationTokenAsync(TUser user); + + /// + /// Finds and returns a user, if any, who has the specified user name. + /// + /// The user name to search for. + /// + /// The that represents the asynchronous operation, containing the user matching the specified if it exists. + /// + Task FindByNameAsync(string userName); + + /// + /// Increments the access failed count for the user as an asynchronous operation. + /// If the failed access account is greater than or equal to the configured maximum number of attempts, + /// the user will be locked out for the configured lockout time span. + /// + /// The user whose failed access count to increment. + /// The that represents the asynchronous operation, containing the of the operation. + Task AccessFailedAsync(TUser user); + + /// + /// Returns a flag indicating whether the specified has two factor authentication enabled or not, + /// as an asynchronous operation. + /// + /// The user whose two factor authentication enabled status should be retrieved. + /// + /// The that represents the asynchronous operation, true if the specified + /// has two factor authentication enabled, otherwise false. + /// + Task GetTwoFactorEnabledAsync(TUser user); + + /// + /// Gets a list of valid two factor token providers for the specified , + /// as an asynchronous operation. + /// + /// The user the whose two factor authentication providers will be returned. + /// + /// The that represents result of the asynchronous operation, a list of two + /// factor authentication providers for the specified user. + /// + Task> GetValidTwoFactorProvidersAsync(TUser user); + + /// + /// Verifies the specified two factor authentication against the . + /// + /// The user the token is supposed to be for. + /// The provider which will verify the token. + /// The token to verify. + /// + /// The that represents result of the asynchronous operation, true if the token is valid, + /// otherwise false. + /// + Task VerifyTwoFactorTokenAsync(TUser user, string tokenProvider, string token); + + /// + /// Adds an external Microsoft.AspNetCore.Identity.UserLoginInfo to the specified user. + /// + /// The user to add the login to. + /// The external Microsoft.AspNetCore.Identity.UserLoginInfo to add to the specified user. + /// The System.Threading.Tasks.Task that represents the asynchronous operation, containing the Microsoft.AspNetCore.Identity.IdentityResult of the operation. + Task AddLoginAsync(TUser user, UserLoginInfo login); + + /// + /// Attempts to remove the provided external login information from the specified user. and returns a flag indicating whether the removal succeed or not. + /// + /// The user to remove the login information from. + /// The login provide whose information should be removed. + /// The key given by the external login provider for the specified user. + /// The System.Threading.Tasks.Task that represents the asynchronous operation, containing the Microsoft.AspNetCore.Identity.IdentityResult of the operation. + Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey); + + Task ResetAccessFailedCountAsync(TUser user); + + Task GenerateTwoFactorTokenAsync(TUser user, string tokenProvider); + + /// + /// Gets the email address for the specified user. + /// + /// The user whose email should be returned. + /// The task object containing the results of the asynchronous operation, the email address for the specified user. + Task GetEmailAsync(TUser user); + + /// + /// Gets the telephone number, if any, for the specified user. + /// + /// The user whose telephone number should be retrieved. + /// The System.Threading.Tasks.Task that represents the asynchronous operation, containing the user's telephone number, if any. + /// + /// A user can only support a phone number if the BackOfficeUserStore is replaced with another that implements IUserPhoneNumberStore + /// + Task GetPhoneNumberAsync(TUser user); + + // TODO: These are raised from outside the signinmanager and usermanager in the auth and user controllers, + // let's see if there's a way to avoid that and only have these called within signinmanager and usermanager + // which means we can remove these from the interface (things like invite seems like they cannot be moved) + void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, int userId); + void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, int userId); + SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId); + UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser); + bool HasSendingUserInviteEventHandler { get; } + + } +} diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs index 04f715c7c0..ed45f24a96 100644 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs +++ b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core.BackOffice; using Umbraco.Core.Builder; using Umbraco.Core.Mapping; +using Umbraco.Core.Security; using Umbraco.Web.Models.Mapping; namespace Umbraco.Core.Composing.CompositionExtensions diff --git a/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs b/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs index 961c2e6137..34bd9c9a42 100644 --- a/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs +++ b/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.BackOffice +using Umbraco.Core.Security; + +namespace Umbraco.Core.BackOffice { /// diff --git a/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs b/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs index 4e980b7bb1..2aefb47c14 100644 --- a/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs +++ b/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs @@ -1,4 +1,5 @@ using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Core.BackOffice diff --git a/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs b/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs index b9acd9529c..ab5821c81c 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs @@ -4,9 +4,9 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Mapping; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Common.Security; diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs index 26c3f7875c..b6a86344a2 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using Umbraco.Extensions; using Umbraco.Core.BackOffice; using Umbraco.Tests.Integration.Testing; +using Umbraco.Core.Security; namespace Umbraco.Tests.Integration.Umbraco.Web.BackOffice { diff --git a/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs b/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs index 78d5d5554c..365dca780c 100644 --- a/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs +++ b/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs @@ -10,10 +10,10 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Moq; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; +using Umbraco.Core.Security; using Umbraco.Tests.Common.Builders; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.BackOffice.Routing; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs index 5291c1b12e..9d8edbc75e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs @@ -10,6 +10,7 @@ using Umbraco.Core; using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; using Umbraco.Extensions; namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice @@ -98,7 +99,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice const string expectedClaimType = ClaimTypes.Role; const string expectedClaimValue = "b87309fb-4caf-48dc-b45a-2b752d051508"; - _testUser.Roles.Add(new global::Umbraco.Core.Models.Identity.IdentityUserRole{RoleId = expectedClaimValue}); + _testUser.Roles.Add(new global::Umbraco.Core.Models.Identity.IdentityUserRole{RoleId = expectedClaimValue}); _mockUserManager.Setup(x => x.SupportsUserRole).Returns(true); _mockUserManager.Setup(x => x.GetRolesAsync(_testUser)).ReturnsAsync(new[] {expectedClaimValue}); @@ -115,7 +116,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice const string expectedClaimType = "custom"; const string expectedClaimValue = "val"; - _testUser.Claims.Add(new global::Umbraco.Core.Models.Identity.IdentityUserClaim {ClaimType = expectedClaimType, ClaimValue = expectedClaimValue}); + _testUser.Claims.Add(new global::Umbraco.Core.Models.Identity.IdentityUserClaim {ClaimType = expectedClaimType, ClaimValue = expectedClaimValue}); _mockUserManager.Setup(x => x.SupportsUserClaim).Returns(true); _mockUserManager.Setup(x => x.GetClaimsAsync(_testUser)).ReturnsAsync( new List {new Claim(expectedClaimType, expectedClaimValue)}); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs index 9e9d29a123..8dcaafafcb 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Security.Claims; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs index a078456f8f..30706b1b67 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Security.Claims; using Umbraco.Extensions; using Umbraco.Core; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Extensions { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs index b04a5ff158..6ecda57cc6 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Identity; using Moq; using NUnit.Framework; using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; using Umbraco.Tests.UnitTests.AutoFixture; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.Common.Exceptions; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs index d93bc01b4e..ccebe17b09 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs @@ -1,19 +1,15 @@ -using Microsoft.AspNetCore.Antiforgery; +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; -using Moq; using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; using Umbraco.Core; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; using Umbraco.Web.BackOffice.Security; namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Security @@ -25,8 +21,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Security { var httpContext = new DefaultHttpContext() { - User = new ClaimsPrincipal(new UmbracoBackOfficeIdentity(-1, "test", "test", Enumerable.Empty(), Enumerable.Empty(), "en-US", - Guid.NewGuid().ToString(), Enumerable.Empty(), Enumerable.Empty())) + User = new ClaimsPrincipal(new UmbracoBackOfficeIdentity( + -1, + "test", + "test", + Enumerable.Empty(), + Enumerable.Empty(), + "en-US", + Guid.NewGuid().ToString(), + Enumerable.Empty(), + Enumerable.Empty())) }; httpContext.Request.IsHttps = true; return httpContext; diff --git a/src/Umbraco.Tests/Security/OwinDataProtectorTokenProviderTests.cs b/src/Umbraco.Tests/Security/OwinDataProtectorTokenProviderTests.cs index 66965ca632..c44844fd66 100644 --- a/src/Umbraco.Tests/Security/OwinDataProtectorTokenProviderTests.cs +++ b/src/Umbraco.Tests/Security/OwinDataProtectorTokenProviderTests.cs @@ -6,10 +6,10 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Owin.Security.DataProtection; using Moq; using NUnit.Framework; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; using Umbraco.Tests.Common.Builders; using Umbraco.Web.Security; diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs index 48ffdbcdec..3673bdf333 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.Threading.Tasks; using Microsoft.Owin; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Infrastructure; using Owin; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Tests.TestHelpers.ControllerTesting { diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 23f7e09f5d..f993ee5b6a 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -6,7 +6,6 @@ using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Moq; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.PublishedContent; diff --git a/src/Umbraco.Web.BackOffice/Extensions/IdentityBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/IdentityBuilderExtensions.cs index ddf46a24a7..e6385e6bf9 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/IdentityBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/IdentityBuilderExtensions.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Extensions { diff --git a/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs index 9cfaae6980..a770a01e4d 100644 --- a/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs @@ -7,12 +7,12 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Extensions; using Umbraco.Web.BackOffice.Security; diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficePasswordHasher.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficePasswordHasher.cs index 6c61e7bb35..65f1a7f5bc 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficePasswordHasher.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficePasswordHasher.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Identity; -using Umbraco.Core.BackOffice; using Umbraco.Core.Security; using Umbraco.Core; using Umbraco.Core.Models.Membership; diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSecureDataFormat.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSecureDataFormat.cs index 91b982b5f6..377801a0b7 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSecureDataFormat.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSecureDataFormat.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Authentication; using System; using System.Security.Claims; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Web.BackOffice.Security { diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSecurityStampValidator.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSecurityStampValidator.cs index f12b6279bb..abd0af1353 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSecurityStampValidator.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSecurityStampValidator.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; using Umbraco.Web.Common.Security; namespace Umbraco.Web.BackOffice.Security diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs index e17067daa0..6d1c348d7f 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Security; diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs index 2906d9d87a..cc9b9410a6 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs @@ -65,6 +65,7 @@ namespace Umbraco.Web.Common.Security IpResolver = ipResolver ?? throw new ArgumentNullException(nameof(ipResolver)); _httpContextAccessor = httpContextAccessor; PasswordConfiguration = passwordConfiguration.Value ?? throw new ArgumentNullException(nameof(passwordConfiguration)); + } // We don't support an IUserClaimStore and don't need to (at least currently) diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs index 019eed7e39..5f0757ea9c 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs @@ -6,6 +6,7 @@ using Umbraco.Core.BackOffice; using Umbraco.Core.Compose; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; using Umbraco.Core.Services; namespace Umbraco.Web.Common.Security diff --git a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs index 590edf397a..9f90395ff3 100644 --- a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; diff --git a/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs index 8d9f57945b..8636d9e62d 100644 --- a/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs @@ -1,8 +1,8 @@ using Microsoft.AspNetCore.Identity; using System; using System.Runtime.Serialization; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Security; using SecurityConstants = Umbraco.Core.Constants.Security; namespace Umbraco.Web.BackOffice.Security diff --git a/src/Umbraco.Web.BackOffice/Security/IBackOfficeSignInManager.cs b/src/Umbraco.Web.BackOffice/Security/IBackOfficeSignInManager.cs index ce87484b2c..669ca21239 100644 --- a/src/Umbraco.Web.BackOffice/Security/IBackOfficeSignInManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/IBackOfficeSignInManager.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Identity; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Web.Common.Security { diff --git a/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs index 15d3d04c0b..f484ddac18 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs @@ -5,7 +5,7 @@ using System.Security.Claims; using System.Security.Principal; using System.Text; using Microsoft.AspNetCore.Http.Features; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Extensions { diff --git a/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs b/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs index 82c9cb8496..8071af2f5d 100644 --- a/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs +++ b/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.DirectoryServices.AccountManagement; using System.Threading.Tasks; using Microsoft.Extensions.Options; @@ -9,6 +9,7 @@ using Umbraco.Core.Configuration.Models; namespace Umbraco.Web.Security { // TODO: This relies on an assembly that is not .NET Standard (at least not at the time of implementation) :( + // TODO: This could be ported now, see https://stackoverflow.com/questions/37330705/working-with-directoryservices-in-asp-net-core public class ActiveDirectoryBackOfficeUserPasswordChecker : IBackOfficeUserPasswordChecker { private readonly IOptions _activeDirectorySettings; diff --git a/src/Umbraco.Web/Security/AuthenticationExtensions.cs b/src/Umbraco.Web/Security/AuthenticationExtensions.cs index de5abf8a6b..aa0cd6aca2 100644 --- a/src/Umbraco.Web/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Web/Security/AuthenticationExtensions.cs @@ -12,8 +12,8 @@ using Microsoft.Owin; using Microsoft.Owin.Security; using Newtonsoft.Json; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Security; using Umbraco.Extensions; using Umbraco.Web.Composing; using Constants = Umbraco.Core.Constants; diff --git a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs index e5ba931b0b..010c2d4d33 100644 --- a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs @@ -10,6 +10,7 @@ using Microsoft.Owin.Security; using Umbraco.Core; using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Security; namespace Umbraco.Web.Security { diff --git a/src/Umbraco.Web/Security/FixWindowsAuthMiddlware.cs b/src/Umbraco.Web/Security/FixWindowsAuthMiddlware.cs index 9e26964091..3338344e73 100644 --- a/src/Umbraco.Web/Security/FixWindowsAuthMiddlware.cs +++ b/src/Umbraco.Web/Security/FixWindowsAuthMiddlware.cs @@ -4,7 +4,7 @@ using System.Security.Principal; using System.Threading.Tasks; using Microsoft.Owin; using Umbraco.Core; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Web.Security { diff --git a/src/Umbraco.Web/Security/IBackOfficeUserPasswordChecker.cs b/src/Umbraco.Web/Security/IBackOfficeUserPasswordChecker.cs index 2fae308eb0..7bd67e608a 100644 --- a/src/Umbraco.Web/Security/IBackOfficeUserPasswordChecker.cs +++ b/src/Umbraco.Web/Security/IBackOfficeUserPasswordChecker.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Web.Security { diff --git a/src/Umbraco.Web/Security/OwinDataProtectorTokenProvider.cs b/src/Umbraco.Web/Security/OwinDataProtectorTokenProvider.cs index 72e12b8621..429014dea8 100644 --- a/src/Umbraco.Web/Security/OwinDataProtectorTokenProvider.cs +++ b/src/Umbraco.Web/Security/OwinDataProtectorTokenProvider.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Owin.Security.DataProtection; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Web.Security { diff --git a/src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs b/src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs index 73c1c3fd55..d1b0c54279 100644 --- a/src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs +++ b/src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs @@ -1,6 +1,6 @@ using System; using Microsoft.Owin.Security; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Web.Security { From 90b7ee3f377436989c3d4b07124557cfcc820972 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 3 Dec 2020 23:55:10 +1100 Subject: [PATCH 40/63] oops broke build --- .../Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs b/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs index 8071af2f5d..901e7bf81b 100644 --- a/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs +++ b/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Options; using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Security; namespace Umbraco.Web.Security { From c51ed88d56aecb3de4fabdb4b8b54f7df5138d9c Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Dec 2020 00:20:48 +1100 Subject: [PATCH 41/63] Adds notes for external login service/repo, changes IdentityUserLogin user id to string for now so it can be shared with members/users --- .../Models/Identity/IIdentityUserLogin.cs | 2 +- .../Models/Identity/IdentityUserLogin.cs | 6 +- .../Models/Identity/IdentityUserRole.cs | 2 +- .../Security/BackOfficeIdentityUser.cs | 8 +-- .../BackOffice/BackOfficeUserStore.cs | 24 ++++---- .../BackOffice/IUmbracoUserManager.cs | 56 ++++++++++++++++--- .../Persistence/Dtos/UserDto.cs | 4 +- .../Factories/ExternalLoginFactory.cs | 6 +- .../Implement/ExternalLoginRepository.cs | 4 +- .../Implement/ExternalLoginService.cs | 5 +- .../Services/ExternalLoginServiceTests.cs | 8 +-- 11 files changed, 87 insertions(+), 38 deletions(-) diff --git a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs index 62c92de16d..05703a1b2c 100644 --- a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs +++ b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs @@ -21,7 +21,7 @@ namespace Umbraco.Core.Models.Identity /// /// Gets or sets user Id for the user who owns this login /// - int UserId { get; set; } + string UserId { get; set; } // TODO: This should be able to be used by both users and members /// /// Gets or sets any arbitrary data for the user and external provider - like user tokens returned from the provider diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs index 18e8d4694b..1ae19da128 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs @@ -9,14 +9,14 @@ namespace Umbraco.Core.Models.Identity /// public class IdentityUserLogin : EntityBase, IIdentityUserLogin { - public IdentityUserLogin(string loginProvider, string providerKey, int userId) + public IdentityUserLogin(string loginProvider, string providerKey, string userId) { LoginProvider = loginProvider; ProviderKey = providerKey; UserId = userId; } - public IdentityUserLogin(int id, string loginProvider, string providerKey, int userId, DateTime createDate) + public IdentityUserLogin(int id, string loginProvider, string providerKey, string userId, DateTime createDate) { Id = id; LoginProvider = loginProvider; @@ -32,7 +32,7 @@ namespace Umbraco.Core.Models.Identity public string ProviderKey { get; set; } /// - public int UserId { get; set; } + public string UserId { get; set; } /// public string UserData { get; set; } diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs b/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs index 39ed65112d..8a0b6b891d 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Models.Identity /// /// Gets or sets userId for the user that is in the role /// - public virtual string UserId { get; set; } + public virtual int UserId { get; set; } /// /// Gets or sets roleId for the role diff --git a/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs index e8e036b51b..07811f39e1 100644 --- a/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs @@ -239,18 +239,18 @@ namespace Umbraco.Core.Security get => _groups; set { - //so they recalculate + // so they recalculate _allowedSections = null; _groups = value; - //now clear all roles and re-add them + // now clear all roles and re-add them _roles.CollectionChanged -= _roles_CollectionChanged; _roles.Clear(); foreach (var identityUserRole in _groups.Select(x => new IdentityUserRole { RoleId = x.Alias, - UserId = Id.ToString() + UserId = Id })) { _roles.Add(identityUserRole); @@ -342,7 +342,7 @@ namespace Umbraco.Core.Security { Roles.Add(new IdentityUserRole { - UserId = Id.ToString(), + UserId = Id, RoleId = role }); } diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs index e297eca86d..8e980e7598 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs +++ b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs @@ -417,7 +417,7 @@ namespace Umbraco.Core.BackOffice if (login == null) throw new ArgumentNullException(nameof(login)); var logins = user.Logins; - var instance = new IdentityUserLogin(login.LoginProvider, login.ProviderKey, user.Id); + var instance = new IdentityUserLogin(login.LoginProvider, login.ProviderKey, user.Id.ToString()); var userLogin = instance; logins.Add(userLogin); @@ -462,25 +462,29 @@ namespace Umbraco.Core.BackOffice /// /// Returns the user associated with this login /// - /// public Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - //get all logins associated with the login id - var result = _externalLoginService.Find(loginProvider, providerKey).ToArray(); + // get all logins associated with the login id + IIdentityUserLogin[] result = _externalLoginService.Find(loginProvider, providerKey).ToArray(); if (result.Any()) { - //return the first user that matches the result + // return the first user that matches the result BackOfficeIdentityUser output = null; - foreach (var l in result) + foreach (IIdentityUserLogin l in result) { - var user = _userService.GetUserById(l.UserId); - if (user != null) + // TODO: This won't be necessary once we add GUID support for users and make the external login + // table uses GUIDs without referential integrity + if (int.TryParse(l.UserId, out int userId)) { - output = _mapper.Map(user); - break; + IUser user = _userService.GetUserById(userId); + if (user != null) + { + output = _mapper.Map(user); + break; + } } } diff --git a/src/Umbraco.Infrastructure/BackOffice/IUmbracoUserManager.cs b/src/Umbraco.Infrastructure/BackOffice/IUmbracoUserManager.cs index 8f8e0ffc50..803f64e0e6 100644 --- a/src/Umbraco.Infrastructure/BackOffice/IUmbracoUserManager.cs +++ b/src/Umbraco.Infrastructure/BackOffice/IUmbracoUserManager.cs @@ -18,16 +18,47 @@ namespace Umbraco.Core.BackOffice public interface IUmbracoUserManager : IDisposable where TUser : BackOfficeIdentityUser { + /// + /// Gets the user id of a user + /// + /// The user + /// A representing the result of the asynchronous operation. Task GetUserIdAsync(TUser user); + /// + /// Get the from a + /// + /// The + /// A representing the result of the asynchronous operation. Task GetUserAsync(ClaimsPrincipal principal); + /// + /// Get the user id from the + /// + /// the + /// Returns the user id from the string GetUserId(ClaimsPrincipal principal); + /// + /// Gets the external logins for the user + /// + /// + /// A representing the result of the asynchronous operation. Task> GetLoginsAsync(TUser user); + /// + /// Deletes a user + /// + /// + /// A representing the result of the asynchronous operation. Task DeleteAsync(TUser user); + /// + /// Finds a user by the external login provider + /// + /// + /// + /// A representing the result of the asynchronous operation. Task FindByLoginAsync(string loginProvider, string providerKey); /// @@ -147,8 +178,7 @@ namespace Umbraco.Core.BackOffice /// The that represents the asynchronous operation, returning true if the /// is valid, otherwise false. /// - Task VerifyUserTokenAsync(TUser user, string tokenProvider, string purpose, - string token); + Task VerifyUserTokenAsync(TUser user, string tokenProvider, string purpose, string token); /// /// Adds the to the specified only if the user @@ -185,15 +215,14 @@ namespace Umbraco.Core.BackOffice /// The that represents the asynchronous operation, containing the /// of the operation. /// - Task ChangePasswordAsync(TUser user, string currentPassword, - string newPassword); + Task ChangePasswordAsync(TUser user, string currentPassword, string newPassword); /// /// Used to validate a user's session /// /// /// - /// + /// Returns true if the session is valid, otherwise false Task ValidateSessionIdAsync(string userId, string sessionId); /// @@ -208,12 +237,11 @@ namespace Umbraco.Core.BackOffice Task CreateAsync(TUser user); /// - /// Helper method to generate a password for a user based on the current password validator + /// Generate a password for a user based on the current password validator /// - /// + /// A generated password string GeneratePassword(); - /// /// Generates an email confirmation token for the specified user. /// @@ -292,8 +320,19 @@ namespace Umbraco.Core.BackOffice /// The System.Threading.Tasks.Task that represents the asynchronous operation, containing the Microsoft.AspNetCore.Identity.IdentityResult of the operation. Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey); + /// + /// Resets the access failed count for the user + /// + /// + /// A representing the result of the asynchronous operation. Task ResetAccessFailedCountAsync(TUser user); + /// + /// Generates a two factor token for the user + /// + /// + /// + /// A representing the result of the asynchronous operation. Task GenerateTwoFactorTokenAsync(TUser user, string tokenProvider); /// @@ -316,6 +355,7 @@ namespace Umbraco.Core.BackOffice // TODO: These are raised from outside the signinmanager and usermanager in the auth and user controllers, // let's see if there's a way to avoid that and only have these called within signinmanager and usermanager // which means we can remove these from the interface (things like invite seems like they cannot be moved) + // TODO: When we change to not having the crappy static events this will need to be revisited void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, int userId); void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, int userId); SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId); diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/UserDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/UserDto.cs index 028b760ba5..46bec34a49 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/UserDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/UserDto.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; @@ -19,6 +19,8 @@ namespace Umbraco.Core.Persistence.Dtos UserStartNodeDtos = new HashSet(); } + // TODO: We need to add a GUID for users and track external logins with that instead of the INT + [Column("id")] [PrimaryKeyColumn(Name = "PK_user")] public int Id { get; set; } diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs index 74d2fe7ff0..aa4b20aa40 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Core.Models.Identity; using Umbraco.Core.Persistence.Dtos; @@ -8,7 +8,7 @@ namespace Umbraco.Core.Persistence.Factories { public static IIdentityUserLogin BuildEntity(ExternalLoginDto dto) { - var entity = new IdentityUserLogin(dto.Id, dto.LoginProvider, dto.ProviderKey, dto.UserId, dto.CreateDate) + var entity = new IdentityUserLogin(dto.Id, dto.LoginProvider, dto.ProviderKey, dto.UserId.ToString(), dto.CreateDate) { UserData = dto.UserData }; @@ -26,7 +26,7 @@ namespace Umbraco.Core.Persistence.Factories CreateDate = entity.CreateDate, LoginProvider = entity.LoginProvider, ProviderKey = entity.ProviderKey, - UserId = entity.UserId, + UserId = int.Parse(entity.UserId), // TODO: This is temp until we change the ext logins to use GUIDs UserData = entity.UserData }; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs index 33fd3af7fc..c3ed111ffb 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -13,6 +13,8 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.Repositories.Implement { + // TODO: We should update this to support both users and members. It means we would remove referential integrity from users + // and the user/member key would be a GUID (we also need to add a GUID to users) internal class ExternalLoginRepository : NPocoRepositoryBase, IExternalLoginRepository { public ExternalLoginRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) diff --git a/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs b/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs index fabbfea1d4..5edbe77cdb 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -25,7 +25,8 @@ namespace Umbraco.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - return _externalLoginRepository.Get(Query().Where(x => x.UserId == userId)) + var asString = userId.ToString(); // TODO: This is temp until we update the external service to support guids for both users and members + return _externalLoginRepository.Get(Query().Where(x => x.UserId == asString)) .ToList(); } } diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs index 429e1953f7..192971f405 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Threading; using NUnit.Framework; @@ -93,7 +93,7 @@ namespace Umbraco.Tests.Services var user = new User(GlobalSettings, "Test", "test@test.com", "test", "helloworldtest"); UserService.Save(user); - var extLogin = new IdentityUserLogin("test1", Guid.NewGuid().ToString("N"), user.Id) + var extLogin = new IdentityUserLogin("test1", Guid.NewGuid().ToString("N"), user.Id.ToString()) { UserData = "hello" }; @@ -112,7 +112,7 @@ namespace Umbraco.Tests.Services var user = new User(GlobalSettings, "Test", "test@test.com", "test", "helloworldtest"); UserService.Save(user); - var extLogin = new IdentityUserLogin("test1", Guid.NewGuid().ToString("N"), user.Id) + var extLogin = new IdentityUserLogin("test1", Guid.NewGuid().ToString("N"), user.Id.ToString()) { UserData = "hello" }; @@ -218,7 +218,7 @@ namespace Umbraco.Tests.Services var logins = ExternalLoginService.GetAll(user.Id).OrderBy(x => x.LoginProvider).ToList(); logins.RemoveAt(0); // remove the first one - logins.Add(new IdentityUserLogin("test5", Guid.NewGuid().ToString("N"), user.Id)); // add a new one + logins.Add(new IdentityUserLogin("test5", Guid.NewGuid().ToString("N"), user.Id.ToString())); // add a new one // save new list ExternalLoginService.Save(user.Id, logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey))); From 8e9dfad381129f3604d2eb304d38a9add09d80e8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Dec 2020 00:54:28 +1100 Subject: [PATCH 42/63] Simplifying IdentityUser --- .../Models/Identity/IdentityUser.cs | 224 ++++++++++-- .../Security/BackOfficeIdentityUser.cs | 328 ++---------------- .../BackOffice/BackOfficeUserStore.cs | 12 +- 3 files changed, 240 insertions(+), 324 deletions(-) diff --git a/src/Umbraco.Core/Models/Identity/IdentityUser.cs b/src/Umbraco.Core/Models/Identity/IdentityUser.cs index dd3841d2c8..516bd60c49 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUser.cs @@ -1,14 +1,15 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Identity { /// - /// Abstract class for use in Umbraco Identity + /// Abstract class for use in Umbraco Identity for users and members /// - /// The type of user login - /// The type of user role - /// The type of user claims /// /// This class was originally borrowed from the EF implementation in Identity prior to netcore. /// The new IdentityUser in netcore does not have properties such as Claims, Roles and Logins and those are instead @@ -16,40 +17,79 @@ namespace Umbraco.Core.Models.Identity /// to a user. We will continue using this approach since it works fine for what we need which does the change tracking of /// claims, roles and logins directly on the user model. /// - public abstract class IdentityUser - where TLogin : IIdentityUserLogin - where TRole : IdentityUserRole - where TClaim : IdentityUserClaim + public abstract class IdentityUser : IRememberBeingDirty { + private int _id; + private string _email; + private string _userName; + private DateTime? _lastLoginDateUtc; + private bool _emailConfirmed; + private int _accessFailedCount; + private string _passwordHash; + private DateTime? _lastPasswordChangeDateUtc; + private ObservableCollection _logins; + private Lazy> _getLogins; + private ObservableCollection _roles; + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - protected IdentityUser() + public IdentityUser() { - Claims = new List(); - Roles = new List(); - Logins = new List(); + // must initialize before setting groups + _roles = new ObservableCollection(); + _roles.CollectionChanged += Roles_CollectionChanged; + Claims = new List(); + } + + public event PropertyChangedEventHandler PropertyChanged + { + add + { + BeingDirty.PropertyChanged += value; + } + + remove + { + BeingDirty.PropertyChanged -= value; + } } /// /// Gets or sets last login date /// - public virtual DateTime? LastLoginDateUtc { get; set; } + public DateTime? LastLoginDateUtc + { + get => _lastLoginDateUtc; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _lastLoginDateUtc, nameof(LastLoginDateUtc)); + } /// /// Gets or sets email /// - public virtual string Email { get; set; } + public string Email + { + get => _email; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _email, nameof(Email)); + } /// /// Gets or sets a value indicating whether the email is confirmed, default is false /// - public virtual bool EmailConfirmed { get; set; } + public bool EmailConfirmed + { + get => _emailConfirmed; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _emailConfirmed, nameof(EmailConfirmed)); + } /// /// Gets or sets the salted/hashed form of the user password /// - public virtual string PasswordHash { get; set; } + public string PasswordHash + { + get => _passwordHash; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordHash, nameof(PasswordHash)); + } /// /// Gets or sets a random value that should change whenever a users credentials have changed (password changed, login removed) @@ -88,41 +128,173 @@ namespace Umbraco.Core.Models.Identity /// /// Gets or sets dateTime in UTC when the password was last changed. /// - public virtual DateTime? LastPasswordChangeDateUtc { get; set; } + public DateTime? LastPasswordChangeDateUtc + { + get => _lastPasswordChangeDateUtc; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangeDateUtc, nameof(LastPasswordChangeDateUtc)); + } /// /// Gets or sets a value indicating whether is lockout enabled for this user /// - public virtual bool LockoutEnabled { get; set; } + /// + /// Currently this is always true for users and members + /// + public bool LockoutEnabled + { + get => true; + set { } + } /// /// Gets or sets the value to record failures for the purposes of lockout /// - public virtual int AccessFailedCount { get; set; } + public int AccessFailedCount + { + get => _accessFailedCount; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _accessFailedCount, nameof(AccessFailedCount)); + } /// - /// Gets the user roles collection + /// Gets or sets the user roles collection /// - public virtual ICollection Roles { get; } + public ICollection Roles + { + get => _roles; + set + { + _roles.CollectionChanged -= Roles_CollectionChanged; + _roles = new ObservableCollection(value); + _roles.CollectionChanged += Roles_CollectionChanged; + } + } /// /// Gets navigation the user claims collection /// - public virtual ICollection Claims { get; } + public ICollection Claims { get; } /// /// Gets the user logins collection /// - public virtual ICollection Logins { get; } + public ICollection Logins + { + get + { + // return if it exists + if (_logins != null) + { + return _logins; + } + + _logins = new ObservableCollection(); + + // if the callback is there and hasn't been created yet then execute it and populate the logins + if (_getLogins != null && !_getLogins.IsValueCreated) + { + foreach (IIdentityUserLogin l in _getLogins.Value) + { + _logins.Add(l); + } + } + + // now assign events + _logins.CollectionChanged += Logins_CollectionChanged; + + return _logins; + } + } /// /// Gets or sets user ID (Primary Key) /// - public virtual int Id { get; set; } + public int Id + { + get => _id; + set + { + _id = value; + HasIdentity = true; + } + } + + /// + /// Gets or sets a value indicating whether returns an Id has been set on this object this will be false if the object is new and not persisted to the database + /// + public bool HasIdentity { get; protected set; } /// /// Gets or sets user name /// - public virtual string UserName { get; set; } + public string UserName + { + get => _userName; + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _userName, nameof(UserName)); + } + + /// + /// Gets the for change tracking + /// + protected BeingDirty BeingDirty { get; } = new BeingDirty(); + + /// + public bool IsDirty() => BeingDirty.IsDirty(); + + /// + public bool IsPropertyDirty(string propName) => BeingDirty.IsPropertyDirty(propName); + + /// + public IEnumerable GetDirtyProperties() => BeingDirty.GetDirtyProperties(); + + /// + public void ResetDirtyProperties() => BeingDirty.ResetDirtyProperties(); + + /// + public bool WasDirty() => BeingDirty.WasDirty(); + + /// + public bool WasPropertyDirty(string propertyName) => BeingDirty.WasPropertyDirty(propertyName); + + /// + public void ResetWereDirtyProperties() => BeingDirty.ResetWereDirtyProperties(); + + /// + public void ResetDirtyProperties(bool rememberDirty) => BeingDirty.ResetDirtyProperties(rememberDirty); + + /// + public IEnumerable GetWereDirtyProperties() => BeingDirty.GetWereDirtyProperties(); + + /// + /// Disables change tracking. + /// + public void DisableChangeTracking() => BeingDirty.DisableChangeTracking(); + + /// + /// Enables change tracking. + /// + public void EnableChangeTracking() => BeingDirty.EnableChangeTracking(); + + /// + /// Adds a role + /// + /// The role to add + /// + /// Adding a role this way will not reflect on the user's group's collection or it's allowed sections until the user is persisted + /// + public void AddRole(string role) => Roles.Add(new IdentityUserRole + { + UserId = Id, + RoleId = role + }); + + /// + /// Used to set a lazy call back to populate the user's Login list + /// + /// The lazy value + public void SetLoginsCallback(Lazy> callback) => _getLogins = callback ?? throw new ArgumentNullException(nameof(callback)); + + private void Logins_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => BeingDirty.OnPropertyChanged(nameof(Logins)); + + private void Roles_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => BeingDirty.OnPropertyChanged(nameof(Roles)); } } diff --git a/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs index 07811f39e1..051a68a362 100644 --- a/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs @@ -12,25 +12,24 @@ using Umbraco.Core.Models.Membership; namespace Umbraco.Core.Security { - public class BackOfficeIdentityUser : IdentityUser, IRememberBeingDirty + public class BackOfficeIdentityUser : IdentityUser { - private string _email; - private string _userName; - private int _id; - private DateTime? _lastLoginDateUtc; - private bool _emailConfirmed; private string _name; - private int _accessFailedCount; - private string _passwordHash; private string _passwordConfig; private string _culture; - private ObservableCollection _logins; - private Lazy> _getLogins; private IReadOnlyUserGroup[] _groups; private string[] _allowedSections; private int[] _startMediaIds; private int[] _startContentIds; - private DateTime? _lastPasswordChangeDateUtc; + + // Custom comparer for enumerables + private static readonly DelegateEqualityComparer s_groupsComparer = new DelegateEqualityComparer( + (groups, enumerable) => groups.Select(x => x.Alias).UnsortedSequenceEqual(enumerable.Select(x => x.Alias)), + groups => groups.GetHashCode()); + + private static readonly DelegateEqualityComparer s_startIdsComparer = new DelegateEqualityComparer( + (groups, enumerable) => groups.UnsortedSequenceEqual(enumerable), + groups => groups.GetHashCode()); /// /// Used to construct a new instance without an identity @@ -54,12 +53,12 @@ namespace Umbraco.Core.Security var user = new BackOfficeIdentityUser(globalSettings, Array.Empty()); user.DisableChangeTracking(); - user._userName = username; - user._email = email; + user.UserName = username; + user.Email = email; // we are setting minvalue here because the default is "0" which is the id of the admin user // which we cannot allow because the admin user will always exist - user._id = int.MinValue; + user.Id = int.MinValue; user.HasIdentity = false; user._culture = culture; user._name = name; @@ -74,10 +73,6 @@ namespace Umbraco.Core.Security _allowedSections = Array.Empty(); _culture = globalSettings.DefaultUILanguage; - // must initialize before setting groups - _roles = new ObservableCollection(); - _roles.CollectionChanged += _roles_CollectionChanged; - // use the property setters - they do more than just setting a field Groups = groups; } @@ -95,105 +90,28 @@ namespace Umbraco.Core.Security Id = userId; } - /// - /// Returns true if an Id has been set on this object this will be false if the object is new and not persisted to the database - /// - public bool HasIdentity { get; private set; } - public int[] CalculatedMediaStartNodeIds { get; set; } public int[] CalculatedContentStartNodeIds { get; set; } - public override int Id - { - get => _id; - set - { - _id = value; - HasIdentity = true; - } - } - /// - /// Override Email so we can track changes to it - /// - public override string Email - { - get => _email; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _email, nameof(Email)); - } - - /// - /// Override UserName so we can track changes to it - /// - public override string UserName - { - get => _userName; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _userName, nameof(UserName)); - } - - /// - /// LastPasswordChangeDateUtc so we can track changes to it - /// - public override DateTime? LastPasswordChangeDateUtc - { - get { return _lastPasswordChangeDateUtc; } - set { _beingDirty.SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangeDateUtc, nameof(LastPasswordChangeDateUtc)); } - } - - /// - /// Override LastLoginDateUtc so we can track changes to it - /// - public override DateTime? LastLoginDateUtc - { - get => _lastLoginDateUtc; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _lastLoginDateUtc, nameof(LastLoginDateUtc)); - } - - /// - /// Override EmailConfirmed so we can track changes to it - /// - public override bool EmailConfirmed - { - get => _emailConfirmed; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _emailConfirmed, nameof(EmailConfirmed)); - } - - /// - /// Gets/sets the user's real name + /// Gets or sets the user's real name /// public string Name { get => _name; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } - /// - /// Override AccessFailedCount so we can track changes to it - /// - public override int AccessFailedCount - { - get => _accessFailedCount; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _accessFailedCount, nameof(AccessFailedCount)); - } - - /// - /// Override PasswordHash so we can track changes to it - /// - public override string PasswordHash - { - get => _passwordHash; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordHash, nameof(PasswordHash)); - } public string PasswordConfig { get => _passwordConfig; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordConfig, nameof(PasswordConfig)); + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordConfig, nameof(PasswordConfig)); } /// - /// Content start nodes assigned to the User (not ones assigned to the user's groups) + /// Gets or sets content start nodes assigned to the User (not ones assigned to the user's groups) /// public int[] StartContentIds { @@ -201,13 +119,16 @@ namespace Umbraco.Core.Security set { if (value == null) + { value = new int[0]; - _beingDirty.SetPropertyValueAndDetectChanges(value, ref _startContentIds, nameof(StartContentIds), StartIdsComparer); + } + + BeingDirty.SetPropertyValueAndDetectChanges(value, ref _startContentIds, nameof(StartContentIds), s_startIdsComparer); } } /// - /// Media start nodes assigned to the User (not ones assigned to the user's groups) + /// Gets or sets media start nodes assigned to the User (not ones assigned to the user's groups) /// public int[] StartMediaIds { @@ -215,23 +136,23 @@ namespace Umbraco.Core.Security set { if (value == null) + { value = new int[0]; - _beingDirty.SetPropertyValueAndDetectChanges(value, ref _startMediaIds, nameof(StartMediaIds), StartIdsComparer); + } + + BeingDirty.SetPropertyValueAndDetectChanges(value, ref _startMediaIds, nameof(StartMediaIds), s_startIdsComparer); } } /// - /// This is a readonly list of the user's allowed sections which are based on it's user groups + /// Gets a readonly list of the user's allowed sections which are based on it's user groups /// - public string[] AllowedSections - { - get { return _allowedSections ?? (_allowedSections = _groups.SelectMany(x => x.AllowedSections).Distinct().ToArray()); } - } + public string[] AllowedSections => _allowedSections ?? (_allowedSections = _groups.SelectMany(x => x.AllowedSections).Distinct().ToArray()); public string Culture { get => _culture; - set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _culture, nameof(Culture)); + set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _culture, nameof(Culture)); } public IReadOnlyUserGroup[] Groups @@ -244,34 +165,23 @@ namespace Umbraco.Core.Security _groups = value; - // now clear all roles and re-add them - _roles.CollectionChanged -= _roles_CollectionChanged; - _roles.Clear(); - foreach (var identityUserRole in _groups.Select(x => new IdentityUserRole + var roles = new List(); + foreach (IdentityUserRole identityUserRole in _groups.Select(x => new IdentityUserRole { RoleId = x.Alias, UserId = Id })) { - _roles.Add(identityUserRole); + roles.Add(identityUserRole); } - _roles.CollectionChanged += _roles_CollectionChanged; - _beingDirty.SetPropertyValueAndDetectChanges(value, ref _groups, nameof(Groups), GroupsComparer); + // now reset the collection + Roles = roles; + + BeingDirty.SetPropertyValueAndDetectChanges(value, ref _groups, nameof(Groups), s_groupsComparer); } } - /// - /// Lockout is always enabled - /// - public override bool LockoutEnabled - { - get { return true; } - set - { - //do nothing - } - } /// /// Based on the user's lockout end date, this will determine if they are locked out @@ -290,171 +200,5 @@ namespace Umbraco.Core.Security /// public bool IsApproved { get; set; } - /// - /// Overridden to make the retrieval lazy - /// - public override ICollection Logins - { - get - { - // return if it exists - if (_logins != null) - return _logins; - - _logins = new ObservableCollection(); - - // if the callback is there and hasn't been created yet then execute it and populate the logins - if (_getLogins != null && !_getLogins.IsValueCreated) - { - foreach (var l in _getLogins.Value) - { - _logins.Add(l); - } - } - - //now assign events - _logins.CollectionChanged += Logins_CollectionChanged; - - return _logins; - } - } - - void Logins_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - _beingDirty.OnPropertyChanged(nameof(Logins)); - } - - private void _roles_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - _beingDirty.OnPropertyChanged(nameof(Roles)); - } - - private readonly ObservableCollection _roles; - - /// - /// helper method to easily add a role without having to deal with IdentityUserRole{T} - /// - /// - /// - /// Adding a role this way will not reflect on the user's group's collection or it's allowed sections until the user is persisted - /// - public void AddRole(string role) - { - Roles.Add(new IdentityUserRole - { - UserId = Id, - RoleId = role - }); - } - - /// - /// Override Roles because the value of these are the user's group aliases - /// - public override ICollection Roles => _roles; - - /// - /// Used to set a lazy call back to populate the user's Login list - /// - /// - public void SetLoginsCallback(Lazy> callback) - { - _getLogins = callback ?? throw new ArgumentNullException(nameof(callback)); - } - - #region BeingDirty - - private readonly BeingDirty _beingDirty = new BeingDirty(); - - /// - public bool IsDirty() - { - return _beingDirty.IsDirty(); - } - - /// - public bool IsPropertyDirty(string propName) - { - return _beingDirty.IsPropertyDirty(propName); - } - - /// - public IEnumerable GetDirtyProperties() - { - return _beingDirty.GetDirtyProperties(); - } - - /// - public void ResetDirtyProperties() - { - _beingDirty.ResetDirtyProperties(); - } - - /// - public bool WasDirty() - { - return _beingDirty.WasDirty(); - } - - /// - public bool WasPropertyDirty(string propertyName) - { - return _beingDirty.WasPropertyDirty(propertyName); - } - - /// - public void ResetWereDirtyProperties() - { - _beingDirty.ResetWereDirtyProperties(); - } - - /// - public void ResetDirtyProperties(bool rememberDirty) - { - _beingDirty.ResetDirtyProperties(rememberDirty); - } - - /// - public IEnumerable GetWereDirtyProperties() - => _beingDirty.GetWereDirtyProperties(); - - /// - /// Disables change tracking. - /// - public void DisableChangeTracking() - { - _beingDirty.DisableChangeTracking(); - } - - /// - /// Enables change tracking. - /// - public void EnableChangeTracking() - { - _beingDirty.EnableChangeTracking(); - } - - public event PropertyChangedEventHandler PropertyChanged - { - add - { - _beingDirty.PropertyChanged += value; - } - remove - { - _beingDirty.PropertyChanged -= value; - } - } - - #endregion - - //Custom comparer for enumerables - private static readonly DelegateEqualityComparer GroupsComparer = new DelegateEqualityComparer( - (groups, enumerable) => groups.Select(x => x.Alias).UnsortedSequenceEqual(enumerable.Select(x => x.Alias)), - groups => groups.GetHashCode()); - - private static readonly DelegateEqualityComparer StartIdsComparer = new DelegateEqualityComparer( - (groups, enumerable) => groups.UnsortedSequenceEqual(enumerable), - groups => groups.GetHashCode()); - } } diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs index 8e980e7598..234d6eae66 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs +++ b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs @@ -870,28 +870,28 @@ namespace Umbraco.Core.BackOffice { anythingChanged = true; - //clear out the current groups (need to ToArray since we are modifying the iterator) + // clear out the current groups (need to ToArray since we are modifying the iterator) user.ClearGroups(); - //go lookup all these groups + // go lookup all these groups var groups = _userService.GetUserGroupsByAlias(combinedAliases).Select(x => x.ToReadOnlyGroup()).ToArray(); - //use all of the ones assigned and add them + // use all of the ones assigned and add them foreach (var group in groups) { user.AddGroup(group); } - //re-assign + // re-assign identityUser.Groups = groups; } } - //we should re-set the calculated start nodes + // we should re-set the calculated start nodes identityUser.CalculatedMediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService); identityUser.CalculatedContentStartNodeIds = user.CalculateContentStartNodeIds(_entityService); - //reset all changes + // reset all changes identityUser.ResetDirtyProperties(false); return anythingChanged; From 35af86c3d3ecc2c3ba76edfa4cc7208caadfc510 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Dec 2020 01:38:36 +1100 Subject: [PATCH 43/63] Splits user manager into a base class that can be reused changes base class of IdentityUser to UmbracoIdentityUser --- linting/codeanalysis.ruleset | 2 + ...IdentityUser.cs => UmbracoIdentityUser.cs} | 6 +- .../Security/BackOfficeIdentityUser.cs | 20 +- .../BackOffice/BackOfficeUserStore.cs | 128 +------- .../Security/BackOfficeUserManager.cs | 303 +++--------------- .../Security/UmbracoUserManager.cs | 241 ++++++++++++++ 6 files changed, 309 insertions(+), 391 deletions(-) rename src/Umbraco.Core/Models/Identity/{IdentityUser.cs => UmbracoIdentityUser.cs} (98%) rename src/{Umbraco.Web.BackOffice => Umbraco.Web.Common}/Security/BackOfficeUserManager.cs (56%) create mode 100644 src/Umbraco.Web.Common/Security/UmbracoUserManager.cs diff --git a/linting/codeanalysis.ruleset b/linting/codeanalysis.ruleset index 4fde2bef8d..57c9fb7d60 100644 --- a/linting/codeanalysis.ruleset +++ b/linting/codeanalysis.ruleset @@ -11,6 +11,8 @@ + + \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Identity/IdentityUser.cs b/src/Umbraco.Core/Models/Identity/UmbracoIdentityUser.cs similarity index 98% rename from src/Umbraco.Core/Models/Identity/IdentityUser.cs rename to src/Umbraco.Core/Models/Identity/UmbracoIdentityUser.cs index 516bd60c49..ffa549ab47 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/UmbracoIdentityUser.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Models.Identity /// to a user. We will continue using this approach since it works fine for what we need which does the change tracking of /// claims, roles and logins directly on the user model. /// - public abstract class IdentityUser : IRememberBeingDirty + public abstract class UmbracoIdentityUser : IRememberBeingDirty { private int _id; private string _email; @@ -32,9 +32,9 @@ namespace Umbraco.Core.Models.Identity private ObservableCollection _roles; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public IdentityUser() + public UmbracoIdentityUser() { // must initialize before setting groups _roles = new ObservableCollection(); diff --git a/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs index 051a68a362..4de1ae4d0f 100644 --- a/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs @@ -1,10 +1,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; using System.Linq; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Identity; @@ -12,7 +8,10 @@ using Umbraco.Core.Models.Membership; namespace Umbraco.Core.Security { - public class BackOfficeIdentityUser : IdentityUser + /// + /// The identity user used for the back office + /// + public class BackOfficeIdentityUser : UmbracoIdentityUser { private string _name; private string _passwordConfig; @@ -34,11 +33,7 @@ namespace Umbraco.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) { if (string.IsNullOrWhiteSpace(username)) @@ -80,9 +75,6 @@ namespace Umbraco.Core.Security /// /// Initializes a new instance of the class. /// - /// - /// - /// public BackOfficeIdentityUser(GlobalSettings globalSettings, int userId, IEnumerable groups) : this(globalSettings, groups.ToArray()) { @@ -184,7 +176,7 @@ namespace Umbraco.Core.Security /// - /// Based on the user's lockout end date, this will determine if they are locked out + /// Gets a value indicating whether the user is locked out based on the user's lockout end date /// public bool IsLockedOut { @@ -196,7 +188,7 @@ namespace Umbraco.Core.Security } /// - /// This is a 1:1 mapping with IUser.IsApproved + /// Gets or sets a value indicating the IUser IsApproved /// public bool IsApproved { get; set; } diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs index 234d6eae66..e74690b76f 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs +++ b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Mapping; using Umbraco.Core.Models; @@ -18,6 +17,8 @@ using Umbraco.Core.Services; namespace Umbraco.Core.BackOffice { + // TODO: Make this into a base class that can be re-used + public class BackOfficeUserStore : DisposableObjectSlim, IUserPasswordStore, IUserEmailStore, @@ -28,11 +29,11 @@ namespace Umbraco.Core.BackOffice IUserSessionStore // TODO: This would require additional columns/tables and then a lot of extra coding support to make this happen natively within umbraco - //IUserTwoFactorStore, + // IUserTwoFactorStore, // TODO: This would require additional columns/tables for now people will need to implement this on their own - //IUserPhoneNumberStore, + // IUserPhoneNumberStore, // TODO: To do this we need to implement IQueryable - we'll have an IQuerable implementation soon with the UmbracoLinqPadDriver implementation - //IQueryableUserStore + // IQueryableUserStore { private readonly IScopeProvider _scopeProvider; private readonly IUserService _userService; @@ -42,15 +43,16 @@ namespace Umbraco.Core.BackOffice private readonly UmbracoMapper _mapper; private bool _disposed = false; + /// + /// Initializes a new instance of the class. + /// public BackOfficeUserStore(IScopeProvider scopeProvider, IUserService userService, IEntityService entityService, IExternalLoginService externalLoginService, IOptions globalSettings, UmbracoMapper mapper) { _scopeProvider = scopeProvider; - _userService = userService; + _userService = userService ?? throw new ArgumentNullException(nameof(userService)); _entityService = entityService; - _externalLoginService = externalLoginService; + _externalLoginService = externalLoginService ?? throw new ArgumentNullException(nameof(externalLoginService)); _globalSettings = globalSettings.Value; - if (userService == null) throw new ArgumentNullException("userService"); - if (externalLoginService == null) throw new ArgumentNullException("externalLoginService"); _mapper = mapper; _userService = userService; _externalLoginService = externalLoginService; @@ -59,10 +61,7 @@ namespace Umbraco.Core.BackOffice /// /// Handles the disposal of resources. Derived from abstract class which handles common required locking logic. /// - protected override void DisposeResources() - { - _disposed = true; - } + protected override void DisposeResources() => _disposed = true; public Task GetUserIdAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) { @@ -105,9 +104,6 @@ namespace Umbraco.Core.BackOffice /// /// Insert a new user /// - /// - /// - /// public Task CreateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -158,9 +154,6 @@ namespace Umbraco.Core.BackOffice /// /// Update a user /// - /// - /// - /// public Task UpdateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -206,8 +199,6 @@ namespace Umbraco.Core.BackOffice /// /// Delete a user /// - /// - /// public Task DeleteAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -227,9 +218,6 @@ namespace Umbraco.Core.BackOffice /// /// Finds a user /// - /// - /// - /// public async Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -244,9 +232,6 @@ namespace Umbraco.Core.BackOffice /// /// Find a user by name /// - /// - /// - /// public async Task FindByNameAsync(string userName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -265,9 +250,6 @@ namespace Umbraco.Core.BackOffice /// /// Set the user password hash /// - /// - /// - /// public Task SetPasswordHashAsync(BackOfficeIdentityUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -278,6 +260,7 @@ namespace Umbraco.Core.BackOffice user.PasswordHash = passwordHash; user.PasswordConfig = null; // Clear this so that it's reset at the repository level + user.LastPasswordChangeDateUtc = DateTime.UtcNow; return Task.CompletedTask; } @@ -285,9 +268,6 @@ namespace Umbraco.Core.BackOffice /// /// Get the user password hash /// - /// - /// - /// public Task GetPasswordHashAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -300,9 +280,6 @@ namespace Umbraco.Core.BackOffice /// /// Returns true if a user has a password set /// - /// - /// - /// public Task HasPasswordAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -315,9 +292,6 @@ namespace Umbraco.Core.BackOffice /// /// Set the user email /// - /// - /// - /// public Task SetEmailAsync(BackOfficeIdentityUser user, string email, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -333,9 +307,6 @@ namespace Umbraco.Core.BackOffice /// /// Get the user email /// - /// - /// - /// public Task GetEmailAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -348,9 +319,6 @@ namespace Umbraco.Core.BackOffice /// /// Returns true if the user email is confirmed /// - /// - /// - /// public Task GetEmailConfirmedAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -363,9 +331,6 @@ namespace Umbraco.Core.BackOffice /// /// Sets whether the user email is confirmed /// - /// - /// - /// public Task SetEmailConfirmedAsync(BackOfficeIdentityUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -377,9 +342,6 @@ namespace Umbraco.Core.BackOffice /// /// Returns the user associated with this email /// - /// - /// - /// public Task FindByEmailAsync(string email, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -393,22 +355,14 @@ namespace Umbraco.Core.BackOffice } public Task GetNormalizedEmailAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) - { - return GetEmailAsync(user, cancellationToken); - } + => GetEmailAsync(user, cancellationToken); public Task SetNormalizedEmailAsync(BackOfficeIdentityUser user, string normalizedEmail, CancellationToken cancellationToken) - { - return SetEmailAsync(user, normalizedEmail, cancellationToken); - } + => SetEmailAsync(user, normalizedEmail, cancellationToken); /// /// Adds a user login with the specified provider and key /// - /// - /// - /// - /// public Task AddLoginAsync(BackOfficeIdentityUser user, UserLoginInfo login, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -427,11 +381,6 @@ namespace Umbraco.Core.BackOffice /// /// Removes the user login with the specified combination if it exists /// - /// - /// - /// - /// - /// public Task RemoveLoginAsync(BackOfficeIdentityUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -447,9 +396,6 @@ namespace Umbraco.Core.BackOffice /// /// Returns the linked accounts for this user /// - /// - /// - /// public Task> GetLoginsAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -498,10 +444,6 @@ namespace Umbraco.Core.BackOffice /// /// Adds a user to a role (user group) /// - /// - /// - /// - /// public Task AddToRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -523,10 +465,6 @@ namespace Umbraco.Core.BackOffice /// /// Removes the role (user group) for the user /// - /// - /// - /// - /// public Task RemoveFromRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -548,9 +486,6 @@ namespace Umbraco.Core.BackOffice /// /// Returns the roles (user groups) for this user /// - /// - /// - /// public Task> GetRolesAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -562,10 +497,6 @@ namespace Umbraco.Core.BackOffice /// /// Returns true if a user is in the role /// - /// - /// - /// - /// public Task IsInRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -597,10 +528,6 @@ namespace Umbraco.Core.BackOffice /// /// Set the security stamp for the user /// - /// - /// - /// - /// public Task SetSecurityStampAsync(BackOfficeIdentityUser user, string stamp, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -614,9 +541,6 @@ namespace Umbraco.Core.BackOffice /// /// Get the user security stamp /// - /// - /// - /// public Task GetSecurityStampAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -644,10 +568,7 @@ namespace Umbraco.Core.BackOffice /// /// Returns the DateTimeOffset that represents the end of a user's lockout, any time in the past should be considered not locked out. /// - /// - /// /// - /// /// Currently we do not support a timed lock out, when they are locked out, an admin will have to reset the status /// public Task GetLockoutEndDateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) @@ -664,9 +585,6 @@ namespace Umbraco.Core.BackOffice /// /// Locks a user out until the specified end date (set to a past date, to unlock a user) /// - /// - /// - /// /// /// Currently we do not support a timed lock out, when they are locked out, an admin will have to reset the status /// @@ -683,9 +601,6 @@ namespace Umbraco.Core.BackOffice /// /// Used to record when an attempt to access the user has failed /// - /// - /// - /// public Task IncrementAccessFailedCountAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -699,9 +614,6 @@ namespace Umbraco.Core.BackOffice /// /// Used to reset the access failed count, typically after the account is successfully accessed /// - /// - /// - /// public Task ResetAccessFailedCountAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -716,9 +628,6 @@ namespace Umbraco.Core.BackOffice /// Returns the current number of failed access attempts. This number usually will be reset whenever the password is /// verified or the account is locked out. /// - /// - /// - /// public Task GetAccessFailedCountAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -730,9 +639,6 @@ namespace Umbraco.Core.BackOffice /// /// Returns true /// - /// - /// - /// public Task GetLockoutEnabledAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -744,9 +650,6 @@ namespace Umbraco.Core.BackOffice /// /// Doesn't actually perform any function, users can always be locked out /// - /// - /// - /// public Task SetLockoutEnabledAsync(BackOfficeIdentityUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -904,8 +807,7 @@ namespace Umbraco.Core.BackOffice public Task ValidateSessionIdAsync(string userId, string sessionId) { - Guid guidSessionId; - if (Guid.TryParse(sessionId, out guidSessionId)) + if (Guid.TryParse(sessionId, out Guid guidSessionId)) { return Task.FromResult(_userService.ValidateLoginSession(UserIdToInt(userId), guidSessionId)); } diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs similarity index 56% rename from src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs rename to src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs index cc9b9410a6..9f77cdb7d4 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs @@ -11,6 +11,7 @@ using Umbraco.Core; using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; using Umbraco.Extensions; @@ -20,9 +21,10 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Common.Security { - - public class BackOfficeUserManager : BackOfficeUserManager, IBackOfficeUserManager + public class BackOfficeUserManager : UmbracoUserManager, IBackOfficeUserManager { + private readonly IHttpContextAccessor _httpContextAccessor; + public BackOfficeUserManager( IIpResolver ipResolver, IUserStore store, @@ -36,135 +38,11 @@ namespace Umbraco.Web.Common.Security IHttpContextAccessor httpContextAccessor, ILogger> logger, IOptions passwordConfiguration) - : base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, httpContextAccessor, logger, passwordConfiguration) + : base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger, passwordConfiguration) { - } - } - - public class BackOfficeUserManager : UserManager - where T : BackOfficeIdentityUser - { - private PasswordGenerator _passwordGenerator; - private readonly IHttpContextAccessor _httpContextAccessor; - - public BackOfficeUserManager( - IIpResolver ipResolver, - IUserStore store, - IOptions optionsAccessor, - IPasswordHasher passwordHasher, - IEnumerable> userValidators, - IEnumerable> passwordValidators, - BackOfficeLookupNormalizer keyNormalizer, - BackOfficeIdentityErrorDescriber errors, - IServiceProvider services, - IHttpContextAccessor httpContextAccessor, - ILogger> logger, - IOptions passwordConfiguration) - : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) - { - IpResolver = ipResolver ?? throw new ArgumentNullException(nameof(ipResolver)); _httpContextAccessor = httpContextAccessor; - PasswordConfiguration = passwordConfiguration.Value ?? throw new ArgumentNullException(nameof(passwordConfiguration)); - } - // We don't support an IUserClaimStore and don't need to (at least currently) - public override bool SupportsUserClaim => false; - - // It would be nice to support this but we don't need to currently and that would require IQueryable support for our user service/repository - public override bool SupportsQueryableUsers => false; - - /// - /// Developers will need to override this to support custom 2 factor auth - /// - public override bool SupportsUserTwoFactor => false; - - // We haven't needed to support this yet, though might be necessary for 2FA - public override bool SupportsUserPhoneNumber => false; - - /// - /// Replace the underlying options property with our own strongly typed version - /// - public new BackOfficeIdentityOptions Options - { - get => (BackOfficeIdentityOptions)base.Options; - set => base.Options = value; - } - - /// - /// Used to validate a user's session - /// - /// The user id - /// The sesion id - /// True if the sesion is valid, else false - public virtual async Task ValidateSessionIdAsync(string userId, string sessionId) - { - var userSessionStore = Store as IUserSessionStore; - - // if this is not set, for backwards compat (which would be super rare), we'll just approve it - if (userSessionStore == null) - { - return true; - } - - return await userSessionStore.ValidateSessionIdAsync(userId, sessionId); - } - - /// - /// This will determine which password hasher to use based on what is defined in config - /// - /// The - /// An - protected virtual IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) => new PasswordHasher(); - - /// - /// Gets/sets the default back office user password checker - /// - public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get; set; } - public IPasswordConfiguration PasswordConfiguration { get; protected set; } - public IIpResolver IpResolver { get; } - - /// - /// Helper method to generate a password for a user based on the current password validator - /// - /// The generated password - public string GeneratePassword() - { - if (_passwordGenerator == null) - { - _passwordGenerator = new PasswordGenerator(PasswordConfiguration); - } - - var password = _passwordGenerator.GeneratePassword(); - return password; - } - - /// - /// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date - /// - /// The user - /// True if the user is locked out, else false - /// - /// In the ASP.NET Identity world, there is only one value for being locked out, in Umbraco we have 2 so when checking this for Umbraco we need to check both values - /// - public override async Task IsLockedOutAsync(T user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (user.IsApproved == false) - { - return true; - } - - return await base.IsLockedOutAsync(user); - } - - /// - /// Logic used to validate a username and password - /// /// /// /// By default this uses the standard ASP.Net Identity approach which is: @@ -180,7 +58,7 @@ namespace Umbraco.Web.Common.Security /// We've allowed this check to be overridden with a simple callback so that developers don't actually /// have to implement/override this class. /// - public override async Task CheckPasswordAsync(T user, string password) + public override async Task CheckPasswordAsync(BackOfficeIdentityUser user, string password) { if (BackOfficeUserPasswordChecker != null) { @@ -198,36 +76,49 @@ namespace Umbraco.Web.Common.Security } } - // we cannot proceed if the user passed in does not have an identity - if (user.HasIdentity == false) - { - return false; - } - // use the default behavior return await base.CheckPasswordAsync(user, password); } /// - /// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event + /// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date /// - /// The userId - /// The reset password token - /// The new password to set it to - /// The + /// The user + /// True if the user is locked out, else false /// - /// We use this because in the back office the only way an admin can change another user's password without first knowing their password - /// is to generate a token and reset it, however, when we do this we want to track a password change, not a password reset + /// In the ASP.NET Identity world, there is only one value for being locked out, in Umbraco we have 2 so when checking this for Umbraco we need to check both values /// - public async Task ChangePasswordWithResetAsync(int userId, string token, string newPassword) + public override async Task IsLockedOutAsync(BackOfficeIdentityUser user) { - T user = await FindByIdAsync(userId.ToString()); if (user == null) { - throw new InvalidOperationException("Could not find user"); + throw new ArgumentNullException(nameof(user)); } - IdentityResult result = await base.ResetPasswordAsync(user, token, newPassword); + if (user.IsApproved == false) + { + return true; + } + + return await base.IsLockedOutAsync(user); + } + + public override async Task AccessFailedAsync(BackOfficeIdentityUser user) + { + IdentityResult result = await base.AccessFailedAsync(user); + + // Slightly confusing: this will return a Success if we successfully update the AccessFailed count + if (result.Succeeded) + { + RaiseLoginFailedEvent(_httpContextAccessor.HttpContext?.User, user.Id); + } + + return result; + } + + public override async Task ChangePasswordWithResetAsync(int userId, string token, string newPassword) + { + IdentityResult result = await base.ChangePasswordWithResetAsync(userId, token, newPassword); if (result.Succeeded) { RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, userId); @@ -236,79 +127,19 @@ namespace Umbraco.Web.Common.Security return result; } - public override async Task ChangePasswordAsync(T user, string currentPassword, string newPassword) + public override async Task ChangePasswordAsync(BackOfficeIdentityUser user, string currentPassword, string newPassword) { IdentityResult result = await base.ChangePasswordAsync(user, currentPassword, newPassword); if (result.Succeeded) { RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, user.Id); } + return result; } - /// - /// Override to determine how to hash the password - /// /// - protected override async Task UpdatePasswordHash(T user, string newPassword, bool validatePassword) - { - user.LastPasswordChangeDateUtc = DateTime.UtcNow; - - if (validatePassword) - { - IdentityResult validate = await ValidatePasswordAsync(user, newPassword); - if (!validate.Succeeded) - { - return validate; - } - } - - var passwordStore = Store as IUserPasswordStore; - if (passwordStore == null) - { - throw new NotSupportedException("The current user store does not implement " + typeof(IUserPasswordStore<>)); - } - - var hash = newPassword != null ? PasswordHasher.HashPassword(user, newPassword) : null; - await passwordStore.SetPasswordHashAsync(user, hash, CancellationToken); - await UpdateSecurityStampInternal(user); - return IdentityResult.Success; - } - - /// - /// This is copied from the underlying .NET base class since they decided to not expose it - /// - private async Task UpdateSecurityStampInternal(T user) - { - if (SupportsUserSecurityStamp == false) - { - return; - } - - await GetSecurityStore().SetSecurityStampAsync(user, NewSecurityStamp(), CancellationToken.None); - } - - /// - /// This is copied from the underlying .NET base class since they decided to not expose it - /// - private IUserSecurityStampStore GetSecurityStore() - { - var store = Store as IUserSecurityStampStore; - if (store == null) - { - throw new NotSupportedException("The current user store does not implement " + typeof(IUserSecurityStampStore<>)); - } - - return store; - } - - /// - /// This is copied from the underlying .NET base class since they decided to not expose it - /// - private static string NewSecurityStamp() => Guid.NewGuid().ToString(); - - /// - public override async Task SetLockoutEndDateAsync(T user, DateTimeOffset? lockoutEnd) + public override async Task SetLockoutEndDateAsync(BackOfficeIdentityUser user, DateTimeOffset? lockoutEnd) { if (user == null) { @@ -320,7 +151,7 @@ namespace Umbraco.Web.Common.Security // The way we unlock is by setting the lockoutEnd date to the current datetime if (result.Succeeded && lockoutEnd >= DateTimeOffset.UtcNow) { - RaiseAccountLockedEvent(_httpContextAccessor.HttpContext?.User, user.Id); + RaiseAccountLockedEvent(_httpContextAccessor.HttpContext?.User, user.Id); } else { @@ -334,62 +165,12 @@ namespace Umbraco.Web.Common.Security } /// - public override async Task ResetAccessFailedCountAsync(T user) + public override async Task ResetAccessFailedCountAsync(BackOfficeIdentityUser user) { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var lockoutStore = (IUserLockoutStore)Store; - var accessFailedCount = await GetAccessFailedCountAsync(user); - - if (accessFailedCount == 0) - { - return IdentityResult.Success; - } - - await lockoutStore.ResetAccessFailedCountAsync(user, CancellationToken.None); + IdentityResult result = await base.ResetAccessFailedCountAsync(user); // raise the event now that it's reset RaiseResetAccessFailedCountEvent(_httpContextAccessor.HttpContext?.User, user.Id); - return await UpdateAsync(user); - } - - /// - /// Overrides the Microsoft ASP.NET user management method - /// - /// - public override async Task AccessFailedAsync(T user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var lockoutStore = Store as IUserLockoutStore; - if (lockoutStore == null) - { - throw new NotSupportedException("The current user store does not implement " + typeof(IUserLockoutStore<>)); - } - - var count = await lockoutStore.IncrementAccessFailedCountAsync(user, CancellationToken.None); - - if (count >= Options.Lockout.MaxFailedAccessAttempts) - { - await lockoutStore.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan), CancellationToken.None); - - // NOTE: in normal aspnet identity this would do set the number of failed attempts back to 0 - // here we are persisting the value for the back office - } - - IdentityResult result = await UpdateAsync(user); - - // Slightly confusing: this will return a Success if we successfully update the AccessFailed count - if (result.Succeeded) - { - RaiseLoginFailedEvent(_httpContextAccessor.HttpContext?.User, user.Id); - } return result; } diff --git a/src/Umbraco.Web.Common/Security/UmbracoUserManager.cs b/src/Umbraco.Web.Common/Security/UmbracoUserManager.cs new file mode 100644 index 0000000000..a555cca4be --- /dev/null +++ b/src/Umbraco.Web.Common/Security/UmbracoUserManager.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Core.BackOffice; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Models.Identity; +using Umbraco.Core.Security; +using Umbraco.Net; + + +namespace Umbraco.Web.Common.Security +{ + + /// + /// Abstract class for Umbraco User Managers for back office users or front-end members + /// + /// The type of user + public abstract class UmbracoUserManager : UserManager + where T : UmbracoIdentityUser + { + private PasswordGenerator _passwordGenerator; + + /// + /// Initializes a new instance of the class. + /// + public UmbracoUserManager( + IIpResolver ipResolver, + IUserStore store, + IOptions optionsAccessor, + IPasswordHasher passwordHasher, + IEnumerable> userValidators, + IEnumerable> passwordValidators, + BackOfficeLookupNormalizer keyNormalizer, + BackOfficeIdentityErrorDescriber errors, + IServiceProvider services, + ILogger> logger, + IOptions passwordConfiguration) + : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) + { + IpResolver = ipResolver ?? throw new ArgumentNullException(nameof(ipResolver)); + PasswordConfiguration = passwordConfiguration.Value ?? throw new ArgumentNullException(nameof(passwordConfiguration)); + } + + // We don't support an IUserClaimStore and don't need to (at least currently) + public override bool SupportsUserClaim => false; + + // It would be nice to support this but we don't need to currently and that would require IQueryable support for our user service/repository + public override bool SupportsQueryableUsers => false; + + /// + /// Developers will need to override this to support custom 2 factor auth + /// + public override bool SupportsUserTwoFactor => false; + + // We haven't needed to support this yet, though might be necessary for 2FA + public override bool SupportsUserPhoneNumber => false; + + /// + /// Used to validate a user's session + /// + /// The user id + /// The sesion id + /// True if the sesion is valid, else false + public virtual async Task ValidateSessionIdAsync(string userId, string sessionId) + { + var userSessionStore = Store as IUserSessionStore; + + // if this is not set, for backwards compat (which would be super rare), we'll just approve it + if (userSessionStore == null) + { + return true; + } + + return await userSessionStore.ValidateSessionIdAsync(userId, sessionId); + } + + /// + /// This will determine which password hasher to use based on what is defined in config + /// + /// The + /// An + protected virtual IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) => new PasswordHasher(); + + /// + /// Gets or sets the default back office user password checker + /// + public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get; set; } + + public IPasswordConfiguration PasswordConfiguration { get; protected set; } + + public IIpResolver IpResolver { get; } + + /// + /// Helper method to generate a password for a user based on the current password validator + /// + /// The generated password + public string GeneratePassword() + { + if (_passwordGenerator == null) + { + _passwordGenerator = new PasswordGenerator(PasswordConfiguration); + } + + var password = _passwordGenerator.GeneratePassword(); + return password; + } + + /// + public override async Task CheckPasswordAsync(T user, string password) + { + // we cannot proceed if the user passed in does not have an identity + if (user.HasIdentity == false) + { + return false; + } + + // use the default behavior + return await base.CheckPasswordAsync(user, password); + } + + /// + /// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event + /// + /// The userId + /// The reset password token + /// The new password to set it to + /// The + /// + /// We use this because in the back office the only way an admin can change another user's password without first knowing their password + /// is to generate a token and reset it, however, when we do this we want to track a password change, not a password reset + /// + public virtual async Task ChangePasswordWithResetAsync(int userId, string token, string newPassword) + { + T user = await FindByIdAsync(userId.ToString()); + if (user == null) + { + throw new InvalidOperationException("Could not find user"); + } + + IdentityResult result = await base.ResetPasswordAsync(user, token, newPassword); + return result; + } + + /// + /// This is copied from the underlying .NET base class since they decided to not expose it + /// + private IUserSecurityStampStore GetSecurityStore() + { + var store = Store as IUserSecurityStampStore; + if (store == null) + { + throw new NotSupportedException("The current user store does not implement " + typeof(IUserSecurityStampStore<>)); + } + + return store; + } + + /// + /// This is copied from the underlying .NET base class since they decided to not expose it + /// + private static string NewSecurityStamp() => Guid.NewGuid().ToString(); + + /// + public override async Task SetLockoutEndDateAsync(T user, DateTimeOffset? lockoutEnd) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + IdentityResult result = await base.SetLockoutEndDateAsync(user, lockoutEnd); + + // The way we unlock is by setting the lockoutEnd date to the current datetime + if (!result.Succeeded || lockoutEnd < DateTimeOffset.UtcNow) + { + // Resets the login attempt fails back to 0 when unlock is clicked + await ResetAccessFailedCountAsync(user); + } + + return result; + } + + /// + public override async Task ResetAccessFailedCountAsync(T user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var lockoutStore = (IUserLockoutStore)Store; + var accessFailedCount = await GetAccessFailedCountAsync(user); + + if (accessFailedCount == 0) + { + return IdentityResult.Success; + } + + await lockoutStore.ResetAccessFailedCountAsync(user, CancellationToken.None); + + return await UpdateAsync(user); + } + + /// + /// Overrides the Microsoft ASP.NET user management method + /// + /// + public override async Task AccessFailedAsync(T user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var lockoutStore = Store as IUserLockoutStore; + if (lockoutStore == null) + { + throw new NotSupportedException("The current user store does not implement " + typeof(IUserLockoutStore<>)); + } + + var count = await lockoutStore.IncrementAccessFailedCountAsync(user, CancellationToken.None); + + if (count >= Options.Lockout.MaxFailedAccessAttempts) + { + await lockoutStore.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan), CancellationToken.None); + + // NOTE: in normal aspnet identity this would do set the number of failed attempts back to 0 + // here we are persisting the value for the back office + } + + IdentityResult result = await UpdateAsync(user); + return result; + } + + } +} From aeec18d80828a665a01a4983d44b1a0c89aa0984 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Dec 2020 01:50:30 +1100 Subject: [PATCH 44/63] removes the interface --- .../Models/Identity/IIdentityUser.cs | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 src/Umbraco.Core/Models/Identity/IIdentityUser.cs diff --git a/src/Umbraco.Core/Models/Identity/IIdentityUser.cs b/src/Umbraco.Core/Models/Identity/IIdentityUser.cs deleted file mode 100644 index fa7f52c710..0000000000 --- a/src/Umbraco.Core/Models/Identity/IIdentityUser.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Models.Identity -{ - public interface IIdentityUser - { - int AccessFailedCount { get; set; } - //ICollection Claims { get; } - string Email { get; set; } - bool EmailConfirmed { get; set; } - TKey Id { get; set; } - DateTime? LastLoginDateUtc { get; set; } - DateTime? LastPasswordChangeDateUtc { get; set; } - bool LockoutEnabled { get; set; } - DateTime? LockoutEndDateUtc { get; set; } - //ICollection Logins { get; } - string PasswordHash { get; set; } - string PhoneNumber { get; set; } - bool PhoneNumberConfirmed { get; set; } - //ICollection Roles { get; } - string SecurityStamp { get; set; } - bool TwoFactorEnabled { get; set; } - string UserName { get; set; } - } -} From 86d231f5de05a55adefff362b25fd29dabcd4ffe Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Dec 2020 02:21:21 +1100 Subject: [PATCH 45/63] removes remaining back office things from underlying UmbracoUserManager moves files --- .../IBackOfficeUserPasswordChecker.cs | 3 - .../Install/InstallSteps/NewInstallStep.cs | 5 +- .../BackOfficeClaimsPrincipalFactory.cs | 2 +- .../BackOfficeIdentityBuilder.cs | 24 ++++---- .../BackOfficeIdentityErrorDescriber.cs | 5 +- .../BackOfficeIdentityOptions.cs | 4 +- .../BackOfficeLookupNormalizer.cs | 6 +- .../BackOfficeUserStore.cs | 2 +- .../BackOfficeUserValidator.cs | 4 +- .../IBackOfficeUserManager.cs | 2 +- .../IUmbracoUserManager.cs | 2 +- .../IUserSessionStore.cs | 2 +- .../IdentityExtensions.cs | 0 .../Security/SignOutAuditEventArgs.cs | 4 +- .../Security/UserInviteEventArgs.cs | 6 +- ...kOfficeServiceCollectionExtensionsTests.cs | 5 +- .../BackOfficeClaimsPrincipalFactoryTests.cs | 1 - .../BackOffice/NopLookupNormalizerTests.cs | 4 +- .../BackOfficeLookupNormalizerTests.cs | 4 +- .../Controllers/UsersControllerUnitTests.cs | 3 - .../Controllers/AuthenticationController.cs | 8 +-- .../Controllers/BackOfficeController.cs | 21 ++++--- .../Controllers/CurrentUserController.cs | 10 ++-- .../Controllers/UsersController.cs | 18 +++--- .../BackOfficeApplicationBuilderExtensions.cs | 1 - .../BackOfficeServiceCollectionExtensions.cs | 5 -- .../Extensions/WebMappingProfiles.cs | 5 +- .../Security/BackOfficeSessionIdValidator.cs | 4 +- .../Security/BackOfficeUserManagerAuditer.cs | 4 +- .../ConfigureBackOfficeIdentityOptions.cs | 4 +- .../Security/PasswordChanger.cs | 4 +- .../Security/BackOfficeUserManager.cs | 11 ++-- .../Security/UmbracoUserManager.cs | 59 +++++++++---------- ...eDirectoryBackOfficeUserPasswordChecker.cs | 2 - .../BackOfficeCookieAuthenticationProvider.cs | 46 --------------- .../Security/BackOfficeSignInManager.cs | 47 --------------- src/Umbraco.Web/Umbraco.Web.csproj | 4 +- 37 files changed, 111 insertions(+), 230 deletions(-) rename src/Umbraco.Infrastructure/{BackOffice => Security}/BackOfficeClaimsPrincipalFactory.cs (99%) rename src/Umbraco.Infrastructure/{BackOffice => Security}/BackOfficeIdentityBuilder.cs (69%) rename src/Umbraco.Infrastructure/{BackOffice => Security}/BackOfficeIdentityErrorDescriber.cs (55%) rename src/Umbraco.Infrastructure/{BackOffice => Security}/BackOfficeIdentityOptions.cs (72%) rename src/Umbraco.Infrastructure/{BackOffice => Security}/BackOfficeLookupNormalizer.cs (75%) rename src/Umbraco.Infrastructure/{BackOffice => Security}/BackOfficeUserStore.cs (99%) rename src/Umbraco.Infrastructure/{BackOffice => Security}/BackOfficeUserValidator.cs (90%) rename src/Umbraco.Infrastructure/{BackOffice => Security}/IBackOfficeUserManager.cs (86%) rename src/Umbraco.Infrastructure/{BackOffice => Security}/IUmbracoUserManager.cs (99%) rename src/Umbraco.Infrastructure/{BackOffice => Security}/IUserSessionStore.cs (92%) rename src/Umbraco.Infrastructure/{BackOffice => Security}/IdentityExtensions.cs (100%) delete mode 100644 src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs delete mode 100644 src/Umbraco.Web/Security/BackOfficeSignInManager.cs diff --git a/src/Umbraco.Core/Security/IBackOfficeUserPasswordChecker.cs b/src/Umbraco.Core/Security/IBackOfficeUserPasswordChecker.cs index 45f5ea44e2..fdf1f1fcf2 100644 --- a/src/Umbraco.Core/Security/IBackOfficeUserPasswordChecker.cs +++ b/src/Umbraco.Core/Security/IBackOfficeUserPasswordChecker.cs @@ -11,9 +11,6 @@ namespace Umbraco.Core.Security /// /// Checks a password for a user /// - /// - /// - /// /// /// This will allow a developer to auto-link a local account which is required if the user queried doesn't exist locally. /// The user parameter will always contain the username, if the user doesn't exist locally, the other properties will not be filled in. diff --git a/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs index 96e4a9ae34..80570ae5de 100644 --- a/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs +++ b/src/Umbraco.Infrastructure/Install/InstallSteps/NewInstallStep.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Specialized; using System.Net.Http; using System.Text; @@ -6,10 +6,9 @@ using System.Threading.Tasks; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Umbraco.Core; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Extensions; using Umbraco.Web.Install.Models; diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs b/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs similarity index 99% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs rename to src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs index 380ed452d0..8a6680d2bf 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeClaimsPrincipalFactory.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Umbraco.Core.Security; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// /// A diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityBuilder.cs b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityBuilder.cs similarity index 69% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityBuilder.cs rename to src/Umbraco.Infrastructure/Security/BackOfficeIdentityBuilder.cs index 90c2823122..c9f8d35ada 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityBuilder.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityBuilder.cs @@ -1,19 +1,25 @@ -using System; +using System; using System.Reflection; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Security; -namespace Umbraco.Infrastructure.BackOffice +namespace Umbraco.Core.Security { public class BackOfficeIdentityBuilder : IdentityBuilder { - public BackOfficeIdentityBuilder(IServiceCollection services) : base(typeof(BackOfficeIdentityUser), services) + /// + /// Initializes a new instance of the class. + /// + public BackOfficeIdentityBuilder(IServiceCollection services) + : base(typeof(BackOfficeIdentityUser), services) { } - public BackOfficeIdentityBuilder(Type role, IServiceCollection services) : base(typeof(BackOfficeIdentityUser), role, services) + /// + /// Initializes a new instance of the class. + /// + public BackOfficeIdentityBuilder(Type role, IServiceCollection services) + : base(typeof(BackOfficeIdentityUser), role, services) { } @@ -29,10 +35,8 @@ namespace Umbraco.Infrastructure.BackOffice { throw new InvalidOperationException($"Invalid Type for TokenProvider: {provider.FullName}"); } - Services.Configure(options => - { - options.Tokens.ProviderMap[providerName] = new TokenProviderDescriptor(provider); - }); + + Services.Configure(options => options.Tokens.ProviderMap[providerName] = new TokenProviderDescriptor(provider)); Services.AddTransient(provider); return this; } diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityErrorDescriber.cs b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityErrorDescriber.cs similarity index 55% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityErrorDescriber.cs rename to src/Umbraco.Infrastructure/Security/BackOfficeIdentityErrorDescriber.cs index 012ac5650f..6d36e489b8 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityErrorDescriber.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityErrorDescriber.cs @@ -1,11 +1,12 @@ -using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// /// Umbraco back office specific /// public class BackOfficeIdentityErrorDescriber : IdentityErrorDescriber { + // TODO: Override all the methods in order to provide our own translated error messages } } diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityOptions.cs b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityOptions.cs similarity index 72% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityOptions.cs rename to src/Umbraco.Infrastructure/Security/BackOfficeIdentityOptions.cs index 2f729072a6..77849c4d0c 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityOptions.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityOptions.cs @@ -1,6 +1,6 @@ -using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// /// Identity options specifically for the back office identity implementation diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizer.cs b/src/Umbraco.Infrastructure/Security/BackOfficeLookupNormalizer.cs similarity index 75% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizer.cs rename to src/Umbraco.Infrastructure/Security/BackOfficeLookupNormalizer.cs index cc9249d462..957e36d1d0 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizer.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeLookupNormalizer.cs @@ -1,6 +1,6 @@ -using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// @@ -8,6 +8,8 @@ namespace Umbraco.Core.BackOffice /// public class BackOfficeLookupNormalizer : ILookupNormalizer { + // TODO: Do we need this? + public string NormalizeName(string name) => name; public string NormalizeEmail(string email) => email; diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs similarity index 99% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs rename to src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs index e74690b76f..4b4383c402 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs @@ -15,7 +15,7 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Security; using Umbraco.Core.Services; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { // TODO: Make this into a base class that can be re-used diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserValidator.cs b/src/Umbraco.Infrastructure/Security/BackOfficeUserValidator.cs similarity index 90% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeUserValidator.cs rename to src/Umbraco.Infrastructure/Security/BackOfficeUserValidator.cs index b7cbb7555d..8b2c8932a7 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserValidator.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeUserValidator.cs @@ -1,8 +1,8 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Umbraco.Core.Security; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { public class BackOfficeUserValidator : UserValidator where T : BackOfficeIdentityUser diff --git a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs b/src/Umbraco.Infrastructure/Security/IBackOfficeUserManager.cs similarity index 86% rename from src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs rename to src/Umbraco.Infrastructure/Security/IBackOfficeUserManager.cs index be4bd194f9..4235195bb1 100644 --- a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs +++ b/src/Umbraco.Infrastructure/Security/IBackOfficeUserManager.cs @@ -1,6 +1,6 @@ using Umbraco.Core.Security; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// /// The user manager for the back office diff --git a/src/Umbraco.Infrastructure/BackOffice/IUmbracoUserManager.cs b/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs similarity index 99% rename from src/Umbraco.Infrastructure/BackOffice/IUmbracoUserManager.cs rename to src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs index 803f64e0e6..c50b012dae 100644 --- a/src/Umbraco.Infrastructure/BackOffice/IUmbracoUserManager.cs +++ b/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs @@ -8,7 +8,7 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; using Umbraco.Web.Models.ContentEditing; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// diff --git a/src/Umbraco.Infrastructure/BackOffice/IUserSessionStore.cs b/src/Umbraco.Infrastructure/Security/IUserSessionStore.cs similarity index 92% rename from src/Umbraco.Infrastructure/BackOffice/IUserSessionStore.cs rename to src/Umbraco.Infrastructure/Security/IUserSessionStore.cs index 69d5408cf7..06b7c2f165 100644 --- a/src/Umbraco.Infrastructure/BackOffice/IUserSessionStore.cs +++ b/src/Umbraco.Infrastructure/Security/IUserSessionStore.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// /// An IUserStore interface part to implement if the store supports validating user session Ids diff --git a/src/Umbraco.Infrastructure/BackOffice/IdentityExtensions.cs b/src/Umbraco.Infrastructure/Security/IdentityExtensions.cs similarity index 100% rename from src/Umbraco.Infrastructure/BackOffice/IdentityExtensions.cs rename to src/Umbraco.Infrastructure/Security/IdentityExtensions.cs diff --git a/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs b/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs index 34bd9c9a42..2e5997b603 100644 --- a/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs +++ b/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs @@ -1,6 +1,6 @@ -using Umbraco.Core.Security; +using Umbraco.Core.Security; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { /// diff --git a/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs b/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs index 2aefb47c14..811092a2c9 100644 --- a/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs +++ b/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs @@ -1,8 +1,8 @@ -using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; using Umbraco.Web.Models.ContentEditing; -namespace Umbraco.Core.BackOffice +namespace Umbraco.Core.Security { public class UserInviteEventArgs : IdentityAuditEventArgs { @@ -25,7 +25,7 @@ namespace Umbraco.Core.BackOffice /// /// The local user that has been created that is pending the invite - /// + /// public IUser User { get; } /// diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs index b6a86344a2..bf198d9819 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs @@ -2,10 +2,9 @@ using System; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; -using Umbraco.Extensions; -using Umbraco.Core.BackOffice; -using Umbraco.Tests.Integration.Testing; using Umbraco.Core.Security; +using Umbraco.Extensions; +using Umbraco.Tests.Integration.Testing; namespace Umbraco.Tests.Integration.Umbraco.Web.BackOffice { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs index 9d8edbc75e..f85c15b3bf 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/NopLookupNormalizerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/NopLookupNormalizerTests.cs index 1447b7f97e..02ff01ff3b 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/NopLookupNormalizerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/NopLookupNormalizerTests.cs @@ -1,6 +1,6 @@ -using System; +using System; using NUnit.Framework; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizerTests.cs index 3feb458fe8..8172a712d8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizerTests.cs @@ -1,6 +1,6 @@ -using System; +using System; using NUnit.Framework; -using Umbraco.Core.BackOffice; +using Umbraco.Core.Security; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs index 6ecda57cc6..4f4db85e5e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs @@ -1,9 +1,6 @@ -using System.Threading; using AutoFixture.NUnit3; -using Microsoft.AspNetCore.Identity; using Moq; using NUnit.Framework; -using Umbraco.Core.BackOffice; using Umbraco.Core.Security; using Umbraco.Tests.UnitTests.AutoFixture; using Umbraco.Web.BackOffice.Controllers; diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index efe28763f1..f7e10d77af 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Security.Claims; using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Mapping; using Umbraco.Core.Models; @@ -26,6 +25,7 @@ using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.Security; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.Controllers; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Common.Filters; @@ -33,8 +33,6 @@ using Umbraco.Web.Common.Security; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; using Constants = Umbraco.Core.Constants; -using Microsoft.AspNetCore.Authorization; -using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Controllers { diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index 1ce0831502..89b121b575 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -1,15 +1,19 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Grid; @@ -22,21 +26,16 @@ using Umbraco.Core.WebAssets; using Umbraco.Extensions; using Umbraco.Web.BackOffice.ActionResults; using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.BackOffice.Security; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.Filters; using Umbraco.Web.Common.Security; using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.WebAssets; using Constants = Umbraco.Core.Constants; -using Microsoft.AspNetCore.Identity; -using System.Security.Claims; -using Microsoft.AspNetCore.Http; -using Umbraco.Web.BackOffice.Security; -using Umbraco.Web.Common.ActionsResults; -using Microsoft.AspNetCore.Authorization; -using Umbraco.Web.Common.Authorization; -using Microsoft.AspNetCore.Authentication; namespace Umbraco.Web.BackOffice.Controllers { diff --git a/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs b/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs index 7c984e901e..d156551c26 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs @@ -1,15 +1,15 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Newtonsoft.Json; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; @@ -23,12 +23,10 @@ using Umbraco.Extensions; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.Security; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.Exceptions; -using Umbraco.Web.Common.Filters; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; -using Microsoft.AspNetCore.Authorization; -using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Controllers { diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index 38bf69721a..5052f5146e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -6,13 +6,13 @@ using System.Net; using System.Runtime.Serialization; using System.Security.Cryptography; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; @@ -26,23 +26,21 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.Web.Models; -using Umbraco.Web.Models.ContentEditing; using Umbraco.Extensions; +using Umbraco.Web.BackOffice.ActionResults; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.ModelBinders; using Umbraco.Web.BackOffice.Security; -using Umbraco.Web.BackOffice.ActionResults; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Editors; +using Umbraco.Web.Models; +using Umbraco.Web.Models.ContentEditing; using Constants = Umbraco.Core.Constants; using IUser = Umbraco.Core.Models.Membership.IUser; using Task = System.Threading.Tasks.Task; -using Umbraco.Net; -using Umbraco.Web.Common.ActionsResults; -using Microsoft.AspNetCore.Authorization; -using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Controllers { diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs index a097ead4a1..6ff42a5737 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs @@ -2,7 +2,6 @@ using System; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using SixLabors.ImageSharp.Web.DependencyInjection; -using Umbraco.Core.BackOffice; using Umbraco.Web.BackOffice.Middleware; using Umbraco.Web.BackOffice.Routing; using Umbraco.Web.Common.Security; diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs index 74953b19be..9ad448a603 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs @@ -1,18 +1,13 @@ -using System; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Security; using Umbraco.Core.Serialization; -using Umbraco.Infrastructure.BackOffice; using Umbraco.Net; using Umbraco.Web.Actions; using Umbraco.Web.BackOffice.Authorization; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.Security; using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.Authorization; diff --git a/src/Umbraco.Web.BackOffice/Extensions/WebMappingProfiles.cs b/src/Umbraco.Web.BackOffice/Extensions/WebMappingProfiles.cs index 600ff101fe..6df63a1655 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/WebMappingProfiles.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/WebMappingProfiles.cs @@ -1,8 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core; -using Umbraco.Core.BackOffice; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.Builder; -using Umbraco.Core.Composing; using Umbraco.Core.Mapping; using Umbraco.Web.BackOffice.Mapping; diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs index b5974c870a..1ccb94e988 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs @@ -1,4 +1,4 @@ - + using System; using System.Security.Claims; using System.Threading.Tasks; @@ -7,9 +7,9 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; +using Umbraco.Core.Security; using Umbraco.Extensions; namespace Umbraco.Web.BackOffice.Security diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs index 5f0757ea9c..ef6d278554 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs @@ -1,8 +1,6 @@ -using Microsoft.Extensions.Options; using System; -using System.Threading.Tasks; +using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Compose; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Membership; diff --git a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeIdentityOptions.cs b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeIdentityOptions.cs index 31b5de2e43..989c852350 100644 --- a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeIdentityOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeIdentityOptions.cs @@ -1,11 +1,11 @@ -using System; +using System; using System.Security.Claims; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Security; namespace Umbraco.Web.BackOffice.Security { diff --git a/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs b/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs index 1a4298cd6b..dd92801d59 100644 --- a/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs +++ b/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Models; +using Umbraco.Core.Security; using Umbraco.Extensions; using Umbraco.Web.Models; using IUser = Umbraco.Core.Models.Membership.IUser; diff --git a/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs index 9f77cdb7d4..230faeff28 100644 --- a/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs @@ -1,17 +1,13 @@ using System; using System.Collections.Generic; using System.Security.Principal; -using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; using Umbraco.Extensions; @@ -21,7 +17,7 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Common.Security { - public class BackOfficeUserManager : UmbracoUserManager, IBackOfficeUserManager + public class BackOfficeUserManager : UmbracoUserManager, IBackOfficeUserManager { private readonly IHttpContextAccessor _httpContextAccessor; @@ -43,6 +39,11 @@ namespace Umbraco.Web.Common.Security _httpContextAccessor = httpContextAccessor; } + /// + /// Gets or sets the default back office user password checker + /// + public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get; set; } // TODO: This isn't a good way to set this, it needs to be injected + /// /// /// By default this uses the standard ASP.Net Identity approach which is: diff --git a/src/Umbraco.Web.Common/Security/UmbracoUserManager.cs b/src/Umbraco.Web.Common/Security/UmbracoUserManager.cs index 675262fd7a..a9f7b0ae74 100644 --- a/src/Umbraco.Web.Common/Security/UmbracoUserManager.cs +++ b/src/Umbraco.Web.Common/Security/UmbracoUserManager.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Identity; @@ -18,27 +17,29 @@ namespace Umbraco.Web.Common.Security /// /// Abstract class for Umbraco User Managers for back office users or front-end members /// - /// The type of user - public abstract class UmbracoUserManager : UserManager - where T : UmbracoIdentityUser + /// The type of user + /// /// The type password config + public abstract class UmbracoUserManager : UserManager + where TUser : UmbracoIdentityUser + where TPasswordConfig: class, IPasswordConfiguration, new() { private PasswordGenerator _passwordGenerator; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public UmbracoUserManager( IIpResolver ipResolver, - IUserStore store, - IOptions optionsAccessor, - IPasswordHasher passwordHasher, - IEnumerable> userValidators, - IEnumerable> passwordValidators, - BackOfficeLookupNormalizer keyNormalizer, - BackOfficeIdentityErrorDescriber errors, + IUserStore store, + IOptions optionsAccessor, + IPasswordHasher passwordHasher, + IEnumerable> userValidators, + IEnumerable> passwordValidators, + ILookupNormalizer keyNormalizer, + IdentityErrorDescriber errors, IServiceProvider services, - ILogger> logger, - IOptions passwordConfiguration) + ILogger> logger, + IOptions passwordConfiguration) : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) { IpResolver = ipResolver ?? throw new ArgumentNullException(nameof(ipResolver)); @@ -67,9 +68,10 @@ namespace Umbraco.Web.Common.Security /// True if the sesion is valid, else false public virtual async Task ValidateSessionIdAsync(string userId, string sessionId) { - var userSessionStore = Store as IUserSessionStore; + var userSessionStore = Store as IUserSessionStore; // if this is not set, for backwards compat (which would be super rare), we'll just approve it + // TODO: This should be removed after members supports this if (userSessionStore == null) { return true; @@ -83,14 +85,9 @@ namespace Umbraco.Web.Common.Security /// /// The /// An - protected virtual IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) => new PasswordHasher(); + protected virtual IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) => new PasswordHasher(); - /// - /// Gets or sets the default back office user password checker - /// - public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get; set; } - - public IPasswordConfiguration PasswordConfiguration { get; protected set; } + public IPasswordConfiguration PasswordConfiguration { get; } public IIpResolver IpResolver { get; } @@ -110,7 +107,7 @@ namespace Umbraco.Web.Common.Security } /// - public override async Task CheckPasswordAsync(T user, string password) + public override async Task CheckPasswordAsync(TUser user, string password) { // we cannot proceed if the user passed in does not have an identity if (user.HasIdentity == false) @@ -135,7 +132,7 @@ namespace Umbraco.Web.Common.Security /// public virtual async Task ChangePasswordWithResetAsync(int userId, string token, string newPassword) { - T user = await FindByIdAsync(userId.ToString()); + TUser user = await FindByIdAsync(userId.ToString()); if (user == null) { throw new InvalidOperationException("Could not find user"); @@ -148,9 +145,9 @@ namespace Umbraco.Web.Common.Security /// /// This is copied from the underlying .NET base class since they decided to not expose it /// - private IUserSecurityStampStore GetSecurityStore() + private IUserSecurityStampStore GetSecurityStore() { - var store = Store as IUserSecurityStampStore; + var store = Store as IUserSecurityStampStore; if (store == null) { throw new NotSupportedException("The current user store does not implement " + typeof(IUserSecurityStampStore<>)); @@ -165,7 +162,7 @@ namespace Umbraco.Web.Common.Security private static string NewSecurityStamp() => Guid.NewGuid().ToString(); /// - public override async Task SetLockoutEndDateAsync(T user, DateTimeOffset? lockoutEnd) + public override async Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd) { if (user == null) { @@ -185,14 +182,14 @@ namespace Umbraco.Web.Common.Security } /// - public override async Task ResetAccessFailedCountAsync(T user) + public override async Task ResetAccessFailedCountAsync(TUser user) { if (user == null) { throw new ArgumentNullException(nameof(user)); } - var lockoutStore = (IUserLockoutStore)Store; + var lockoutStore = (IUserLockoutStore)Store; var accessFailedCount = await GetAccessFailedCountAsync(user); if (accessFailedCount == 0) @@ -209,14 +206,14 @@ namespace Umbraco.Web.Common.Security /// Overrides the Microsoft ASP.NET user management method /// /// - public override async Task AccessFailedAsync(T user) + public override async Task AccessFailedAsync(TUser user) { if (user == null) { throw new ArgumentNullException(nameof(user)); } - var lockoutStore = Store as IUserLockoutStore; + var lockoutStore = Store as IUserLockoutStore; if (lockoutStore == null) { throw new NotSupportedException("The current user store does not implement " + typeof(IUserLockoutStore<>)); diff --git a/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs b/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs index 901e7bf81b..46b6540d73 100644 --- a/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs +++ b/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs @@ -2,8 +2,6 @@ using System; using System.DirectoryServices.AccountManagement; using System.Threading.Tasks; using Microsoft.Extensions.Options; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Security; diff --git a/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs b/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs deleted file mode 100644 index 6ce61c90d6..0000000000 --- a/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.Extensions.Options; -using Microsoft.Owin; -using Microsoft.Owin.Security.Cookies; -using Umbraco.Core; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Services; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Hosting; -using Umbraco.Core.Security; - -namespace Umbraco.Web.Security -{ - // TODO: Migrate this logic to cookie events in ConfigureUmbracoBackOfficeCookieOptions - - public class BackOfficeCookieAuthenticationProvider : CookieAuthenticationProvider - { - private readonly IUserService _userService; - private readonly IRuntimeState _runtimeState; - private readonly GlobalSettings _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly SecuritySettings _securitySettings; - - public BackOfficeCookieAuthenticationProvider(IUserService userService, IRuntimeState runtimeState, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IOptions securitySettings) - { - _userService = userService; - _runtimeState = runtimeState; - _globalSettings = globalSettings; - _hostingEnvironment = hostingEnvironment; - _securitySettings = securitySettings.Value; - } - - - public override void ResponseSignOut(CookieResponseSignOutContext context) - { - - } - - - - } -} diff --git a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs deleted file mode 100644 index 010c2d4d33..0000000000 --- a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Diagnostics; -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Options; -using Microsoft.Owin; -using Microsoft.Owin.Logging; -using Microsoft.Owin.Security; -using Umbraco.Core; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Security; - -namespace Umbraco.Web.Security -{ - // TODO: This has been migrated to netcore - public class BackOfficeSignInManager : IDisposable - { - private readonly IBackOfficeUserManager _userManager; - private readonly IUserClaimsPrincipalFactory _claimsPrincipalFactory; - private readonly IAuthenticationManager _authenticationManager; - private readonly ILogger _logger; - private readonly GlobalSettings _globalSettings; - private readonly IOwinRequest _request; - - public BackOfficeSignInManager( - IBackOfficeUserManager userManager, - IUserClaimsPrincipalFactory claimsPrincipalFactory, - IAuthenticationManager authenticationManager, - ILogger logger, - GlobalSettings globalSettings, - IOwinRequest request) - { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _claimsPrincipalFactory = claimsPrincipalFactory ?? throw new ArgumentNullException(nameof(claimsPrincipalFactory)); - _authenticationManager = authenticationManager ?? throw new ArgumentNullException(nameof(authenticationManager)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); - _request = request ?? throw new ArgumentNullException(nameof(request)); - } - - public void Dispose() - { - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index b1ddf26b05..50d379102d 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -159,7 +159,6 @@ - @@ -181,7 +180,6 @@ - @@ -306,4 +304,4 @@ - + \ No newline at end of file From 4dea624e23cfef3b1d95d47c35b9d2539df76530 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Dec 2020 02:28:11 +1100 Subject: [PATCH 46/63] remove code --- src/Umbraco.Web.Common/Security/UmbracoUserManager.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Umbraco.Web.Common/Security/UmbracoUserManager.cs b/src/Umbraco.Web.Common/Security/UmbracoUserManager.cs index a9f7b0ae74..68b9011aa4 100644 --- a/src/Umbraco.Web.Common/Security/UmbracoUserManager.cs +++ b/src/Umbraco.Web.Common/Security/UmbracoUserManager.cs @@ -156,11 +156,6 @@ namespace Umbraco.Web.Common.Security return store; } - /// - /// This is copied from the underlying .NET base class since they decided to not expose it - /// - private static string NewSecurityStamp() => Guid.NewGuid().ToString(); - /// public override async Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd) { From 5172b0e58a7c56ac0ba3ff00d47e1212f14f5415 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Dec 2020 12:44:27 +1100 Subject: [PATCH 47/63] Updates user manager, user store and identity user to use the aspnetcore base classes instead of copies of our own, uses string ids for user and roles to simplify everything and to allow for sharing between members --- src/Umbraco.Core/Constants-Security.cs | 6 +- .../Models/Identity/ExternalLogin.cs | 5 +- .../Models/Identity/IExternalLogin.cs | 13 +- .../Models/Identity/IdentityUserClaim.cs | 28 - .../Models/Identity/IdentityUserLogin.cs | 6 + .../Models/Identity/IdentityUserRole.cs | 19 - .../Security/IdentityAuditEventArgs.cs | 10 +- .../Security/UmbracoBackOfficeIdentity.cs | 8 +- .../CoreMappingProfiles.cs | 3 +- .../Security/BackOfficeIdentityUser.cs | 33 +- .../Security/BackOfficeUserStore.cs | 757 ++++++++---------- .../IBackOfficeUserPasswordChecker.cs | 0 .../Security/IUmbracoUserManager.cs | 24 +- .../Security/IUserSessionStore.cs | 8 +- .../Security/IdentityMapDefinition.cs | 2 +- .../Security/SignOutAuditEventArgs.cs | 2 +- .../Security}/UmbracoIdentityUser.cs | 76 +- .../Security/UmbracoUserManager.cs | 32 +- .../Security/UserInviteEventArgs.cs | 4 +- ...kOfficeServiceCollectionExtensionsTests.cs | 1 + .../BackOfficeClaimsPrincipalFactoryTests.cs | 4 +- .../UmbracoBackOfficeIdentityTests.cs | 6 +- .../ClaimsPrincipalExtensionsTests.cs | 4 +- .../Security/BackOfficeAntiforgeryTests.cs | 2 +- .../AuthenticateEverythingMiddleware.cs | 5 +- .../Controllers/AuthenticationController.cs | 12 +- .../Controllers/BackOfficeController.cs | 2 +- .../Security/BackOfficeUserManagerAuditer.cs | 89 +- .../Security/PasswordChanger.cs | 2 +- .../Security/BackOfficeUserManager.cs | 30 +- 30 files changed, 541 insertions(+), 652 deletions(-) delete mode 100644 src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs delete mode 100644 src/Umbraco.Core/Models/Identity/IdentityUserRole.cs rename src/{Umbraco.Core => Umbraco.Infrastructure}/Security/BackOfficeIdentityUser.cs (85%) rename src/{Umbraco.Core => Umbraco.Infrastructure}/Security/IBackOfficeUserPasswordChecker.cs (100%) rename src/{Umbraco.Core => Umbraco.Infrastructure}/Security/IdentityMapDefinition.cs (96%) rename src/{Umbraco.Core/Models/Identity => Umbraco.Infrastructure/Security}/UmbracoIdentityUser.cs (81%) rename src/{Umbraco.Web.Common => Umbraco.Infrastructure}/Security/UmbracoUserManager.cs (90%) diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 24b8b20731..9a4936d42d 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core +namespace Umbraco.Core { public static partial class Constants { @@ -11,6 +11,8 @@ /// public const int SuperUserId = -1; + public const string SuperUserIdAsString = "-1"; + /// /// The id for the 'unknown' user. /// @@ -22,7 +24,7 @@ /// /// The name of the 'unknown' user. /// - public const string UnknownUserName = "SYTEM"; + public const string UnknownUserName = "SYSTEM"; public const string AdminGroupAlias = "admin"; public const string EditorGroupAlias = "editor"; diff --git a/src/Umbraco.Core/Models/Identity/ExternalLogin.cs b/src/Umbraco.Core/Models/Identity/ExternalLogin.cs index 6e4abf2906..a5de9da0cb 100644 --- a/src/Umbraco.Core/Models/Identity/ExternalLogin.cs +++ b/src/Umbraco.Core/Models/Identity/ExternalLogin.cs @@ -1,10 +1,13 @@ -using System; +using System; namespace Umbraco.Core.Models.Identity { /// public class ExternalLogin : IExternalLogin { + /// + /// Initializes a new instance of the class. + /// public ExternalLogin(string loginProvider, string providerKey, string userData = null) { LoginProvider = loginProvider ?? throw new ArgumentNullException(nameof(loginProvider)); diff --git a/src/Umbraco.Core/Models/Identity/IExternalLogin.cs b/src/Umbraco.Core/Models/Identity/IExternalLogin.cs index 68f66a5cee..2718802324 100644 --- a/src/Umbraco.Core/Models/Identity/IExternalLogin.cs +++ b/src/Umbraco.Core/Models/Identity/IExternalLogin.cs @@ -1,12 +1,23 @@ -namespace Umbraco.Core.Models.Identity +namespace Umbraco.Core.Models.Identity { /// /// Used to persist external login data for a user /// public interface IExternalLogin { + /// + /// Gets the login provider + /// string LoginProvider { get; } + + /// + /// Gets the provider key + /// string ProviderKey { get; } + + /// + /// Gets the user data + /// string UserData { get; } } } diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs b/src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs deleted file mode 100644 index 2524463284..0000000000 --- a/src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Umbraco.Core.Models.Identity -{ - /// - /// EntityType that represents one specific user claim - /// - public class IdentityUserClaim - { - /// - /// Gets or sets primary key - /// - public virtual string Id { get; set; } // TODO: Not used - - /// - /// Gets or sets user Id for the user who owns this login - /// - public virtual string UserId { get; set; } - - /// - /// Gets or sets claim type - /// - public virtual string ClaimType { get; set; } - - /// - /// Gets or sets claim value - /// - public virtual string ClaimValue { get; set; } - } -} diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs index 1ae19da128..5974822c20 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs @@ -9,6 +9,9 @@ namespace Umbraco.Core.Models.Identity /// public class IdentityUserLogin : EntityBase, IIdentityUserLogin { + /// + /// Initializes a new instance of the class. + /// public IdentityUserLogin(string loginProvider, string providerKey, string userId) { LoginProvider = loginProvider; @@ -16,6 +19,9 @@ namespace Umbraco.Core.Models.Identity UserId = userId; } + /// + /// Initializes a new instance of the class. + /// public IdentityUserLogin(int id, string loginProvider, string providerKey, string userId, DateTime createDate) { Id = id; diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs b/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs deleted file mode 100644 index 8a0b6b891d..0000000000 --- a/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Umbraco.Core.Models.Identity -{ - /// - /// EntityType that represents a user belonging to a role - /// - /// - public class IdentityUserRole - { - /// - /// Gets or sets userId for the user that is in the role - /// - public virtual int UserId { get; set; } - - /// - /// Gets or sets roleId for the role - /// - public virtual string RoleId { get; set; } - } -} diff --git a/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs b/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs index 454d651944..b9884c8e7d 100644 --- a/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs +++ b/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs @@ -27,12 +27,12 @@ namespace Umbraco.Core.Security /// /// The user affected by the event raised /// - public int AffectedUser { get; private set; } + public string AffectedUser { get; private set; } /// /// If a user is performing an action on a different user, then this will be set. Otherwise it will be -1 /// - public int PerformingUser { get; private set; } + public string PerformingUser { get; private set; } /// /// An optional comment about the action being logged @@ -53,7 +53,7 @@ namespace Umbraco.Core.Security /// /// /// - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, int performingUser, string comment, int affectedUser, string affectedUsername) + public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string performingUser, string comment, string affectedUser, string affectedUsername) { DateTimeUtc = DateTime.UtcNow; Action = action; @@ -64,8 +64,8 @@ namespace Umbraco.Core.Security AffectedUser = affectedUser; } - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, int performingUser, string comment, string affectedUsername) - : this(action, ipAddress, performingUser, comment, -1, affectedUsername) + public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string performingUser, string comment, string affectedUsername) + : this(action, ipAddress, performingUser, comment, Constants.Security.SuperUserIdAsString, affectedUsername) { } diff --git a/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs index 3430814f83..5fd9f23c92 100644 --- a/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs +++ b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs @@ -53,7 +53,7 @@ namespace Umbraco.Core.Security /// /// /// - public UmbracoBackOfficeIdentity(int userId, string username, string realName, + public UmbracoBackOfficeIdentity(string userId, string username, string realName, IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, string securityStamp, IEnumerable allowedApps, IEnumerable roles) : base(Enumerable.Empty(), Constants.Security.BackOfficeAuthenticationType) //this ctor is used to ensure the IsAuthenticated property is true @@ -87,7 +87,7 @@ namespace Umbraco.Core.Security /// /// public UmbracoBackOfficeIdentity(ClaimsIdentity childIdentity, - int userId, string username, string realName, + string userId, string username, string realName, IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, string securityStamp, IEnumerable allowedApps, IEnumerable roles) : base(childIdentity.Claims, Constants.Security.BackOfficeAuthenticationType) @@ -126,13 +126,13 @@ namespace Umbraco.Core.Security /// /// Adds claims based on the ctor data /// - private void AddRequiredClaims(int userId, string username, string realName, + private void AddRequiredClaims(string userId, string username, string realName, IEnumerable startContentNodes, IEnumerable startMediaNodes, string culture, string securityStamp, IEnumerable allowedApps, IEnumerable roles) { //This is the id that 'identity' uses to check for the user id if (HasClaim(x => x.Type == ClaimTypes.NameIdentifier) == false) - AddClaim(new Claim(ClaimTypes.NameIdentifier, userId.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this)); + AddClaim(new Claim(ClaimTypes.NameIdentifier, userId, ClaimValueTypes.String, Issuer, Issuer, this)); if (HasClaim(x => x.Type == ClaimTypes.Name) == false) AddClaim(new Claim(ClaimTypes.Name, username, ClaimValueTypes.String, Issuer, Issuer, this)); diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs index ed45f24a96..90701a888e 100644 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs +++ b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.Builder; using Umbraco.Core.Mapping; using Umbraco.Core.Security; @@ -19,7 +19,6 @@ namespace Umbraco.Core.Composing.CompositionExtensions builder.Services.AddUnique(); builder.WithCollectionBuilder() - .Add() .Add() .Add() .Add() diff --git a/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs similarity index 85% rename from src/Umbraco.Core/Security/BackOfficeIdentityUser.cs rename to src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs index 4de1ae4d0f..e2e8031768 100644 --- a/src/Umbraco.Core/Security/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeIdentityUser.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Identity; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Identity; @@ -16,13 +17,13 @@ namespace Umbraco.Core.Security private string _name; private string _passwordConfig; private string _culture; - private IReadOnlyUserGroup[] _groups; + private IReadOnlyCollection _groups; private string[] _allowedSections; private int[] _startMediaIds; private int[] _startContentIds; // Custom comparer for enumerables - private static readonly DelegateEqualityComparer s_groupsComparer = new DelegateEqualityComparer( + private static readonly DelegateEqualityComparer> s_groupsComparer = new DelegateEqualityComparer>( (groups, enumerable) => groups.Select(x => x.Alias).UnsortedSequenceEqual(enumerable.Select(x => x.Alias)), groups => groups.GetHashCode()); @@ -51,9 +52,7 @@ namespace Umbraco.Core.Security user.UserName = username; user.Email = email; - // we are setting minvalue here because the default is "0" which is the id of the admin user - // which we cannot allow because the admin user will always exist - user.Id = int.MinValue; + user.Id = null; user.HasIdentity = false; user._culture = culture; user._name = name; @@ -61,7 +60,7 @@ namespace Umbraco.Core.Security return user; } - private BackOfficeIdentityUser(GlobalSettings globalSettings, IReadOnlyUserGroup[] groups) + private BackOfficeIdentityUser(GlobalSettings globalSettings, IReadOnlyCollection groups) { _startMediaIds = Array.Empty(); _startContentIds = Array.Empty(); @@ -79,7 +78,7 @@ namespace Umbraco.Core.Security : this(globalSettings, groups.ToArray()) { // use the property setters - they do more than just setting a field - Id = userId; + Id = UserIdToString(userId); } public int[] CalculatedMediaStartNodeIds { get; set; } @@ -141,13 +140,19 @@ namespace Umbraco.Core.Security /// public string[] AllowedSections => _allowedSections ?? (_allowedSections = _groups.SelectMany(x => x.AllowedSections).Distinct().ToArray()); + /// + /// Gets or sets the culture + /// public string Culture { get => _culture; set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _culture, nameof(Culture)); } - public IReadOnlyUserGroup[] Groups + /// + /// Gets or sets the user groups + /// + public IReadOnlyCollection Groups { get => _groups; set @@ -155,13 +160,13 @@ namespace Umbraco.Core.Security // so they recalculate _allowedSections = null; - _groups = value; + _groups = value.Where(x => x.Alias != null).ToArray(); - var roles = new List(); - foreach (IdentityUserRole identityUserRole in _groups.Select(x => new IdentityUserRole + var roles = new List>(); + foreach (IdentityUserRole identityUserRole in _groups.Select(x => new IdentityUserRole { RoleId = x.Alias, - UserId = Id + UserId = Id?.ToString() })) { roles.Add(identityUserRole); @@ -174,7 +179,6 @@ namespace Umbraco.Core.Security } } - /// /// Gets a value indicating whether the user is locked out based on the user's lockout end date /// @@ -182,7 +186,7 @@ namespace Umbraco.Core.Security { get { - var isLocked = LockoutEndDateUtc.HasValue && LockoutEndDateUtc.Value.ToLocalTime() >= DateTime.Now; + var isLocked = LockoutEnd.HasValue && LockoutEnd.Value.ToLocalTime() >= DateTime.Now; return isLocked; } } @@ -192,5 +196,6 @@ namespace Umbraco.Core.Security /// public bool IsApproved { get; set; } + private static string UserIdToString(int userId) => string.Intern(userId.ToString()); } } diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs index 4b4383c402..befa5ebac2 100644 --- a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Data; using System.Linq; +using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; @@ -12,28 +14,16 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Scoping; -using Umbraco.Core.Security; using Umbraco.Core.Services; namespace Umbraco.Core.Security { // TODO: Make this into a base class that can be re-used - public class BackOfficeUserStore : DisposableObjectSlim, - IUserPasswordStore, - IUserEmailStore, - IUserLoginStore, - IUserRoleStore, - IUserSecurityStampStore, - IUserLockoutStore, - IUserSessionStore - - // TODO: This would require additional columns/tables and then a lot of extra coding support to make this happen natively within umbraco - // IUserTwoFactorStore, - // TODO: This would require additional columns/tables for now people will need to implement this on their own - // IUserPhoneNumberStore, - // TODO: To do this we need to implement IQueryable - we'll have an IQuerable implementation soon with the UmbracoLinqPadDriver implementation - // IQueryableUserStore + /// + /// The user store for back office users + /// + public class BackOfficeUserStore : UserStoreBase, string, IdentityUserClaim, IdentityUserRole, IdentityUserLogin, IdentityUserToken, IdentityRoleClaim> { private readonly IScopeProvider _scopeProvider; private readonly IUserService _userService; @@ -41,12 +31,19 @@ namespace Umbraco.Core.Security private readonly IExternalLoginService _externalLoginService; private readonly GlobalSettings _globalSettings; private readonly UmbracoMapper _mapper; - private bool _disposed = false; /// /// Initializes a new instance of the class. /// - public BackOfficeUserStore(IScopeProvider scopeProvider, IUserService userService, IEntityService entityService, IExternalLoginService externalLoginService, IOptions globalSettings, UmbracoMapper mapper) + public BackOfficeUserStore( + IScopeProvider scopeProvider, + IUserService userService, + IEntityService entityService, + IExternalLoginService externalLoginService, + IOptions globalSettings, + UmbracoMapper mapper, + IdentityErrorDescriber describer) + : base(describer) { _scopeProvider = scopeProvider; _userService = userService ?? throw new ArgumentNullException(nameof(userService)); @@ -59,61 +56,32 @@ namespace Umbraco.Core.Security } /// - /// Handles the disposal of resources. Derived from abstract class which handles common required locking logic. + /// Not supported in Umbraco /// - protected override void DisposeResources() => _disposed = true; + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override IQueryable Users => throw new NotImplementedException(); - public Task GetUserIdAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) + /// + public override Task GetNormalizedUserNameAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) => GetUserNameAsync(user, cancellationToken); + + /// + public override Task SetNormalizedUserNameAsync(BackOfficeIdentityUser user, string normalizedName, CancellationToken cancellationToken) => SetUserNameAsync(user, normalizedName, cancellationToken); + + /// + public override Task CreateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } - return Task.FromResult(user.Id.ToString()); - } - - public Task GetUserNameAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - return Task.FromResult(user.UserName); - } - - public Task SetUserNameAsync(BackOfficeIdentityUser user, string userName, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - user.UserName = userName; - return Task.CompletedTask; - } - - public Task GetNormalizedUserNameAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) - { - return GetUserNameAsync(user, cancellationToken); - } - - public Task SetNormalizedUserNameAsync(BackOfficeIdentityUser user, string normalizedName, CancellationToken cancellationToken) - { - return SetUserNameAsync(user, normalizedName, cancellationToken); - } - - /// - /// Insert a new user - /// - public Task CreateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - //the password must be 'something' it could be empty if authenticating + // the password must be 'something' it could be empty if authenticating // with an external provider so we'll just generate one and prefix it, the // prefix will help us determine if the password hasn't actually been specified yet. - //this will hash the guid with a salt so should be nicely random + // this will hash the guid with a salt so should be nicely random var aspHasher = new PasswordHasher(); var emptyPasswordValue = Constants.Security.EmptyPasswordPrefix + aspHasher.HashPassword(user, Guid.NewGuid().ToString("N")); @@ -133,15 +101,18 @@ namespace Umbraco.Core.Security _userService.Save(userEntity); - if (!userEntity.HasIdentity) throw new DataException("Could not create the user, check logs for details"); + if (!userEntity.HasIdentity) + { + throw new DataException("Could not create the user, check logs for details"); + } - //re-assign id - user.Id = userEntity.Id; + // re-assign id + user.Id = UserIdToString(userEntity.Id); if (isLoginsPropertyDirty) { _externalLoginService.Save( - user.Id, + userEntity.Id, user.Logins.Select(x => new ExternalLogin( x.LoginProvider, x.ProviderKey, @@ -151,24 +122,25 @@ namespace Umbraco.Core.Security return Task.FromResult(IdentityResult.Success); } - /// - /// Update a user - /// - public Task UpdateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) + /// + public override Task UpdateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } - var asInt = user.Id.TryConvertTo(); + Attempt asInt = user.Id.TryConvertTo(); if (asInt == false) { throw new InvalidOperationException("The user id must be an integer to work with the Umbraco"); } - using (var scope = _scopeProvider.CreateScope()) + using (IScope scope = _scopeProvider.CreateScope()) { - var found = _userService.GetUserById(asInt.Result); + IUser found = _userService.GetUserById(asInt.Result); if (found != null) { // we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it. @@ -196,263 +168,233 @@ namespace Umbraco.Core.Security return Task.FromResult(IdentityResult.Success); } - /// - /// Delete a user - /// - public Task DeleteAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) + /// + public override Task DeleteAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } - var found = _userService.GetUserById(user.Id); + IUser found = _userService.GetUserById(UserIdToInt(user.Id)); if (found != null) { _userService.Delete(found); } - _externalLoginService.DeleteUserLogins(user.Id); + + _externalLoginService.DeleteUserLogins(UserIdToInt(user.Id)); return Task.FromResult(IdentityResult.Success); } - /// - /// Finds a user - /// - public async Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) + /// + public override Task FindByIdAsync(string userId, CancellationToken cancellationToken = default) => FindUserAsync(userId, cancellationToken); + + /// + protected override Task FindUserAsync(string userId, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - var user = _userService.GetUserById(UserIdToInt(userId)); - if (user == null) return null; - - return await Task.FromResult(AssignLoginsCallback(_mapper.Map(user))); - } - - /// - /// Find a user by name - /// - public async Task FindByNameAsync(string userName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - var user = _userService.GetByUsername(userName); + IUser user = _userService.GetUserById(UserIdToInt(userId)); if (user == null) { return null; } - var result = AssignLoginsCallback(_mapper.Map(user)); + return Task.FromResult(AssignLoginsCallback(_mapper.Map(user))); + } + + /// + public override async Task FindByNameAsync(string userName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + IUser user = _userService.GetByUsername(userName); + if (user == null) + { + return null; + } + + BackOfficeIdentityUser result = AssignLoginsCallback(_mapper.Map(user)); return await Task.FromResult(result); } - /// - /// Set the user password hash - /// - public Task SetPasswordHashAsync(BackOfficeIdentityUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken)) + /// + public override async Task SetPasswordHashAsync(BackOfficeIdentityUser user, string passwordHash, CancellationToken cancellationToken = default) { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - if (passwordHash == null) throw new ArgumentNullException(nameof(passwordHash)); - if (string.IsNullOrEmpty(passwordHash)) throw new ArgumentException("Value can't be empty.", nameof(passwordHash)); + await base.SetPasswordHashAsync(user, passwordHash, cancellationToken); - user.PasswordHash = passwordHash; user.PasswordConfig = null; // Clear this so that it's reset at the repository level user.LastPasswordChangeDateUtc = DateTime.UtcNow; - - return Task.CompletedTask; } - /// - /// Get the user password hash - /// - public Task GetPasswordHashAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) + /// + public override async Task HasPasswordAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) + { + // This checks if it's null + var result = await base.HasPasswordAsync(user, cancellationToken); + if (result) + { + // we also want to check empty + return string.IsNullOrEmpty(user.PasswordHash) == false; + } + + return result; + } + + /// + public override Task FindByEmailAsync(string email, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - return Task.FromResult(user.PasswordHash); - } - - /// - /// Returns true if a user has a password set - /// - public Task HasPasswordAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - return Task.FromResult(string.IsNullOrEmpty(user.PasswordHash) == false); - } - - /// - /// Set the user email - /// - public Task SetEmailAsync(BackOfficeIdentityUser user, string email, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - if (email.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(email)); - - user.Email = email; - - return Task.CompletedTask; - } - - /// - /// Get the user email - /// - public Task GetEmailAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - return Task.FromResult(user.Email); - } - - /// - /// Returns true if the user email is confirmed - /// - public Task GetEmailConfirmedAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - return Task.FromResult(user.EmailConfirmed); - } - - /// - /// Sets whether the user email is confirmed - /// - public Task SetEmailConfirmedAsync(BackOfficeIdentityUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - user.EmailConfirmed = confirmed; - return Task.CompletedTask; - } - - /// - /// Returns the user associated with this email - /// - public Task FindByEmailAsync(string email, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - var user = _userService.GetByEmail(email); - var result = user == null + IUser user = _userService.GetByEmail(email); + BackOfficeIdentityUser result = user == null ? null : _mapper.Map(user); return Task.FromResult(AssignLoginsCallback(result)); } - public Task GetNormalizedEmailAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) + /// + public override Task GetNormalizedEmailAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) => GetEmailAsync(user, cancellationToken); - public Task SetNormalizedEmailAsync(BackOfficeIdentityUser user, string normalizedEmail, CancellationToken cancellationToken) + /// + public override Task SetNormalizedEmailAsync(BackOfficeIdentityUser user, string normalizedEmail, CancellationToken cancellationToken) => SetEmailAsync(user, normalizedEmail, cancellationToken); - /// - /// Adds a user login with the specified provider and key - /// - public Task AddLoginAsync(BackOfficeIdentityUser user, UserLoginInfo login, CancellationToken cancellationToken = default(CancellationToken)) + /// + public override Task AddLoginAsync(BackOfficeIdentityUser user, UserLoginInfo login, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - if (login == null) throw new ArgumentNullException(nameof(login)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } - var logins = user.Logins; + if (login == null) + { + throw new ArgumentNullException(nameof(login)); + } + + ICollection logins = user.Logins; var instance = new IdentityUserLogin(login.LoginProvider, login.ProviderKey, user.Id.ToString()); - var userLogin = instance; + IdentityUserLogin userLogin = instance; logins.Add(userLogin); return Task.CompletedTask; } - /// - /// Removes the user login with the specified combination if it exists - /// - public Task RemoveLoginAsync(BackOfficeIdentityUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) + /// + public override Task RemoveLoginAsync(BackOfficeIdentityUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } - var userLogin = user.Logins.SingleOrDefault(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey); - if (userLogin != null) user.Logins.Remove(userLogin); + IIdentityUserLogin userLogin = user.Logins.SingleOrDefault(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey); + if (userLogin != null) + { + user.Logins.Remove(userLogin); + } return Task.CompletedTask; } - /// - /// Returns the linked accounts for this user - /// - public Task> GetLoginsAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) + /// + public override Task> GetLoginsAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - return Task.FromResult((IList) - user.Logins.Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.LoginProvider)).ToList()); - } - - /// - /// Returns the user associated with this login - /// - public Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - - // get all logins associated with the login id - IIdentityUserLogin[] result = _externalLoginService.Find(loginProvider, providerKey).ToArray(); - if (result.Any()) + if (user == null) { - // return the first user that matches the result - BackOfficeIdentityUser output = null; - foreach (IIdentityUserLogin l in result) - { - // TODO: This won't be necessary once we add GUID support for users and make the external login - // table uses GUIDs without referential integrity - if (int.TryParse(l.UserId, out int userId)) - { - IUser user = _userService.GetUserById(userId); - if (user != null) - { - output = _mapper.Map(user); - break; - } - } - } - - return Task.FromResult(AssignLoginsCallback(output)); + throw new ArgumentNullException(nameof(user)); } - return Task.FromResult(null); + return Task.FromResult((IList)user.Logins.Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.LoginProvider)).ToList()); } + /// + protected override async Task> FindUserLoginAsync(string userId, string loginProvider, string providerKey, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + BackOfficeIdentityUser user = await FindUserAsync(userId, cancellationToken); + if (user == null) + { + return null; + } + + IList logins = await GetLoginsAsync(user, cancellationToken); + UserLoginInfo found = logins.FirstOrDefault(x => x.ProviderKey == providerKey && x.LoginProvider == loginProvider); + if (found == null) + { + return null; + } + + return new IdentityUserLogin + { + LoginProvider = found.LoginProvider, + ProviderKey = found.ProviderKey, + ProviderDisplayName = found.ProviderDisplayName, // TODO: We don't store this value so it will be null + UserId = user.Id + }; + } + + /// + protected override Task> FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + var logins = _externalLoginService.Find(loginProvider, providerKey).ToList(); + if (logins.Count == 0) + { + return Task.FromResult((IdentityUserLogin)null); + } + + IIdentityUserLogin found = logins[0]; + return Task.FromResult(new IdentityUserLogin + { + LoginProvider = found.LoginProvider, + ProviderKey = found.ProviderKey, + ProviderDisplayName = null, // TODO: We don't store this value so it will be null + UserId = found.UserId + }); + } /// /// Adds a user to a role (user group) /// - public Task AddToRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) + public override Task AddToRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - if (normalizedRoleName == null) throw new ArgumentNullException(nameof(normalizedRoleName)); - if (string.IsNullOrWhiteSpace(normalizedRoleName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(normalizedRoleName)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } - var userRole = user.Roles.SingleOrDefault(r => r.RoleId == normalizedRoleName); + if (normalizedRoleName == null) + { + throw new ArgumentNullException(nameof(normalizedRoleName)); + } + + if (string.IsNullOrWhiteSpace(normalizedRoleName)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(normalizedRoleName)); + } + + IdentityUserRole userRole = user.Roles.SingleOrDefault(r => r.RoleId == normalizedRoleName); if (userRole == null) { @@ -465,15 +407,26 @@ namespace Umbraco.Core.Security /// /// Removes the role (user group) for the user /// - public Task RemoveFromRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) + public override Task RemoveFromRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - if (user == null) throw new ArgumentNullException(nameof(user)); - if (normalizedRoleName == null) throw new ArgumentNullException(nameof(normalizedRoleName)); - if (string.IsNullOrWhiteSpace(normalizedRoleName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(normalizedRoleName)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } - var userRole = user.Roles.SingleOrDefault(r => r.RoleId == normalizedRoleName); + if (normalizedRoleName == null) + { + throw new ArgumentNullException(nameof(normalizedRoleName)); + } + + if (string.IsNullOrWhiteSpace(normalizedRoleName)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(normalizedRoleName)); + } + + IdentityUserRole userRole = user.Roles.SingleOrDefault(r => r.RoleId == normalizedRoleName); if (userRole != null) { @@ -486,22 +439,30 @@ namespace Umbraco.Core.Security /// /// Returns the roles (user groups) for this user /// - public Task> GetRolesAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) + public override Task> GetRolesAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult((IList)user.Roles.Select(x => x.RoleId).ToList()); } /// /// Returns true if a user is in the role /// - public Task IsInRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) + public override Task IsInRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult(user.Roles.Select(x => x.RoleId).InvariantContains(normalizedRoleName)); } @@ -511,43 +472,62 @@ namespace Umbraco.Core.Security /// /// Identity Role names are equal to Umbraco UserGroup alias. /// - public Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) + public override Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (normalizedRoleName == null) throw new ArgumentNullException(nameof(normalizedRoleName)); + if (normalizedRoleName == null) + { + throw new ArgumentNullException(nameof(normalizedRoleName)); + } - var userGroup = _userService.GetUserGroupByAlias(normalizedRoleName); + IUserGroup userGroup = _userService.GetUserGroupByAlias(normalizedRoleName); - var users = _userService.GetAllInGroup(userGroup.Id); + IEnumerable users = _userService.GetAllInGroup(userGroup.Id); IList backOfficeIdentityUsers = users.Select(x => _mapper.Map(x)).ToList(); return Task.FromResult(backOfficeIdentityUsers); } - /// - /// Set the security stamp for the user - /// - public Task SetSecurityStampAsync(BackOfficeIdentityUser user, string stamp, CancellationToken cancellationToken = default(CancellationToken)) + /// + protected override Task> FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + IUserGroup group = _userService.GetUserGroupByAlias(normalizedRoleName); + if (group == null) + { + return Task.FromResult((IdentityRole)null); + } - user.SecurityStamp = stamp; - return Task.CompletedTask; + return Task.FromResult(new IdentityRole(group.Name) + { + Id = group.Alias + }); } - /// - /// Get the user security stamp - /// - public Task GetSecurityStampAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) + /// + protected override async Task> FindUserRoleAsync(string userId, string roleId, CancellationToken cancellationToken) + { + BackOfficeIdentityUser user = await FindUserAsync(userId, cancellationToken); + if (user == null) + { + return null; + } + + IdentityUserRole found = user.Roles.FirstOrDefault(x => x.RoleId.InvariantEquals(roleId)); + return found; + } + + /// + public override Task GetSecurityStampAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } - //the stamp cannot be null, so if it is currently null then we'll just return a hash of the password + // the stamp cannot be null, so if it is currently null then we'll just return a hash of the password return Task.FromResult(user.SecurityStamp.IsNullOrWhiteSpace() ? user.PasswordHash.GenerateHash() : user.SecurityStamp); @@ -557,157 +537,65 @@ namespace Umbraco.Core.Security { if (user != null) { - user.SetLoginsCallback(new Lazy>(() => - _externalLoginService.GetAll(user.Id))); + user.SetLoginsCallback(new Lazy>(() => _externalLoginService.GetAll(UserIdToInt(user.Id)))); } + return user; } - #region IUserLockoutStore - - /// - /// Returns the DateTimeOffset that represents the end of a user's lockout, any time in the past should be considered not locked out. - /// - /// - /// Currently we do not support a timed lock out, when they are locked out, an admin will have to reset the status - /// - public Task GetLockoutEndDateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - return user.LockoutEndDateUtc.HasValue - ? Task.FromResult(DateTimeOffset.MaxValue) - : Task.FromResult(DateTimeOffset.MinValue); - } - - /// - /// Locks a user out until the specified end date (set to a past date, to unlock a user) - /// - /// - /// Currently we do not support a timed lock out, when they are locked out, an admin will have to reset the status - /// - public Task SetLockoutEndDateAsync(BackOfficeIdentityUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - user.LockoutEndDateUtc = lockoutEnd.Value.UtcDateTime; - return Task.CompletedTask; - } - - /// - /// Used to record when an attempt to access the user has failed - /// - public Task IncrementAccessFailedCountAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - user.AccessFailedCount++; - return Task.FromResult(user.AccessFailedCount); - } - - /// - /// Used to reset the access failed count, typically after the account is successfully accessed - /// - public Task ResetAccessFailedCountAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - user.AccessFailedCount = 0; - return Task.CompletedTask; - } - - /// - /// Returns the current number of failed access attempts. This number usually will be reset whenever the password is - /// verified or the account is locked out. - /// - public Task GetAccessFailedCountAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - return Task.FromResult(user.AccessFailedCount); - } - - /// - /// Returns true - /// - public Task GetLockoutEnabledAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - return Task.FromResult(user.LockoutEnabled); - } - - /// - /// Doesn't actually perform any function, users can always be locked out - /// - public Task SetLockoutEnabledAsync(BackOfficeIdentityUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - - user.LockoutEnabled = enabled; - return Task.CompletedTask; - } - #endregion - private bool UpdateMemberProperties(IUser user, BackOfficeIdentityUser identityUser) { var anythingChanged = false; - //don't assign anything if nothing has changed as this will trigger the track changes of the model - + // don't assign anything if nothing has changed as this will trigger the track changes of the model if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.LastLoginDateUtc)) - || (user.LastLoginDate != default(DateTime) && identityUser.LastLoginDateUtc.HasValue == false) - || identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value) + || (user.LastLoginDate != default && identityUser.LastLoginDateUtc.HasValue == false) + || (identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value)) { anythingChanged = true; - //if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime - var dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime(); + + // if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime + DateTime dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime(); user.LastLoginDate = dt; } + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.LastPasswordChangeDateUtc)) - || (user.LastPasswordChangeDate != default(DateTime) && identityUser.LastPasswordChangeDateUtc.HasValue == false) - || identityUser.LastPasswordChangeDateUtc.HasValue && user.LastPasswordChangeDate.ToUniversalTime() != identityUser.LastPasswordChangeDateUtc.Value) + || (user.LastPasswordChangeDate != default && identityUser.LastPasswordChangeDateUtc.HasValue == false) + || (identityUser.LastPasswordChangeDateUtc.HasValue && user.LastPasswordChangeDate.ToUniversalTime() != identityUser.LastPasswordChangeDateUtc.Value)) { anythingChanged = true; user.LastPasswordChangeDate = identityUser.LastPasswordChangeDateUtc.Value.ToLocalTime(); } + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.EmailConfirmed)) - || (user.EmailConfirmedDate.HasValue && user.EmailConfirmedDate.Value != default(DateTime) && identityUser.EmailConfirmed == false) - || ((user.EmailConfirmedDate.HasValue == false || user.EmailConfirmedDate.Value == default(DateTime)) && identityUser.EmailConfirmed)) + || (user.EmailConfirmedDate.HasValue && user.EmailConfirmedDate.Value != default && identityUser.EmailConfirmed == false) + || ((user.EmailConfirmedDate.HasValue == false || user.EmailConfirmedDate.Value == default) && identityUser.EmailConfirmed)) { anythingChanged = true; user.EmailConfirmedDate = identityUser.EmailConfirmed ? (DateTime?)DateTime.Now : null; } + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Name)) && user.Name != identityUser.Name && identityUser.Name.IsNullOrWhiteSpace() == false) { anythingChanged = true; user.Name = identityUser.Name; } + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Email)) && user.Email != identityUser.Email && identityUser.Email.IsNullOrWhiteSpace() == false) { anythingChanged = true; user.Email = identityUser.Email; } + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.AccessFailedCount)) && user.FailedPasswordAttempts != identityUser.AccessFailedCount) { anythingChanged = true; user.FailedPasswordAttempts = identityUser.AccessFailedCount; } + if (user.IsLockedOut != identityUser.IsLockedOut) { anythingChanged = true; @@ -715,17 +603,18 @@ namespace Umbraco.Core.Security if (user.IsLockedOut) { - //need to set the last lockout date + // need to set the last lockout date user.LastLockoutDate = DateTime.Now; } - } + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.UserName)) && user.Username != identityUser.UserName && identityUser.UserName.IsNullOrWhiteSpace() == false) { anythingChanged = true; user.Username = identityUser.UserName; } + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.PasswordHash)) && user.RawPasswordValue != identityUser.PasswordHash && identityUser.PasswordHash.IsNullOrWhiteSpace() == false) { @@ -740,18 +629,21 @@ namespace Umbraco.Core.Security anythingChanged = true; user.Language = identityUser.Culture; } + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.StartMediaIds)) && user.StartMediaIds.UnsortedSequenceEqual(identityUser.StartMediaIds) == false) { anythingChanged = true; user.StartMediaIds = identityUser.StartMediaIds; } + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.StartContentIds)) && user.StartContentIds.UnsortedSequenceEqual(identityUser.StartContentIds) == false) { anythingChanged = true; user.StartContentIds = identityUser.StartContentIds; } + if (user.SecurityStamp != identityUser.SecurityStamp) { anythingChanged = true; @@ -800,11 +692,7 @@ namespace Umbraco.Core.Security return anythingChanged; } - private void ThrowIfDisposed() - { - if (_disposed) throw new ObjectDisposedException(GetType().Name); - } - + /// public Task ValidateSessionIdAsync(string userId, string sessionId) { if (Guid.TryParse(sessionId, out Guid guidSessionId)) @@ -817,10 +705,73 @@ namespace Umbraco.Core.Security private static int UserIdToInt(string userId) { - var attempt = userId.TryConvertTo(); - if (attempt.Success) return attempt.Result; + Attempt attempt = userId.TryConvertTo(); + if (attempt.Success) + { + return attempt.Result; + } throw new InvalidOperationException("Unable to convert user ID to int", attempt.Exception); } + + private static string UserIdToString(int userId) => string.Intern(userId.ToString()); + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override Task> GetClaimsAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override Task AddClaimsAsync(BackOfficeIdentityUser user, IEnumerable claims, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override Task ReplaceClaimAsync(BackOfficeIdentityUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override Task RemoveClaimsAsync(BackOfficeIdentityUser user, IEnumerable claims, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + // TODO: We should support these + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + protected override Task> FindTokenAsync(BackOfficeIdentityUser user, string loginProvider, string name, CancellationToken cancellationToken) => throw new NotImplementedException(); + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + protected override Task AddUserTokenAsync(IdentityUserToken token) => throw new NotImplementedException(); + + /// + /// Not supported in Umbraco + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + protected override Task RemoveUserTokenAsync(IdentityUserToken token) => throw new NotImplementedException(); } } diff --git a/src/Umbraco.Core/Security/IBackOfficeUserPasswordChecker.cs b/src/Umbraco.Infrastructure/Security/IBackOfficeUserPasswordChecker.cs similarity index 100% rename from src/Umbraco.Core/Security/IBackOfficeUserPasswordChecker.cs rename to src/Umbraco.Infrastructure/Security/IBackOfficeUserPasswordChecker.cs diff --git a/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs b/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs index c50b012dae..4bec4c9c7a 100644 --- a/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs +++ b/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs @@ -42,22 +42,18 @@ namespace Umbraco.Core.Security /// /// Gets the external logins for the user /// - /// /// A representing the result of the asynchronous operation. Task> GetLoginsAsync(TUser user); /// /// Deletes a user /// - /// /// A representing the result of the asynchronous operation. Task DeleteAsync(TUser user); /// /// Finds a user by the external login provider /// - /// - /// /// A representing the result of the asynchronous operation. Task FindByLoginAsync(string loginProvider, string providerKey); @@ -82,15 +78,11 @@ namespace Umbraco.Core.Security /// /// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event /// - /// - /// - /// - /// /// /// We use this because in the back office the only way an admin can change another user's password without first knowing their password /// is to generate a token and reset it, however, when we do this we want to track a password change, not a password reset /// - Task ChangePasswordWithResetAsync(int userId, string token, string newPassword); + Task ChangePasswordWithResetAsync(string userId, string token, string newPassword); /// /// Validates that an email confirmation token matches the specified . @@ -130,8 +122,6 @@ namespace Umbraco.Core.Security /// /// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date /// - /// - /// /// /// In the ASP.NET Identity world, there is only one value for being locked out, in Umbraco we have 2 so when checking this for Umbraco we need to check both values /// @@ -192,7 +182,6 @@ namespace Umbraco.Core.Security /// Task AddPasswordAsync(TUser user, string password); - /// /// Returns a flag indicating whether the given is valid for the /// specified . @@ -220,8 +209,6 @@ namespace Umbraco.Core.Security /// /// Used to validate a user's session /// - /// - /// /// Returns true if the session is valid, otherwise false Task ValidateSessionIdAsync(string userId, string sessionId); @@ -323,15 +310,12 @@ namespace Umbraco.Core.Security /// /// Resets the access failed count for the user /// - /// /// A representing the result of the asynchronous operation. Task ResetAccessFailedCountAsync(TUser user); /// /// Generates a two factor token for the user /// - /// - /// /// A representing the result of the asynchronous operation. Task GenerateTwoFactorTokenAsync(TUser user, string tokenProvider); @@ -356,9 +340,9 @@ namespace Umbraco.Core.Security // let's see if there's a way to avoid that and only have these called within signinmanager and usermanager // which means we can remove these from the interface (things like invite seems like they cannot be moved) // TODO: When we change to not having the crappy static events this will need to be revisited - void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, int userId); - void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, int userId); - SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId); + void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, string userId); + void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, string userId); + SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, string userId); UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser); bool HasSendingUserInviteEventHandler { get; } diff --git a/src/Umbraco.Infrastructure/Security/IUserSessionStore.cs b/src/Umbraco.Infrastructure/Security/IUserSessionStore.cs index 06b7c2f165..c68d1f13f9 100644 --- a/src/Umbraco.Infrastructure/Security/IUserSessionStore.cs +++ b/src/Umbraco.Infrastructure/Security/IUserSessionStore.cs @@ -1,15 +1,17 @@ using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; namespace Umbraco.Core.Security { /// /// An IUserStore interface part to implement if the store supports validating user session Ids /// - /// - public interface IUserSessionStore : IUserStore + /// The user type + public interface IUserSessionStore where TUser : class { + /// + /// Validates a user's session is still valid + /// Task ValidateSessionIdAsync(string userId, string sessionId); } } diff --git a/src/Umbraco.Core/Security/IdentityMapDefinition.cs b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs similarity index 96% rename from src/Umbraco.Core/Security/IdentityMapDefinition.cs rename to src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs index 26a5d11f6e..aebb2de5bf 100644 --- a/src/Umbraco.Core/Security/IdentityMapDefinition.cs +++ b/src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs @@ -65,7 +65,7 @@ namespace Umbraco.Core.Security target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); // project CultureInfo to string target.IsApproved = source.IsApproved; target.SecurityStamp = source.SecurityStamp; - target.LockoutEndDateUtc = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null; + target.LockoutEnd = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null; // this was in AutoMapper but does not have a setter anyways //target.AllowedSections = source.AllowedSections.ToArray(), diff --git a/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs b/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs index 2e5997b603..626932640c 100644 --- a/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs +++ b/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Security /// public class SignOutAuditEventArgs : IdentityAuditEventArgs { - public SignOutAuditEventArgs(AuditEvent action, string ipAddress, string comment = null, int performingUser = -1, int affectedUser = -1) + public SignOutAuditEventArgs(AuditEvent action, string ipAddress, string comment = null, string performingUser = Constants.Security.SuperUserIdAsString, string affectedUser = Constants.Security.SuperUserIdAsString) : base(action, ipAddress, performingUser, comment, affectedUser, null) { } diff --git a/src/Umbraco.Core/Models/Identity/UmbracoIdentityUser.cs b/src/Umbraco.Infrastructure/Security/UmbracoIdentityUser.cs similarity index 81% rename from src/Umbraco.Core/Models/Identity/UmbracoIdentityUser.cs rename to src/Umbraco.Infrastructure/Security/UmbracoIdentityUser.cs index ffa549ab47..1b888123be 100644 --- a/src/Umbraco.Core/Models/Identity/UmbracoIdentityUser.cs +++ b/src/Umbraco.Infrastructure/Security/UmbracoIdentityUser.cs @@ -3,23 +3,33 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; +using Microsoft.AspNetCore.Identity; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Identity { + /// /// Abstract class for use in Umbraco Identity for users and members /// /// + /// + /// This uses strings for the ID of the user, claims, roles. This is because aspnetcore identity's base store will + /// not support having an INT user PK and a string role PK with the way they've made the generics. So we will just use + /// string for both which makes things more flexible anyways for users and members and also if/when we transition to + /// GUID support + /// + /// /// This class was originally borrowed from the EF implementation in Identity prior to netcore. /// The new IdentityUser in netcore does not have properties such as Claims, Roles and Logins and those are instead /// by default managed with their default user store backed by EF which utilizes EF's change tracking to track these values /// to a user. We will continue using this approach since it works fine for what we need which does the change tracking of /// claims, roles and logins directly on the user model. + /// /// - public abstract class UmbracoIdentityUser : IRememberBeingDirty + public abstract class UmbracoIdentityUser : IdentityUser, IRememberBeingDirty { - private int _id; + private string _id; private string _email; private string _userName; private DateTime? _lastLoginDateUtc; @@ -29,7 +39,7 @@ namespace Umbraco.Core.Models.Identity private DateTime? _lastPasswordChangeDateUtc; private ObservableCollection _logins; private Lazy> _getLogins; - private ObservableCollection _roles; + private ObservableCollection> _roles; /// /// Initializes a new instance of the class. @@ -37,9 +47,9 @@ namespace Umbraco.Core.Models.Identity public UmbracoIdentityUser() { // must initialize before setting groups - _roles = new ObservableCollection(); + _roles = new ObservableCollection>(); _roles.CollectionChanged += Roles_CollectionChanged; - Claims = new List(); + Claims = new List>(); } public event PropertyChangedEventHandler PropertyChanged @@ -67,7 +77,7 @@ namespace Umbraco.Core.Models.Identity /// /// Gets or sets email /// - public string Email + public override string Email { get => _email; set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _email, nameof(Email)); @@ -76,7 +86,7 @@ namespace Umbraco.Core.Models.Identity /// /// Gets or sets a value indicating whether the email is confirmed, default is false /// - public bool EmailConfirmed + public override bool EmailConfirmed { get => _emailConfirmed; set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _emailConfirmed, nameof(EmailConfirmed)); @@ -85,46 +95,12 @@ namespace Umbraco.Core.Models.Identity /// /// Gets or sets the salted/hashed form of the user password /// - public string PasswordHash + public override string PasswordHash { get => _passwordHash; set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordHash, nameof(PasswordHash)); } - /// - /// Gets or sets a random value that should change whenever a users credentials have changed (password changed, login removed) - /// - public virtual string SecurityStamp { get; set; } - - /// - /// Gets or sets a phone Number for the user - /// - /// - /// This is unused until we or an end-user requires this value for 2FA - /// - public virtual string PhoneNumber { get; set; } - - /// - /// Gets or sets a value indicating whether true if the phone number is confirmed, default is false - /// - /// - /// This is unused until we or an end-user requires this value for 2FA - /// - public virtual bool PhoneNumberConfirmed { get; set; } - - /// - /// Gets or sets a value indicating whether is two factor enabled for the user - /// - /// - /// This is unused until we or an end-user requires this value for 2FA - /// - public virtual bool TwoFactorEnabled { get; set; } - - /// - /// Gets or sets dateTime in UTC when lockout ends, any time in the past is considered not locked out. - /// - public virtual DateTime? LockoutEndDateUtc { get; set; } - /// /// Gets or sets dateTime in UTC when the password was last changed. /// @@ -140,7 +116,7 @@ namespace Umbraco.Core.Models.Identity /// /// Currently this is always true for users and members /// - public bool LockoutEnabled + public override bool LockoutEnabled { get => true; set { } @@ -149,7 +125,7 @@ namespace Umbraco.Core.Models.Identity /// /// Gets or sets the value to record failures for the purposes of lockout /// - public int AccessFailedCount + public override int AccessFailedCount { get => _accessFailedCount; set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _accessFailedCount, nameof(AccessFailedCount)); @@ -158,13 +134,13 @@ namespace Umbraco.Core.Models.Identity /// /// Gets or sets the user roles collection /// - public ICollection Roles + public ICollection> Roles { get => _roles; set { _roles.CollectionChanged -= Roles_CollectionChanged; - _roles = new ObservableCollection(value); + _roles = new ObservableCollection>(value); _roles.CollectionChanged += Roles_CollectionChanged; } } @@ -172,7 +148,7 @@ namespace Umbraco.Core.Models.Identity /// /// Gets navigation the user claims collection /// - public ICollection Claims { get; } + public ICollection> Claims { get; } /// /// Gets the user logins collection @@ -208,7 +184,7 @@ namespace Umbraco.Core.Models.Identity /// /// Gets or sets user ID (Primary Key) /// - public int Id + public override string Id { get => _id; set @@ -226,7 +202,7 @@ namespace Umbraco.Core.Models.Identity /// /// Gets or sets user name /// - public string UserName + public override string UserName { get => _userName; set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _userName, nameof(UserName)); @@ -281,7 +257,7 @@ namespace Umbraco.Core.Models.Identity /// /// Adding a role this way will not reflect on the user's group's collection or it's allowed sections until the user is persisted /// - public void AddRole(string role) => Roles.Add(new IdentityUserRole + public void AddRole(string role) => Roles.Add(new IdentityUserRole { UserId = Id, RoleId = role diff --git a/src/Umbraco.Web.Common/Security/UmbracoUserManager.cs b/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs similarity index 90% rename from src/Umbraco.Web.Common/Security/UmbracoUserManager.cs rename to src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs index 68b9011aa4..2ea0bc52b2 100644 --- a/src/Umbraco.Web.Common/Security/UmbracoUserManager.cs +++ b/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs @@ -6,12 +6,10 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Identity; -using Umbraco.Core.Security; using Umbraco.Net; -namespace Umbraco.Web.Common.Security +namespace Umbraco.Core.Security { /// @@ -21,7 +19,7 @@ namespace Umbraco.Web.Common.Security /// /// The type password config public abstract class UmbracoUserManager : UserManager where TUser : UmbracoIdentityUser - where TPasswordConfig: class, IPasswordConfiguration, new() + where TPasswordConfig : class, IPasswordConfiguration, new() { private PasswordGenerator _passwordGenerator; @@ -87,8 +85,14 @@ namespace Umbraco.Web.Common.Security /// An protected virtual IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) => new PasswordHasher(); + /// + /// Gets the password configuration + /// public IPasswordConfiguration PasswordConfiguration { get; } + /// + /// Gets the IP resolver + /// public IIpResolver IpResolver { get; } /// @@ -130,32 +134,18 @@ namespace Umbraco.Web.Common.Security /// We use this because in the back office the only way an admin can change another user's password without first knowing their password /// is to generate a token and reset it, however, when we do this we want to track a password change, not a password reset /// - public virtual async Task ChangePasswordWithResetAsync(int userId, string token, string newPassword) + public virtual async Task ChangePasswordWithResetAsync(string userId, string token, string newPassword) { - TUser user = await FindByIdAsync(userId.ToString()); + TUser user = await FindByIdAsync(userId); if (user == null) { throw new InvalidOperationException("Could not find user"); } - IdentityResult result = await base.ResetPasswordAsync(user, token, newPassword); + IdentityResult result = await ResetPasswordAsync(user, token, newPassword); return result; } - /// - /// This is copied from the underlying .NET base class since they decided to not expose it - /// - private IUserSecurityStampStore GetSecurityStore() - { - var store = Store as IUserSecurityStampStore; - if (store == null) - { - throw new NotSupportedException("The current user store does not implement " + typeof(IUserSecurityStampStore<>)); - } - - return store; - } - /// public override async Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd) { diff --git a/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs b/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs index 811092a2c9..80b05497a8 100644 --- a/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs +++ b/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs @@ -6,8 +6,8 @@ namespace Umbraco.Core.Security { public class UserInviteEventArgs : IdentityAuditEventArgs { - public UserInviteEventArgs(string ipAddress, int performingUser, UserInvite invitedUser, IUser localUser, string comment = null) - : base(AuditEvent.SendingUserInvite, ipAddress, performingUser, comment, localUser.Id, localUser.Name) + public UserInviteEventArgs(string ipAddress, string performingUser, UserInvite invitedUser, IUser localUser, string comment = null) + : base(AuditEvent.SendingUserInvite, ipAddress, performingUser, comment, string.Intern(localUser.Id.ToString()), localUser.Name) { InvitedUser = invitedUser ?? throw new System.ArgumentNullException(nameof(invitedUser)); User = localUser; diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs index bf198d9819..d9dee389ee 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using Umbraco.Core.Security; using Umbraco.Extensions; using Umbraco.Tests.Integration.Testing; +using Umbraco.Web.Common.Security; namespace Umbraco.Tests.Integration.Umbraco.Web.BackOffice { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs index f85c15b3bf..64bdca6437 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs @@ -98,7 +98,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice const string expectedClaimType = ClaimTypes.Role; const string expectedClaimValue = "b87309fb-4caf-48dc-b45a-2b752d051508"; - _testUser.Roles.Add(new global::Umbraco.Core.Models.Identity.IdentityUserRole{RoleId = expectedClaimValue}); + _testUser.Roles.Add(new IdentityUserRole { RoleId = expectedClaimValue }); _mockUserManager.Setup(x => x.SupportsUserRole).Returns(true); _mockUserManager.Setup(x => x.GetRolesAsync(_testUser)).ReturnsAsync(new[] {expectedClaimValue}); @@ -115,7 +115,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice const string expectedClaimType = "custom"; const string expectedClaimValue = "val"; - _testUser.Claims.Add(new global::Umbraco.Core.Models.Identity.IdentityUserClaim {ClaimType = expectedClaimType, ClaimValue = expectedClaimValue}); + _testUser.Claims.Add(new IdentityUserClaim { ClaimType = expectedClaimType, ClaimValue = expectedClaimValue}); _mockUserManager.Setup(x => x.SupportsUserClaim).Returns(true); _mockUserManager.Setup(x => x.GetClaimsAsync(_testUser)).ReturnsAsync( new List {new Claim(expectedClaimType, expectedClaimValue)}); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs index 8dcaafafcb..79a9456643 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Security.Claims; using NUnit.Framework; @@ -103,7 +103,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice }); var identity = new UmbracoBackOfficeIdentity(claimsIdentity, - 1234, "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", securityStamp, new[] { "content", "media" }, new[] { "admin" }); + "1234", "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", securityStamp, new[] { "content", "media" }, new[] { "admin" }); Assert.AreEqual(12, identity.Claims.Count()); Assert.IsNull(identity.Actor); @@ -116,7 +116,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice var securityStamp = Guid.NewGuid().ToString(); var identity = new UmbracoBackOfficeIdentity( - 1234, "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", securityStamp, new[] { "content", "media" }, new[] { "admin" }); + "1234", "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", securityStamp, new[] { "content", "media" }, new[] { "admin" }); // this will be filtered out during cloning identity.AddClaim(new Claim(Constants.Security.TicketExpiresClaimType, "test")); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs index 30706b1b67..ad0f292fae 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs @@ -1,4 +1,4 @@ -using NUnit.Framework; +using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq; @@ -15,7 +15,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Extensions [Test] public void Get_Remaining_Ticket_Seconds() { - var backOfficeIdentity = new UmbracoBackOfficeIdentity(-1, "test", "test", + var backOfficeIdentity = new UmbracoBackOfficeIdentity(Constants.Security.SuperUserIdAsString, "test", "test", Enumerable.Empty(), Enumerable.Empty(), "en-US", Guid.NewGuid().ToString(), Enumerable.Empty(), Enumerable.Empty()); var principal = new ClaimsPrincipal(backOfficeIdentity); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs index ccebe17b09..7899ef39c2 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs @@ -22,7 +22,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Security var httpContext = new DefaultHttpContext() { User = new ClaimsPrincipal(new UmbracoBackOfficeIdentity( - -1, + Constants.Security.SuperUserIdAsString, "test", "test", Enumerable.Empty(), diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs index 3673bdf333..e702753236 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/AuthenticateEverythingMiddleware.cs @@ -29,9 +29,10 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting { var securityStamp = Guid.NewGuid().ToString(); var identity = new UmbracoBackOfficeIdentity( - -1, "admin", "Admin", new []{-1}, new[] { -1 }, "en-US", securityStamp, new[] { "content", "media", "members" }, new[] { "admin" }); + Umbraco.Core.Constants.Security.SuperUserIdAsString, "admin", "Admin", new[] { -1 }, new[] { -1 }, "en-US", securityStamp, new[] { "content", "media", "members" }, new[] { "admin" }); - return Task.FromResult(new AuthenticationTicket(identity, + return Task.FromResult(new AuthenticationTicket( + identity, new AuthenticationProperties() { ExpiresUtc = DateTime.Now.AddDays(1) diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index f7e10d77af..36e5c2b6fe 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -390,7 +390,7 @@ namespace Umbraco.Web.BackOffice.Controllers await _emailSender.SendAsync(mailMessage); - _userManager.RaiseForgotPasswordRequestedEvent(User, user.Id); + _userManager.RaiseForgotPasswordRequestedEvent(User, user.Id.ToString()); } } @@ -554,7 +554,7 @@ namespace Umbraco.Web.BackOffice.Controllers } } - _userManager.RaiseForgotPasswordChangedSuccessEvent(User, model.UserId); + _userManager.RaiseForgotPasswordChangedSuccessEvent(User, model.UserId.ToString()); return Ok(); } @@ -577,7 +577,7 @@ namespace Umbraco.Web.BackOffice.Controllers _logger.LogInformation("User {UserName} from IP address {RemoteIpAddress} has logged out", User.Identity == null ? "UNKNOWN" : User.Identity.Name, HttpContext.Connection.RemoteIpAddress); - var userId = int.Parse(result.Principal.Identity.GetUserId()); + var userId = result.Principal.Identity.GetUserId(); var args = _userManager.RaiseLogoutSuccessEvent(User, userId); if (!args.SignOutRedirectUrl.IsNullOrWhiteSpace()) { @@ -608,10 +608,12 @@ namespace Umbraco.Web.BackOffice.Controllers return userDetail; } - private string ConstructCallbackUrl(int userId, string code) + private string ConstructCallbackUrl(string userId, string code) { // Get an mvc helper to get the url - var action = _linkGenerator.GetPathByAction(nameof(BackOfficeController.ValidatePasswordResetCode), ControllerExtensions.GetControllerName(), + var action = _linkGenerator.GetPathByAction( + nameof(BackOfficeController.ValidatePasswordResetCode), + ControllerExtensions.GetControllerName(), new { area = Constants.Web.Mvc.BackOfficeArea, diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index 89b121b575..19fb6aa2df 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -433,7 +433,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (result == Microsoft.AspNetCore.Identity.SignInResult.Success) { - } + } else if (result == Microsoft.AspNetCore.Identity.SignInResult.TwoFactorRequired) { diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs index ef6d278554..81be953d22 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs @@ -33,29 +33,27 @@ namespace Umbraco.Web.Common.Security { // NOTE: This was migrated as-is from v8 including these missing entries // TODO: See note about static events in BackOfficeUserManager - //BackOfficeUserManager.AccountLocked += ; - //BackOfficeUserManager.AccountUnlocked += ; BackOfficeUserManager.ForgotPasswordRequested += OnForgotPasswordRequest; BackOfficeUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange; BackOfficeUserManager.LoginFailed += OnLoginFailed; - //BackOfficeUserManager.LoginRequiresVerification += ; BackOfficeUserManager.LoginSuccess += OnLoginSuccess; BackOfficeUserManager.LogoutSuccess += OnLogoutSuccess; BackOfficeUserManager.PasswordChanged += OnPasswordChanged; BackOfficeUserManager.PasswordReset += OnPasswordReset; - //BackOfficeUserManager.ResetAccessFailedCount += ; } - private IUser GetPerformingUser(int userId) + private IUser GetPerformingUser(string userId) { - var found = userId >= 0 ? _userService.GetUserById(userId) : null; + if (!int.TryParse(userId, out int asInt)) + { + return AuditEventsComponent.UnknownUser(_globalSettings); + } + + IUser found = asInt >= 0 ? _userService.GetUserById(asInt) : null; return found ?? AuditEventsComponent.UnknownUser(_globalSettings); } - private static string FormatEmail(IMembershipUser user) - { - return user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; - } + private static string FormatEmail(IMembershipUser user) => user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; private void OnLoginSuccess(object sender, IdentityAuditEventArgs args) { @@ -65,70 +63,78 @@ namespace Umbraco.Web.Common.Security private void OnLogoutSuccess(object sender, IdentityAuditEventArgs args) { - var performingUser = GetPerformingUser(args.PerformingUser); + IUser performingUser = GetPerformingUser(args.PerformingUser); WriteAudit(performingUser, args.AffectedUser, args.IpAddress, "umbraco/user/sign-in/logout", "logout success"); } - private void OnPasswordReset(object sender, IdentityAuditEventArgs args) - { - WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/reset", "password reset"); - } + private void OnPasswordReset(object sender, IdentityAuditEventArgs args) => WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/reset", "password reset"); - private void OnPasswordChanged(object sender, IdentityAuditEventArgs args) - { - WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/change", "password change"); - } + private void OnPasswordChanged(object sender, IdentityAuditEventArgs args) => WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/change", "password change"); - private void OnLoginFailed(object sender, IdentityAuditEventArgs args) - { - WriteAudit(args.PerformingUser, 0, args.IpAddress, "umbraco/user/sign-in/failed", "login failed", affectedDetails: ""); - } + private void OnLoginFailed(object sender, IdentityAuditEventArgs args) => WriteAudit(args.PerformingUser, "0", args.IpAddress, "umbraco/user/sign-in/failed", "login failed", affectedDetails: ""); - private void OnForgotPasswordChange(object sender, IdentityAuditEventArgs args) - { - WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/forgot/change", "password forgot/change"); - } + private void OnForgotPasswordChange(object sender, IdentityAuditEventArgs args) => WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/forgot/change", "password forgot/change"); - private void OnForgotPasswordRequest(object sender, IdentityAuditEventArgs args) - { - WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/forgot/request", "password forgot/request"); - } + private void OnForgotPasswordRequest(object sender, IdentityAuditEventArgs args) => WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/forgot/request", "password forgot/request"); - private void WriteAudit(int performingId, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) + private void WriteAudit(string performingId, string affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) { - var performingUser = _userService.GetUserById(performingId); + IUser performingUser = null; + if (int.TryParse(performingId, out int asInt)) + { + performingUser = _userService.GetUserById(asInt); + } var performingDetails = performingUser == null ? $"User UNKNOWN:{performingId}" : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; - WriteAudit(performingId, performingDetails, affectedId, ipAddress, eventType, eventDetails, affectedDetails); + if (!int.TryParse(performingId, out int performingIdAsInt)) + { + performingIdAsInt = 0; + } + + if (!int.TryParse(affectedId, out int affectedIdAsInt)) + { + affectedIdAsInt = 0; + } + + WriteAudit(performingIdAsInt, performingDetails, affectedIdAsInt, ipAddress, eventType, eventDetails, affectedDetails); } - private void WriteAudit(IUser performingUser, int affectedId, string ipAddress, string eventType, string eventDetails) + private void WriteAudit(IUser performingUser, string affectedId, string ipAddress, string eventType, string eventDetails) { var performingDetails = performingUser == null ? $"User UNKNOWN" : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; - WriteAudit(performingUser?.Id ?? 0, performingDetails, affectedId, ipAddress, eventType, eventDetails); + if (!int.TryParse(affectedId, out int affectedIdInt)) + { + affectedIdInt = 0; + } + + WriteAudit(performingUser?.Id ?? 0, performingDetails, affectedIdInt, ipAddress, eventType, eventDetails); } private void WriteAudit(int performingId, string performingDetails, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) { if (affectedDetails == null) { - var affectedUser = _userService.GetUserById(affectedId); + IUser affectedUser = _userService.GetUserById(affectedId); affectedDetails = affectedUser == null ? $"User UNKNOWN:{affectedId}" : $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}"; } - _auditService.Write(performingId, performingDetails, + _auditService.Write( + performingId, + performingDetails, ipAddress, DateTime.UtcNow, - affectedId, affectedDetails, - eventType, eventDetails); + affectedId, + affectedDetails, + eventType, + eventDetails); } protected virtual void Dispose(bool disposing) @@ -137,12 +143,9 @@ namespace Umbraco.Web.Common.Security { if (disposing) { - //BackOfficeUserManager.AccountLocked -= ; - //BackOfficeUserManager.AccountUnlocked -= ; BackOfficeUserManager.ForgotPasswordRequested -= OnForgotPasswordRequest; BackOfficeUserManager.ForgotPasswordChangedSuccess -= OnForgotPasswordChange; BackOfficeUserManager.LoginFailed -= OnLoginFailed; - //BackOfficeUserManager.LoginRequiresVerification -= ; BackOfficeUserManager.LoginSuccess -= OnLoginSuccess; BackOfficeUserManager.LogoutSuccess -= OnLogoutSuccess; BackOfficeUserManager.PasswordChanged -= OnPasswordChanged; diff --git a/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs b/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs index dd92801d59..180f433fab 100644 --- a/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs +++ b/src/Umbraco.Web.BackOffice/Security/PasswordChanger.cs @@ -67,7 +67,7 @@ namespace Umbraco.Web.BackOffice.Security //ok, we should be able to reset it var resetToken = await userMgr.GeneratePasswordResetTokenAsync(backOfficeIdentityUser); - var resetResult = await userMgr.ChangePasswordWithResetAsync(savingUser.Id, resetToken, passwordModel.NewPassword); + var resetResult = await userMgr.ChangePasswordWithResetAsync(savingUser.Id.ToString(), resetToken, passwordModel.NewPassword); if (resetResult.Succeeded == false) { diff --git a/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs index 230faeff28..081ca6b581 100644 --- a/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs @@ -117,7 +117,7 @@ namespace Umbraco.Web.Common.Security return result; } - public override async Task ChangePasswordWithResetAsync(int userId, string token, string newPassword) + public override async Task ChangePasswordWithResetAsync(string userId, string token, string newPassword) { IdentityResult result = await base.ChangePasswordWithResetAsync(userId, token, newPassword); if (result.Succeeded) @@ -176,21 +176,21 @@ namespace Umbraco.Web.Common.Security return result; } - private int GetCurrentUserId(IPrincipal currentUser) + private string GetCurrentUserId(IPrincipal currentUser) { UmbracoBackOfficeIdentity umbIdentity = currentUser?.GetUmbracoIdentity(); - var currentUserId = umbIdentity?.GetUserId() ?? Core.Constants.Security.SuperUserId; + var currentUserId = umbIdentity?.GetUserId() ?? Core.Constants.Security.SuperUserIdAsString; return currentUserId; } - private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, IPrincipal currentUser, int affectedUserId, string affectedUsername) + private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, IPrincipal currentUser, string affectedUserId, string affectedUsername) { var currentUserId = GetCurrentUserId(currentUser); var ip = IpResolver.GetCurrentRequestIpAddress(); return new IdentityAuditEventArgs(auditEvent, ip, currentUserId, string.Empty, affectedUserId, affectedUsername); } - private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, BackOfficeIdentityUser currentUser, int affectedUserId, string affectedUsername) + private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, BackOfficeIdentityUser currentUser, string affectedUserId, string affectedUsername) { var currentUserId = currentUser.Id; var ip = IpResolver.GetCurrentRequestIpAddress(); @@ -199,21 +199,21 @@ namespace Umbraco.Web.Common.Security // TODO: Review where these are raised and see if they can be simplified and either done in the this usermanager or the signin manager, // lastly we'll resort to the authentication controller but we should try to remove all instances of that occuring - public void RaiseAccountLockedEvent(IPrincipal currentUser, int userId) => OnAccountLocked(CreateArgs(AuditEvent.AccountLocked, currentUser, userId, string.Empty)); + public void RaiseAccountLockedEvent(IPrincipal currentUser, string userId) => OnAccountLocked(CreateArgs(AuditEvent.AccountLocked, currentUser, userId, string.Empty)); - public void RaiseAccountUnlockedEvent(IPrincipal currentUser, int userId) => OnAccountUnlocked(CreateArgs(AuditEvent.AccountUnlocked, currentUser, userId, string.Empty)); + public void RaiseAccountUnlockedEvent(IPrincipal currentUser, string userId) => OnAccountUnlocked(CreateArgs(AuditEvent.AccountUnlocked, currentUser, userId, string.Empty)); - public void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, int userId) => OnForgotPasswordRequested(CreateArgs(AuditEvent.ForgotPasswordRequested, currentUser, userId, string.Empty)); + public void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, string userId) => OnForgotPasswordRequested(CreateArgs(AuditEvent.ForgotPasswordRequested, currentUser, userId, string.Empty)); - public void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, int userId) => OnForgotPasswordChangedSuccess(CreateArgs(AuditEvent.ForgotPasswordChangedSuccess, currentUser, userId, string.Empty)); + public void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, string userId) => OnForgotPasswordChangedSuccess(CreateArgs(AuditEvent.ForgotPasswordChangedSuccess, currentUser, userId, string.Empty)); - public void RaiseLoginFailedEvent(IPrincipal currentUser, int userId) => OnLoginFailed(CreateArgs(AuditEvent.LoginFailed, currentUser, userId, string.Empty)); + public void RaiseLoginFailedEvent(IPrincipal currentUser, string userId) => OnLoginFailed(CreateArgs(AuditEvent.LoginFailed, currentUser, userId, string.Empty)); - public void RaiseLoginRequiresVerificationEvent(IPrincipal currentUser, int userId) => OnLoginRequiresVerification(CreateArgs(AuditEvent.LoginRequiresVerification, currentUser, userId, string.Empty)); + public void RaiseLoginRequiresVerificationEvent(IPrincipal currentUser, string userId) => OnLoginRequiresVerification(CreateArgs(AuditEvent.LoginRequiresVerification, currentUser, userId, string.Empty)); - public void RaiseLoginSuccessEvent(IPrincipal currentUser, int userId) => OnLoginSuccess(CreateArgs(AuditEvent.LoginSucces, currentUser, userId, string.Empty)); + public void RaiseLoginSuccessEvent(IPrincipal currentUser, string userId) => OnLoginSuccess(CreateArgs(AuditEvent.LoginSucces, currentUser, userId, string.Empty)); - public SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId) + public SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, string userId) { var currentUserId = GetCurrentUserId(currentUser); var args = new SignOutAuditEventArgs(AuditEvent.LogoutSuccess, IpResolver.GetCurrentRequestIpAddress(), performingUser: currentUserId, affectedUser: userId); @@ -221,9 +221,9 @@ namespace Umbraco.Web.Common.Security return args; } - public void RaisePasswordChangedEvent(IPrincipal currentUser, int userId) => OnPasswordChanged(CreateArgs(AuditEvent.LogoutSuccess, currentUser, userId, string.Empty)); + public void RaisePasswordChangedEvent(IPrincipal currentUser, string userId) => OnPasswordChanged(CreateArgs(AuditEvent.LogoutSuccess, currentUser, userId, string.Empty)); - public void RaiseResetAccessFailedCountEvent(IPrincipal currentUser, int userId) => OnResetAccessFailedCount(CreateArgs(AuditEvent.ResetAccessFailedCount, currentUser, userId, string.Empty)); + public void RaiseResetAccessFailedCountEvent(IPrincipal currentUser, string userId) => OnResetAccessFailedCount(CreateArgs(AuditEvent.ResetAccessFailedCount, currentUser, userId, string.Empty)); public UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser) { From 216b8559dadc729e0935fbaa280692c2923b80a1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Dec 2020 12:52:25 +1100 Subject: [PATCH 48/63] cleanup --- .../Security/UmbracoUserManager.cs | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs b/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs index 2ea0bc52b2..6318218669 100644 --- a/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs +++ b/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs @@ -44,19 +44,30 @@ namespace Umbraco.Core.Security PasswordConfiguration = passwordConfiguration.Value ?? throw new ArgumentNullException(nameof(passwordConfiguration)); } - // We don't support an IUserClaimStore and don't need to (at least currently) - public override bool SupportsUserClaim => false; + /// + public override bool SupportsUserClaim => false; // We don't support an IUserClaimStore and don't need to (at least currently) - // It would be nice to support this but we don't need to currently and that would require IQueryable support for our user service/repository - public override bool SupportsQueryableUsers => false; + /// + public override bool SupportsQueryableUsers => false; // It would be nice to support this but we don't need to currently and that would require IQueryable support for our user service/repository /// /// Developers will need to override this to support custom 2 factor auth /// + /// public override bool SupportsUserTwoFactor => false; - // We haven't needed to support this yet, though might be necessary for 2FA - public override bool SupportsUserPhoneNumber => false; + /// + public override bool SupportsUserPhoneNumber => false; // We haven't needed to support this yet, though might be necessary for 2FA + + /// + /// Gets the password configuration + /// + public IPasswordConfiguration PasswordConfiguration { get; } + + /// + /// Gets the IP resolver + /// + public IIpResolver IpResolver { get; } /// /// Used to validate a user's session @@ -85,16 +96,6 @@ namespace Umbraco.Core.Security /// An protected virtual IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) => new PasswordHasher(); - /// - /// Gets the password configuration - /// - public IPasswordConfiguration PasswordConfiguration { get; } - - /// - /// Gets the IP resolver - /// - public IIpResolver IpResolver { get; } - /// /// Helper method to generate a password for a user based on the current password validator /// From 406528f5538c753a9395bc4569578d87436531fb Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Dec 2020 12:54:31 +1100 Subject: [PATCH 49/63] flows claims --- .../Security/BackOfficeClaimsPrincipalFactory.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs b/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs index 8a6680d2bf..77f707d812 100644 --- a/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeClaimsPrincipalFactory.cs @@ -37,13 +37,11 @@ namespace Umbraco.Core.Security ClaimsIdentity baseIdentity = await base.GenerateClaimsAsync(user); - // TODO: How to flow claims then? This is most likely built into aspnetcore now and this is not the way - // now we can flow any custom claims that the actual user has currently assigned which could be done in the OnExternalLogin callback - //foreach (Models.Identity.IdentityUserClaim claim in user.Claims) - //{ - // baseIdentity.AddClaim(new Claim(claim.ClaimType, claim.ClaimValue)); - //} + foreach (IdentityUserClaim claim in user.Claims) + { + baseIdentity.AddClaim(new Claim(claim.ClaimType, claim.ClaimValue)); + } // TODO: We want to remove UmbracoBackOfficeIdentity and only rely on ClaimsIdentity, once // that is done then we'll create a ClaimsIdentity with all of the requirements here instead From 10fc3514708b773552e0fadb02f7fdcb935e637d Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Dec 2020 13:09:08 +1100 Subject: [PATCH 50/63] merge cleanup --- .../AuthenticationBuilderExtensions.cs | 3 ++- src/Umbraco.Web.UI.NetCore/Startup.cs | 11 +++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) rename src/Umbraco.Web.BackOffice/{Security => Extensions}/AuthenticationBuilderExtensions.cs (86%) diff --git a/src/Umbraco.Web.BackOffice/Security/AuthenticationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/AuthenticationBuilderExtensions.cs similarity index 86% rename from src/Umbraco.Web.BackOffice/Security/AuthenticationBuilderExtensions.cs rename to src/Umbraco.Web.BackOffice/Extensions/AuthenticationBuilderExtensions.cs index 9949018d43..8145cb4278 100644 --- a/src/Umbraco.Web.BackOffice/Security/AuthenticationBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/AuthenticationBuilderExtensions.cs @@ -1,7 +1,8 @@ using System; using Umbraco.Core.DependencyInjection; +using Umbraco.Web.BackOffice.Security; -namespace Umbraco.Web.BackOffice.Security +namespace Umbraco.Extensions { public static class AuthenticationBuilderExtensions { diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index d86e6a8776..d496aadfd3 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.UI.NetCore private readonly IConfiguration _config; /// - /// Constructor + /// Initializes a new instance of the class. /// /// The Web Host Environment /// The Configuration @@ -30,15 +30,22 @@ namespace Umbraco.Web.UI.NetCore // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + /// + /// Configures the services + /// public void ConfigureServices(IServiceCollection services) { +#pragma warning disable IDE0022 // Use expression body for methods services.AddUmbraco(_env, _config) .AddAllBackOfficeComponents() .AddUmbracoWebsite() .Build(); +#pragma warning restore IDE0022 // Use expression body for methods } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + /// + /// Configures the application + /// public void Configure(IApplicationBuilder app) { if (_env.IsDevelopment()) From 9441f5c5eae2c06db47238d912a3c1507fd529ad Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 Dec 2020 15:05:47 +1100 Subject: [PATCH 51/63] fix remaining test --- .../Security/BackOfficeUserStore.cs | 8 ++++---- .../Security/BackOfficeAuthenticationBuilder.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs index befa5ebac2..1756e84d76 100644 --- a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs @@ -201,26 +201,26 @@ namespace Umbraco.Core.Security IUser user = _userService.GetUserById(UserIdToInt(userId)); if (user == null) { - return null; + return Task.FromResult((BackOfficeIdentityUser)null); } return Task.FromResult(AssignLoginsCallback(_mapper.Map(user))); } /// - public override async Task FindByNameAsync(string userName, CancellationToken cancellationToken = default) + public override Task FindByNameAsync(string userName, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); IUser user = _userService.GetByUsername(userName); if (user == null) { - return null; + return Task.FromResult((BackOfficeIdentityUser)null); } BackOfficeIdentityUser result = AssignLoginsCallback(_mapper.Map(user)); - return await Task.FromResult(result); + return Task.FromResult(result); } /// diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs index b3418697e2..7012d5f1dd 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; From d74cc9d35a46eeef848ac228c550671c4d8db9eb Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Sat, 5 Dec 2020 08:55:37 +0100 Subject: [PATCH 52/63] Fix wrong version of Smidge in nuspec + Updated to official 3.2.0 --- build/NuSpecs/UmbracoCms.Web.nuspec | 4 ++-- src/Umbraco.Web.Common/Umbraco.Web.Common.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 2fea306ce0..fe5ef6344c 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -30,8 +30,8 @@ - - + + diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index a712a43a45..b09fde0a6a 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -26,8 +26,8 @@ - - + + From 201940290cc6cee89087fcd2b731d76d0d93cffe Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 5 Dec 2020 09:54:59 +0100 Subject: [PATCH 53/63] Adhered to linting rules in hosted services implementations and tests. --- .../HostedServices/HealthCheckNotifier.cs | 33 ++++++-- .../HostedServices/KeepAlive.cs | 28 ++++++- .../HostedServices/LogScrubber.cs | 38 +++++++-- .../RecurringHostedServiceBase.cs | 29 +++++-- .../HostedServices/ScheduledPublishing.cs | 57 +++++++++---- .../InstructionProcessTask.cs | 15 +++- .../ServerRegistration/TouchServerTask.cs | 20 +++-- .../HostedServices/TempFileCleanup.cs | 27 ++++--- .../HealthCheckNotifierTests.cs | 81 +++++++++---------- .../HostedServices/KeepAliveTests.cs | 48 +++++------ .../HostedServices/LogScrubberTests.cs | 42 +++++----- .../ScheduledPublishingTests.cs | 45 +++++------ .../InstructionProcessTaskTests.cs | 24 +++--- .../TouchServerTaskTests.cs | 55 +++++++------ .../HostedServices/TempFileCleanupTests.cs | 26 +++--- 15 files changed, 339 insertions(+), 229 deletions(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs index 58decb80c4..5c8bd0bc2f 100644 --- a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs +++ b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -13,6 +17,7 @@ using Umbraco.Core.Sync; using Umbraco.Infrastructure.Configuration.Extensions; using Umbraco.Infrastructure.HealthCheck; using Umbraco.Web.HealthCheck; +using Umbraco.Web.HealthCheck.NotificationMethods; namespace Umbraco.Infrastructure.HostedServices { @@ -31,6 +36,19 @@ namespace Umbraco.Infrastructure.HostedServices private readonly ILogger _logger; private readonly IProfilingLogger _profilingLogger; + /// + /// Initializes a new instance of the class. + /// + /// The configuration for health check settings. + /// The collection of healthchecks. + /// The collection of healthcheck notification methods. + /// Representation of the state of the Umbraco runtime. + /// Provider of server registrations to the distributed cache. + /// Representation of the main application domain. + /// Provides scopes for database operations. + /// The typed logger. + /// The profiling logger. + /// Parser of crontab expressions. public HealthCheckNotifier( IOptions healthChecksSettings, HealthCheckCollection healthChecks, @@ -42,8 +60,9 @@ namespace Umbraco.Infrastructure.HostedServices ILogger logger, IProfilingLogger profilingLogger, ICronTabParser cronTabParser) - : base(healthChecksSettings.Value.Notification.Period, - healthChecksSettings.Value.GetNotificationDelay(cronTabParser, DateTime.Now, DefaultDelay)) + : base( + healthChecksSettings.Value.Notification.Period, + healthChecksSettings.Value.GetNotificationDelay(cronTabParser, DateTime.Now, DefaultDelay)) { _healthChecksSettings = healthChecksSettings.Value; _healthChecks = healthChecks; @@ -88,25 +107,25 @@ namespace Umbraco.Infrastructure.HostedServices // Ensure we use an explicit scope since we are running on a background thread and plugin health // checks can be making service/database calls so we want to ensure the CallContext/Ambient scope // isn't used since that can be problematic. - using (var scope = _scopeProvider.CreateScope()) + using (IScope scope = _scopeProvider.CreateScope()) using (_profilingLogger.DebugDuration("Health checks executing", "Health checks complete")) { // Don't notify for any checks that are disabled, nor for any disabled just for notifications. - var disabledCheckIds = _healthChecksSettings.Notification.DisabledChecks + Guid[] disabledCheckIds = _healthChecksSettings.Notification.DisabledChecks .Select(x => x.Id) .Union(_healthChecksSettings.DisabledChecks .Select(x => x.Id)) .Distinct() .ToArray(); - var checks = _healthChecks + IEnumerable checks = _healthChecks .Where(x => disabledCheckIds.Contains(x.Id) == false); var results = new HealthCheckResults(checks); results.LogResults(); // Send using registered notification methods that are enabled. - foreach (var notificationMethod in _notifications.Where(x => x.Enabled)) + foreach (IHealthCheckNotificationMethod notificationMethod in _notifications.Where(x => x.Enabled)) { await notificationMethod.SendAsync(results); } diff --git a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs index 39ac0f3d87..6a56b6f98e 100644 --- a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs +++ b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Net.Http; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -24,7 +27,24 @@ namespace Umbraco.Infrastructure.HostedServices private readonly IServerRegistrar _serverRegistrar; private readonly IHttpClientFactory _httpClientFactory; - public KeepAlive(IRequestAccessor requestAccessor, IMainDom mainDom, IOptions keepAliveSettings, ILogger logger, IProfilingLogger profilingLogger, IServerRegistrar serverRegistrar, IHttpClientFactory httpClientFactory) + /// + /// Initializes a new instance of the class. + /// + /// Accessor for the current request. + /// Representation of the main application domain. + /// The configuration for keep alive settings. + /// The typed logger. + /// The profiling logger. + /// Provider of server registrations to the distributed cache. + /// Factory for instances. + public KeepAlive( + IRequestAccessor requestAccessor, + IMainDom mainDom, + IOptions keepAliveSettings, + ILogger logger, + IProfilingLogger profilingLogger, + IServerRegistrar serverRegistrar, + IHttpClientFactory httpClientFactory) : base(TimeSpan.FromMinutes(5), DefaultDelay) { _requestAccessor = requestAccessor; @@ -79,8 +99,8 @@ namespace Umbraco.Infrastructure.HostedServices } var request = new HttpRequestMessage(HttpMethod.Get, keepAlivePingUrl); - var httpClient = _httpClientFactory.CreateClient(); - await httpClient.SendAsync(request); + HttpClient httpClient = _httpClientFactory.CreateClient(); + _ = await httpClient.SendAsync(request); } catch (Exception ex) { diff --git a/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs b/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs index 1cf2da05f9..ca87d3e84e 100644 --- a/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs +++ b/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -27,7 +30,24 @@ namespace Umbraco.Infrastructure.HostedServices private readonly ILogger _logger; private readonly IScopeProvider _scopeProvider; - public LogScrubber(IMainDom mainDom, IServerRegistrar serverRegistrar, IAuditService auditService, IOptions settings, IScopeProvider scopeProvider, ILogger logger, IProfilingLogger profilingLogger) + /// + /// Initializes a new instance of the class. + /// + /// Representation of the main application domain. + /// Provider of server registrations to the distributed cache. + /// Service for handling audit operations. + /// The configuration for logging settings. + /// Provides scopes for database operations. + /// The typed logger. + /// The profiling logger. + public LogScrubber( + IMainDom mainDom, + IServerRegistrar serverRegistrar, + IAuditService auditService, + IOptions settings, + IScopeProvider scopeProvider, + ILogger logger, + IProfilingLogger profilingLogger) : base(TimeSpan.FromHours(4), DefaultDelay) { _mainDom = mainDom; @@ -39,32 +59,34 @@ namespace Umbraco.Infrastructure.HostedServices _profilingLogger = profilingLogger; } - internal override async Task PerformExecuteAsync(object state) + internal override Task PerformExecuteAsync(object state) { switch (_serverRegistrar.GetCurrentServerRole()) { case ServerRole.Replica: _logger.LogDebug("Does not run on replica servers."); - return; + return Task.CompletedTask; case ServerRole.Unknown: _logger.LogDebug("Does not run on servers with unknown role."); - return; + return Task.CompletedTask; } // Ensure we do not run if not main domain, but do NOT lock it if (_mainDom.IsMainDom == false) { _logger.LogDebug("Does not run if not MainDom."); - return; + return Task.CompletedTask; } // Ensure we use an explicit scope since we are running on a background thread. - using (var scope = _scopeProvider.CreateScope()) + using (IScope scope = _scopeProvider.CreateScope()) using (_profilingLogger.DebugDuration("Log scrubbing executing", "Log scrubbing complete")) { _auditService.CleanLogs((int)_settings.MaxLogAge.TotalMinutes); - scope.Complete(); + _ = scope.Complete(); } + + return Task.CompletedTask; } } } diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index ee77326115..7e9354523a 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; @@ -13,44 +16,54 @@ namespace Umbraco.Infrastructure.HostedServices /// public abstract class RecurringHostedServiceBase : IHostedService, IDisposable { + /// + /// The default delay to use for recurring tasks for the first run after application start-up if no alternative is configured. + /// protected static readonly TimeSpan DefaultDelay = TimeSpan.FromMinutes(3); private readonly TimeSpan _period; private readonly TimeSpan _delay; private Timer _timer; + /// + /// Initializes a new instance of the class. + /// + /// Timepsan representing how often the task should recur. + /// Timespan represeting the initial delay after application start-up before the first run of the task occurs. protected RecurringHostedServiceBase(TimeSpan period, TimeSpan delay) { _period = period; _delay = delay; } + /// public Task StartAsync(CancellationToken cancellationToken) { _timer = new Timer(ExecuteAsync, null, (int)_delay.TotalMilliseconds, (int)_period.TotalMilliseconds); return Task.CompletedTask; } - public async void ExecuteAsync(object state) - { + /// + /// Executes the task. + /// + /// The task state. + public async void ExecuteAsync(object state) => // Delegate work to method returning a task, that can be called and asserted in a unit test. // Without this there can be behaviour where tests pass, but an error within them causes the test // running process to crash. // Hat-tip: https://stackoverflow.com/a/14207615/489433 await PerformExecuteAsync(state); - } internal abstract Task PerformExecuteAsync(object state); + /// public Task StopAsync(CancellationToken cancellationToken) { _timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask; } - public void Dispose() - { - _timer?.Dispose(); - } + /// + public void Dispose() => _timer?.Dispose(); } } diff --git a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs index f48e64ad2d..4c235255c2 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs @@ -1,8 +1,13 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Umbraco.Core; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Sync; using Umbraco.Web; @@ -19,18 +24,35 @@ namespace Umbraco.Infrastructure.HostedServices private readonly IContentService _contentService; private readonly ILogger _logger; private readonly IMainDom _mainDom; - private readonly IRuntimeState _runtime; + private readonly IRuntimeState _runtimeState; private readonly IServerMessenger _serverMessenger; private readonly IBackOfficeSecurityFactory _backofficeSecurityFactory; private readonly IServerRegistrar _serverRegistrar; private readonly IUmbracoContextFactory _umbracoContextFactory; + /// + /// Initializes a new instance of the class. + /// + /// Representation of the state of the Umbraco runtime. + /// Representation of the main application domain. + /// Provider of server registrations to the distributed cache. + /// Service for handling content operations. + /// Service for creating and managing Umbraco context. + /// The typed logger. + /// Service broadcasting cache notifications to registered servers. + /// Creates and manages instances. public ScheduledPublishing( - IRuntimeState runtime, IMainDom mainDom, IServerRegistrar serverRegistrar, IContentService contentService, - IUmbracoContextFactory umbracoContextFactory, ILogger logger, IServerMessenger serverMessenger, IBackOfficeSecurityFactory backofficeSecurityFactory) + IRuntimeState runtimeState, + IMainDom mainDom, + IServerRegistrar serverRegistrar, + IContentService contentService, + IUmbracoContextFactory umbracoContextFactory, + ILogger logger, + IServerMessenger serverMessenger, + IBackOfficeSecurityFactory backofficeSecurityFactory) : base(TimeSpan.FromMinutes(1), DefaultDelay) { - _runtime = runtime; + _runtimeState = runtimeState; _mainDom = mainDom; _serverRegistrar = serverRegistrar; _contentService = contentService; @@ -40,35 +62,35 @@ namespace Umbraco.Infrastructure.HostedServices _backofficeSecurityFactory = backofficeSecurityFactory; } - internal override async Task PerformExecuteAsync(object state) + internal override Task PerformExecuteAsync(object state) { if (Suspendable.ScheduledPublishing.CanRun == false) { - return; + return Task.CompletedTask; } switch (_serverRegistrar.GetCurrentServerRole()) { case ServerRole.Replica: _logger.LogDebug("Does not run on replica servers."); - return; + return Task.CompletedTask; case ServerRole.Unknown: _logger.LogDebug("Does not run on servers with unknown role."); - return; + return Task.CompletedTask; } // Ensure we do not run if not main domain, but do NOT lock it if (_mainDom.IsMainDom == false) { _logger.LogDebug("Does not run if not MainDom."); - return; + return Task.CompletedTask; } // Do NOT run publishing if not properly running - if (_runtime.Level != RuntimeLevel.Run) + if (_runtimeState.Level != RuntimeLevel.Run) { _logger.LogDebug("Does not run if run level is not Run."); - return; + return Task.CompletedTask; } try @@ -85,16 +107,17 @@ namespace Umbraco.Infrastructure.HostedServices // - and we should definitively *not* have to flush it here (should be auto) // _backofficeSecurityFactory.EnsureBackOfficeSecurity(); - using var contextReference = _umbracoContextFactory.EnsureUmbracoContext(); + using UmbracoContextReference contextReference = _umbracoContextFactory.EnsureUmbracoContext(); try { // Run - var result = _contentService.PerformScheduledPublish(DateTime.Now); - foreach (var grouped in result.GroupBy(x => x.Result)) + IEnumerable result = _contentService.PerformScheduledPublish(DateTime.Now); + foreach (IGrouping grouped in result.GroupBy(x => x.Result)) { _logger.LogInformation( "Scheduled publishing result: '{StatusCount}' items with status {Status}", - grouped.Count(), grouped.Key); + grouped.Count(), + grouped.Key); } } finally @@ -112,7 +135,7 @@ namespace Umbraco.Infrastructure.HostedServices _logger.LogError(ex, "Failed."); } - return; + return Task.CompletedTask; } } } diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs index 8d669178b0..3c291f187b 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -20,6 +23,10 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration /// /// Initializes a new instance of the class. /// + /// Representation of the state of the Umbraco runtime. + /// Service broadcasting cache notifications to registered servers. + /// The typed logger. + /// The configuration for global settings. public InstructionProcessTask(IRuntimeState runtimeState, IServerMessenger messenger, ILogger logger, IOptions globalSettings) : base(globalSettings.Value.DatabaseServerMessenger.TimeBetweenSyncOperations, TimeSpan.FromMinutes(1)) { @@ -28,11 +35,11 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration _logger = logger; } - internal override async Task PerformExecuteAsync(object state) + internal override Task PerformExecuteAsync(object state) { if (_runtimeState.Level != RuntimeLevel.Run) { - return; + return Task.CompletedTask; } try @@ -43,6 +50,8 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration { _logger.LogError(e, "Failed (will repeat)."); } + + return Task.CompletedTask; } } } diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs index 0384a071c6..25e975582d 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -18,11 +21,16 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration private readonly IServerRegistrationService _serverRegistrationService; private readonly IRequestAccessor _requestAccessor; private readonly ILogger _logger; - private GlobalSettings _globalSettings; + private readonly GlobalSettings _globalSettings; /// /// Initializes a new instance of the class. /// + /// Representation of the state of the Umbraco runtime. + /// Services for server registrations. + /// Accessor for the current request. + /// The typed logger. + /// The configuration for global settings. public TouchServerTask(IRuntimeState runtimeState, IServerRegistrationService serverRegistrationService, IRequestAccessor requestAccessor, ILogger logger, IOptions globalSettings) : base(globalSettings.Value.DatabaseServerRegistrar.WaitTimeBetweenCalls, TimeSpan.FromSeconds(15)) { @@ -33,18 +41,18 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration _globalSettings = globalSettings.Value; } - internal override async Task PerformExecuteAsync(object state) + internal override Task PerformExecuteAsync(object state) { if (_runtimeState.Level != RuntimeLevel.Run) { - return; + return Task.CompletedTask; } var serverAddress = _requestAccessor.GetApplicationUrl()?.ToString(); if (serverAddress.IsNullOrWhiteSpace()) { _logger.LogWarning("No umbracoApplicationUrl for service (yet), skip."); - return; + return Task.CompletedTask; } try @@ -57,6 +65,8 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration { _logger.LogError(ex, "Failed to update server record in database."); } + + return Task.CompletedTask; } } } diff --git a/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs b/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs index e27b83c8f6..7e3f70d510 100644 --- a/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs +++ b/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.IO; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -23,6 +26,12 @@ namespace Umbraco.Infrastructure.HostedServices private readonly DirectoryInfo[] _tempFolders; private readonly TimeSpan _age = TimeSpan.FromDays(1); + /// + /// Initializes a new instance of the class. + /// + /// Helper service for IO operations. + /// Representation of the main application domain. + /// The typed logger. public TempFileCleanup(IIOHelper ioHelper, IMainDom mainDom, ILogger logger) : base(TimeSpan.FromMinutes(60), DefaultDelay) { @@ -33,33 +42,33 @@ namespace Umbraco.Infrastructure.HostedServices _tempFolders = _ioHelper.GetTempFolders(); } - internal override async Task PerformExecuteAsync(object state) + internal override Task PerformExecuteAsync(object state) { // Ensure we do not run if not main domain if (_mainDom.IsMainDom == false) { _logger.LogDebug("Does not run if not MainDom."); - return; + return Task.CompletedTask; } - foreach (var folder in _tempFolders) + foreach (DirectoryInfo folder in _tempFolders) { CleanupFolder(folder); } - return; + return Task.CompletedTask; } private void CleanupFolder(DirectoryInfo folder) { - var result = _ioHelper.CleanFolder(folder, _age); + CleanFolderResult result = _ioHelper.CleanFolder(folder, _age); switch (result.Status) { case CleanFolderResultStatus.FailedAsDoesNotExist: _logger.LogDebug("The cleanup folder doesn't exist {Folder}", folder.FullName); break; case CleanFolderResultStatus.FailedWithException: - foreach (var error in result.Errors) + foreach (CleanFolderResult.Error error in result.Errors) { _logger.LogError(error.Exception, "Could not delete temp file {FileName}", error.ErroringFile.FullName); } @@ -74,8 +83,8 @@ namespace Umbraco.Infrastructure.HostedServices return; } - var files = folder.GetFiles("*.*", SearchOption.AllDirectories); - foreach (var file in files) + FileInfo[] files = folder.GetFiles("*.*", SearchOption.AllDirectories); + foreach (FileInfo file in files) { if (DateTime.UtcNow - file.LastWriteTimeUtc > _age) { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs index cdb98f7fa5..ffad002928 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -25,14 +28,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices { private Mock _mockNotificationMethod; - private const string _check1Id = "00000000-0000-0000-0000-000000000001"; - private const string _check2Id = "00000000-0000-0000-0000-000000000002"; - private const string _check3Id = "00000000-0000-0000-0000-000000000003"; + private const string Check1Id = "00000000-0000-0000-0000-000000000001"; + private const string Check2Id = "00000000-0000-0000-0000-000000000002"; + private const string Check3Id = "00000000-0000-0000-0000-000000000003"; [Test] public async Task Does_Not_Execute_When_Not_Enabled() { - var sut = CreateHealthCheckNotifier(enabled: false); + HealthCheckNotifier sut = CreateHealthCheckNotifier(enabled: false); await sut.PerformExecuteAsync(null); VerifyNotificationsNotSent(); } @@ -44,7 +47,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [TestCase(RuntimeLevel.BootFailed)] public async Task Does_Not_Execute_When_Runtime_State_Is_Not_Run(RuntimeLevel runtimeLevel) { - var sut = CreateHealthCheckNotifier(runtimeLevel: runtimeLevel); + HealthCheckNotifier sut = CreateHealthCheckNotifier(runtimeLevel: runtimeLevel); await sut.PerformExecuteAsync(null); VerifyNotificationsNotSent(); } @@ -52,7 +55,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Replica() { - var sut = CreateHealthCheckNotifier(serverRole: ServerRole.Replica); + HealthCheckNotifier sut = CreateHealthCheckNotifier(serverRole: ServerRole.Replica); await sut.PerformExecuteAsync(null); VerifyNotificationsNotSent(); } @@ -60,7 +63,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Unknown() { - var sut = CreateHealthCheckNotifier(serverRole: ServerRole.Unknown); + HealthCheckNotifier sut = CreateHealthCheckNotifier(serverRole: ServerRole.Unknown); await sut.PerformExecuteAsync(null); VerifyNotificationsNotSent(); } @@ -68,7 +71,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Not_Main_Dom() { - var sut = CreateHealthCheckNotifier(isMainDom: false); + HealthCheckNotifier sut = CreateHealthCheckNotifier(isMainDom: false); await sut.PerformExecuteAsync(null); VerifyNotificationsNotSent(); } @@ -76,7 +79,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_With_No_Enabled_Notification_Methods() { - var sut = CreateHealthCheckNotifier(notificationEnabled: false); + HealthCheckNotifier sut = CreateHealthCheckNotifier(notificationEnabled: false); await sut.PerformExecuteAsync(null); VerifyNotificationsNotSent(); } @@ -84,7 +87,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Executes_With_Enabled_Notification_Methods() { - var sut = CreateHealthCheckNotifier(); + HealthCheckNotifier sut = CreateHealthCheckNotifier(); await sut.PerformExecuteAsync(null); VerifyNotificationsSent(); } @@ -92,10 +95,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Executes_Only_Enabled_Checks() { - var sut = CreateHealthCheckNotifier(); + HealthCheckNotifier sut = CreateHealthCheckNotifier(); await sut.PerformExecuteAsync(null); - _mockNotificationMethod.Verify(x => x.SendAsync(It.Is( - y => y.ResultsAsDictionary.Count == 1 && y.ResultsAsDictionary.ContainsKey("Check1"))), Times.Once); + _mockNotificationMethod.Verify( + x => x.SendAsync( + It.Is(y => y.ResultsAsDictionary.Count == 1 && y.ResultsAsDictionary.ContainsKey("Check1"))), Times.Once); } private HealthCheckNotifier CreateHealthCheckNotifier( @@ -112,12 +116,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices Enabled = enabled, DisabledChecks = new List { - new DisabledHealthCheckSettings { Id = Guid.Parse(_check3Id) } + new DisabledHealthCheckSettings { Id = Guid.Parse(Check3Id) } } }, DisabledChecks = new List { - new DisabledHealthCheckSettings { Id = Guid.Parse(_check2Id) } + new DisabledHealthCheckSettings { Id = Guid.Parse(Check2Id) } } }; var checks = new HealthCheckCollection(new List @@ -144,52 +148,45 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices var mockLogger = new Mock>(); var mockProfilingLogger = new Mock(); - return new HealthCheckNotifier(Options.Create(settings), checks, notifications, - mockRunTimeState.Object, mockServerRegistrar.Object, mockMainDom.Object, mockScopeProvider.Object, - mockLogger.Object, mockProfilingLogger.Object, Mock.Of()); + return new HealthCheckNotifier( + Options.Create(settings), + checks, + notifications, + mockRunTimeState.Object, + mockServerRegistrar.Object, + mockMainDom.Object, + mockScopeProvider.Object, + mockLogger.Object, + mockProfilingLogger.Object, + Mock.Of()); } - private void VerifyNotificationsNotSent() - { - VerifyNotificationsSentTimes(Times.Never()); - } + private void VerifyNotificationsNotSent() => VerifyNotificationsSentTimes(Times.Never()); - private void VerifyNotificationsSent() - { - VerifyNotificationsSentTimes(Times.Once()); - } + private void VerifyNotificationsSent() => VerifyNotificationsSentTimes(Times.Once()); - private void VerifyNotificationsSentTimes(Times times) - { - _mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny()), times); - } + private void VerifyNotificationsSentTimes(Times times) => _mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny()), times); - [HealthCheck(_check1Id, "Check1")] + [HealthCheck(Check1Id, "Check1")] private class TestHealthCheck1 : TestHealthCheck { } - [HealthCheck(_check2Id, "Check2")] + [HealthCheck(Check2Id, "Check2")] private class TestHealthCheck2 : TestHealthCheck { } - [HealthCheck(_check3Id, "Check3")] + [HealthCheck(Check3Id, "Check3")] private class TestHealthCheck3 : TestHealthCheck { } private class TestHealthCheck : HealthCheck { - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - { - return new HealthCheckStatus("Check message"); - } + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => new HealthCheckStatus("Check message"); - public override IEnumerable GetStatus() - { - return Enumerable.Empty(); - } + public override IEnumerable GetStatus() => Enumerable.Empty(); } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs index 9fc1454b6d..98164a7aac 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Net; using System.Net.Http; using System.Threading; @@ -23,12 +26,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices { private Mock _mockHttpMessageHandler; - private const string _applicationUrl = "https://mysite.com"; + private const string ApplicationUrl = "https://mysite.com"; [Test] public async Task Does_Not_Execute_When_Not_Enabled() { - var sut = CreateKeepAlive(enabled: false); + KeepAlive sut = CreateKeepAlive(enabled: false); await sut.PerformExecuteAsync(null); VerifyKeepAliveRequestNotSent(); } @@ -36,7 +39,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Replica() { - var sut = CreateKeepAlive(serverRole: ServerRole.Replica); + KeepAlive sut = CreateKeepAlive(serverRole: ServerRole.Replica); await sut.PerformExecuteAsync(null); VerifyKeepAliveRequestNotSent(); } @@ -44,7 +47,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Unknown() { - var sut = CreateKeepAlive(serverRole: ServerRole.Unknown); + KeepAlive sut = CreateKeepAlive(serverRole: ServerRole.Unknown); await sut.PerformExecuteAsync(null); VerifyKeepAliveRequestNotSent(); } @@ -52,7 +55,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Not_Main_Dom() { - var sut = CreateKeepAlive(isMainDom: false); + KeepAlive sut = CreateKeepAlive(isMainDom: false); await sut.PerformExecuteAsync(null); VerifyKeepAliveRequestNotSent(); } @@ -60,7 +63,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Executes_And_Calls_Ping_Url() { - var sut = CreateKeepAlive(); + KeepAlive sut = CreateKeepAlive(); await sut.PerformExecuteAsync(null); VerifyKeepAliveRequestSent(); } @@ -76,7 +79,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices }; var mockRequestAccessor = new Mock(); - mockRequestAccessor.Setup(x => x.GetApplicationUrl()).Returns(new Uri(_applicationUrl)); + mockRequestAccessor.Setup(x => x.GetApplicationUrl()).Returns(new Uri(ApplicationUrl)); var mockServerRegistrar = new Mock(); mockServerRegistrar.Setup(x => x.GetCurrentServerRole()).Returns(serverRole); @@ -99,26 +102,25 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices var mockHttpClientFactory = new Mock(MockBehavior.Strict); mockHttpClientFactory.Setup(x => x.CreateClient(It.IsAny())).Returns(httpClient); - return new KeepAlive(mockRequestAccessor.Object, mockMainDom.Object, Options.Create(settings), - mockLogger.Object, mockProfilingLogger.Object, mockServerRegistrar.Object, mockHttpClientFactory.Object); + return new KeepAlive( + mockRequestAccessor.Object, + mockMainDom.Object, + Options.Create(settings), + mockLogger.Object, + mockProfilingLogger.Object, + mockServerRegistrar.Object, + mockHttpClientFactory.Object); } - private void VerifyKeepAliveRequestNotSent() - { - VerifyKeepAliveRequestSentTimes(Times.Never()); - } + private void VerifyKeepAliveRequestNotSent() => VerifyKeepAliveRequestSentTimes(Times.Never()); - private void VerifyKeepAliveRequestSent() - { - VerifyKeepAliveRequestSentTimes(Times.Once()); - } + private void VerifyKeepAliveRequestSent() => VerifyKeepAliveRequestSentTimes(Times.Once()); - private void VerifyKeepAliveRequestSentTimes(Times times) - { - _mockHttpMessageHandler.Protected().Verify("SendAsync", + private void VerifyKeepAliveRequestSentTimes(Times times) => _mockHttpMessageHandler.Protected() + .Verify( + "SendAsync", times, - ItExpr.Is(x => x.RequestUri.ToString() == $"{_applicationUrl}/api/keepalive/ping"), + ItExpr.Is(x => x.RequestUri.ToString() == $"{ApplicationUrl}/api/keepalive/ping"), ItExpr.IsAny()); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs index d30c83c545..564b716f75 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Data; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -21,12 +24,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices { private Mock _mockAuditService; - const int _maxLogAgeInMinutes = 60; + private const int MaxLogAgeInMinutes = 60; [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Replica() { - var sut = CreateLogScrubber(serverRole: ServerRole.Replica); + LogScrubber sut = CreateLogScrubber(serverRole: ServerRole.Replica); await sut.PerformExecuteAsync(null); VerifyLogsNotScrubbed(); } @@ -34,7 +37,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Unknown() { - var sut = CreateLogScrubber(serverRole: ServerRole.Unknown); + LogScrubber sut = CreateLogScrubber(serverRole: ServerRole.Unknown); await sut.PerformExecuteAsync(null); VerifyLogsNotScrubbed(); } @@ -42,7 +45,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Not_Main_Dom() { - var sut = CreateLogScrubber(isMainDom: false); + LogScrubber sut = CreateLogScrubber(isMainDom: false); await sut.PerformExecuteAsync(null); VerifyLogsNotScrubbed(); } @@ -50,7 +53,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Executes_And_Scrubs_Logs() { - var sut = CreateLogScrubber(); + LogScrubber sut = CreateLogScrubber(); await sut.PerformExecuteAsync(null); VerifyLogsScrubbed(); } @@ -61,7 +64,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices { var settings = new LoggingSettings { - MaxLogAge = TimeSpan.FromMinutes(_maxLogAgeInMinutes), + MaxLogAge = TimeSpan.FromMinutes(MaxLogAgeInMinutes), }; var mockServerRegistrar = new Mock(); @@ -80,23 +83,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices _mockAuditService = new Mock(); - return new LogScrubber(mockMainDom.Object, mockServerRegistrar.Object, _mockAuditService.Object, - Options.Create(settings), mockScopeProvider.Object, mockLogger.Object, mockProfilingLogger.Object); + return new LogScrubber( + mockMainDom.Object, + mockServerRegistrar.Object, + _mockAuditService.Object, + Options.Create(settings), + mockScopeProvider.Object, + mockLogger.Object, + mockProfilingLogger.Object); } - private void VerifyLogsNotScrubbed() - { - VerifyLogsScrubbed(Times.Never()); - } + private void VerifyLogsNotScrubbed() => VerifyLogsScrubbed(Times.Never()); - private void VerifyLogsScrubbed() - { - VerifyLogsScrubbed(Times.Once()); - } + private void VerifyLogsScrubbed() => VerifyLogsScrubbed(Times.Once()); - private void VerifyLogsScrubbed(Times times) - { - _mockAuditService.Verify(x => x.CleanLogs(It.Is(y => y == _maxLogAgeInMinutes)), times); - } + private void VerifyLogsScrubbed(Times times) => _mockAuditService.Verify(x => x.CleanLogs(It.Is(y => y == MaxLogAgeInMinutes)), times); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs index a7d6925d18..fa3a609ce6 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs @@ -1,15 +1,16 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Logging; using Umbraco.Core.Services; using Umbraco.Core.Sync; using Umbraco.Infrastructure.HostedServices; using Umbraco.Web; -using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices { @@ -22,7 +23,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Not_Enabled() { - var sut = CreateScheduledPublishing(enabled: false); + ScheduledPublishing sut = CreateScheduledPublishing(enabled: false); await sut.PerformExecuteAsync(null); VerifyScheduledPublishingNotPerformed(); } @@ -34,7 +35,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [TestCase(RuntimeLevel.BootFailed)] public async Task Does_Not_Execute_When_Runtime_State_Is_Not_Run(RuntimeLevel runtimeLevel) { - var sut = CreateScheduledPublishing(runtimeLevel: runtimeLevel); + ScheduledPublishing sut = CreateScheduledPublishing(runtimeLevel: runtimeLevel); await sut.PerformExecuteAsync(null); VerifyScheduledPublishingNotPerformed(); } @@ -42,7 +43,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Replica() { - var sut = CreateScheduledPublishing(serverRole: ServerRole.Replica); + ScheduledPublishing sut = CreateScheduledPublishing(serverRole: ServerRole.Replica); await sut.PerformExecuteAsync(null); VerifyScheduledPublishingNotPerformed(); } @@ -50,7 +51,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Server_Role_Is_Unknown() { - var sut = CreateScheduledPublishing(serverRole: ServerRole.Unknown); + ScheduledPublishing sut = CreateScheduledPublishing(serverRole: ServerRole.Unknown); await sut.PerformExecuteAsync(null); VerifyScheduledPublishingNotPerformed(); } @@ -58,7 +59,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Does_Not_Execute_When_Not_Main_Dom() { - var sut = CreateScheduledPublishing(isMainDom: false); + ScheduledPublishing sut = CreateScheduledPublishing(isMainDom: false); await sut.PerformExecuteAsync(null); VerifyScheduledPublishingNotPerformed(); } @@ -66,7 +67,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Executes_And_Performs_Scheduled_Publishing() { - var sut = CreateScheduledPublishing(); + ScheduledPublishing sut = CreateScheduledPublishing(); await sut.PerformExecuteAsync(null); VerifyScheduledPublishingPerformed(); } @@ -106,23 +107,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices var mockBackOfficeSecurityFactory = new Mock(); - return new ScheduledPublishing(mockRunTimeState.Object, mockMainDom.Object, mockServerRegistrar.Object, _mockContentService.Object, - mockUmbracoContextFactory.Object, _mockLogger.Object, mockServerMessenger.Object, mockBackOfficeSecurityFactory.Object); + return new ScheduledPublishing( + mockRunTimeState.Object, + mockMainDom.Object, + mockServerRegistrar.Object, + _mockContentService.Object, + mockUmbracoContextFactory.Object, + _mockLogger.Object, + mockServerMessenger.Object, + mockBackOfficeSecurityFactory.Object); } - private void VerifyScheduledPublishingNotPerformed() - { - VerifyScheduledPublishingPerformed(Times.Never()); - } + private void VerifyScheduledPublishingNotPerformed() => VerifyScheduledPublishingPerformed(Times.Never()); - private void VerifyScheduledPublishingPerformed() - { - VerifyScheduledPublishingPerformed(Times.Once()); - } + private void VerifyScheduledPublishingPerformed() => VerifyScheduledPublishingPerformed(Times.Once()); - private void VerifyScheduledPublishingPerformed(Times times) - { - _mockContentService.Verify(x => x.PerformScheduledPublish(It.IsAny()), times); - } + private void VerifyScheduledPublishingPerformed(Times times) => _mockContentService.Verify(x => x.PerformScheduledPublish(It.IsAny()), times); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs index 86644afc77..6ea56792e2 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; @@ -22,7 +25,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe [TestCase(RuntimeLevel.BootFailed)] public async Task Does_Not_Execute_When_Runtime_State_Is_Not_Run(RuntimeLevel runtimeLevel) { - var sut = CreateInstructionProcessTask(runtimeLevel: runtimeLevel); + InstructionProcessTask sut = CreateInstructionProcessTask(runtimeLevel: runtimeLevel); await sut.PerformExecuteAsync(null); VerifyMessengerNotSynced(); } @@ -30,7 +33,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe [Test] public async Task Executes_And_Touches_Server() { - var sut = CreateInstructionProcessTask(); + InstructionProcessTask sut = CreateInstructionProcessTask(); await sut.PerformExecuteAsync(null); VerifyMessengerSynced(); } @@ -49,19 +52,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe return new InstructionProcessTask(mockRunTimeState.Object, _mockDatabaseServerMessenger.Object, mockLogger.Object, Options.Create(settings)); } - private void VerifyMessengerNotSynced() - { - VerifyMessengerSyncedTimes(Times.Never()); - } + private void VerifyMessengerNotSynced() => VerifyMessengerSyncedTimes(Times.Never()); - private void VerifyMessengerSynced() - { - VerifyMessengerSyncedTimes(Times.Once()); - } + private void VerifyMessengerSynced() => VerifyMessengerSyncedTimes(Times.Once()); - private void VerifyMessengerSyncedTimes(Times times) - { - _mockDatabaseServerMessenger.Verify(x => x.Sync(), times); - } + private void VerifyMessengerSyncedTimes(Times times) => _mockDatabaseServerMessenger.Verify(x => x.Sync(), times); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs index 499ff05f04..7f58f39346 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -17,8 +20,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe { private Mock _mockServerRegistrationService; - private const string _applicationUrl = "https://mysite.com/"; - private const string _serverIdentity = "Test/1"; + private const string ApplicationUrl = "https://mysite.com/"; + private const string ServerIdentity = "Test/1"; private readonly TimeSpan _staleServerTimeout = TimeSpan.FromMinutes(2); [TestCase(RuntimeLevel.Boot)] @@ -28,7 +31,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe [TestCase(RuntimeLevel.BootFailed)] public async Task Does_Not_Execute_When_Runtime_State_Is_Not_Run(RuntimeLevel runtimeLevel) { - var sut = CreateTouchServerTask(runtimeLevel: runtimeLevel); + TouchServerTask sut = CreateTouchServerTask(runtimeLevel: runtimeLevel); await sut.PerformExecuteAsync(null); VerifyServerNotTouched(); } @@ -36,7 +39,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe [Test] public async Task Does_Not_Execute_When_Application_Url_Is_Not_Available() { - var sut = CreateTouchServerTask(applicationUrl: string.Empty); + TouchServerTask sut = CreateTouchServerTask(applicationUrl: string.Empty); await sut.PerformExecuteAsync(null); VerifyServerNotTouched(); } @@ -44,15 +47,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe [Test] public async Task Executes_And_Touches_Server() { - var sut = CreateTouchServerTask(); + TouchServerTask sut = CreateTouchServerTask(); await sut.PerformExecuteAsync(null); VerifyServerTouched(); } - private TouchServerTask CreateTouchServerTask(RuntimeLevel runtimeLevel = RuntimeLevel.Run, string applicationUrl = _applicationUrl) + private TouchServerTask CreateTouchServerTask(RuntimeLevel runtimeLevel = RuntimeLevel.Run, string applicationUrl = ApplicationUrl) { var mockRequestAccessor = new Mock(); - mockRequestAccessor.Setup(x => x.GetApplicationUrl()).Returns(!string.IsNullOrEmpty(applicationUrl) ? new Uri(_applicationUrl) : null); + mockRequestAccessor.Setup(x => x.GetApplicationUrl()).Returns(!string.IsNullOrEmpty(applicationUrl) ? new Uri(ApplicationUrl) : null); var mockRunTimeState = new Mock(); mockRunTimeState.SetupGet(x => x.Level).Returns(runtimeLevel); @@ -60,7 +63,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe var mockLogger = new Mock>(); _mockServerRegistrationService = new Mock(); - _mockServerRegistrationService.SetupGet(x => x.CurrentServerIdentity).Returns(_serverIdentity); + _mockServerRegistrationService.SetupGet(x => x.CurrentServerIdentity).Returns(ServerIdentity); var settings = new GlobalSettings { @@ -70,28 +73,24 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe } }; - return new TouchServerTask(mockRunTimeState.Object, _mockServerRegistrationService.Object, mockRequestAccessor.Object, - mockLogger.Object, Options.Create(settings)); + return new TouchServerTask( + mockRunTimeState.Object, + _mockServerRegistrationService.Object, + mockRequestAccessor.Object, + mockLogger.Object, + Options.Create(settings)); } - private void VerifyServerNotTouched() - { - VerifyServerTouchedTimes(Times.Never()); - } + private void VerifyServerNotTouched() => VerifyServerTouchedTimes(Times.Never()); - private void VerifyServerTouched() - { - VerifyServerTouchedTimes(Times.Once()); - } + private void VerifyServerTouched() => VerifyServerTouchedTimes(Times.Once()); - private void VerifyServerTouchedTimes(Times times) - { - _mockServerRegistrationService - .Verify(x => x.TouchServer( - It.Is(y => y == _applicationUrl), - It.Is(y => y == _serverIdentity), - It.Is(y => y == _staleServerTimeout)), - times); - } + private void VerifyServerTouchedTimes(Times times) => _mockServerRegistrationService + .Verify( + x => x.TouchServer( + It.Is(y => y == ApplicationUrl), + It.Is(y => y == ServerIdentity), + It.Is(y => y == _staleServerTimeout)), + times); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs index 98666ece2f..6024d50ce3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.IO; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -15,12 +18,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices public class TempFileCleanupTests { private Mock _mockIOHelper; - private string _testPath = Path.Combine(TestContext.CurrentContext.TestDirectory.Split("bin")[0], "App_Data", "TEMP"); + private readonly string _testPath = Path.Combine(TestContext.CurrentContext.TestDirectory.Split("bin")[0], "App_Data", "TEMP"); [Test] public async Task Does_Not_Execute_When_Not_Main_Dom() { - var sut = CreateTempFileCleanup(isMainDom: false); + TempFileCleanup sut = CreateTempFileCleanup(isMainDom: false); await sut.PerformExecuteAsync(null); VerifyFilesNotCleaned(); } @@ -28,7 +31,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices [Test] public async Task Executes_And_Cleans_Files() { - var sut = CreateTempFileCleanup(); + TempFileCleanup sut = CreateTempFileCleanup(); await sut.PerformExecuteAsync(null); VerifyFilesCleaned(); } @@ -50,19 +53,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices return new TempFileCleanup(_mockIOHelper.Object, mockMainDom.Object, mockLogger.Object); } - private void VerifyFilesNotCleaned() - { - VerifyFilesCleaned(Times.Never()); - } + private void VerifyFilesNotCleaned() => VerifyFilesCleaned(Times.Never()); - private void VerifyFilesCleaned() - { - VerifyFilesCleaned(Times.Once()); - } + private void VerifyFilesCleaned() => VerifyFilesCleaned(Times.Once()); - private void VerifyFilesCleaned(Times times) - { - _mockIOHelper.Verify(x => x.CleanFolder(It.Is(y => y.FullName == _testPath), It.IsAny()), times); - } + private void VerifyFilesCleaned(Times times) => _mockIOHelper.Verify(x => x.CleanFolder(It.Is(y => y.FullName == _testPath), It.IsAny()), times); } } From ff03cd6a03559a236532740285e9f37a8d1f3571 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 5 Dec 2020 11:12:55 +0100 Subject: [PATCH 54/63] Adhered to linting rules in common unit test model builders. --- .../Builders/AuditEntryBuilder.cs | 70 +++++++++++-- .../Builders/BuilderBase.cs | 3 + .../Builders/ChildBuilderBase.cs | 14 +-- .../Builders/ConfigurationEditorBuilder.cs | 12 ++- .../Builders/ContentBuilder.cs | 99 ++++++++++--------- .../Builders/ContentItemSaveBuilder.cs | 11 ++- .../Builders/ContentPropertyBasicBuilder.cs | 7 +- .../Builders/ContentTypeBaseBuilder.cs | 17 ++-- .../Builders/ContentTypeBuilder.cs | 37 +++---- .../Builders/ContentTypeSortBuilder.cs | 8 +- .../Builders/ContentVariantSaveBuilder.cs | 25 ++++- .../Builders/DataEditorBuilder.cs | 20 ++-- .../Builders/DataTypeBuilder.cs | 27 +++-- .../Builders/DataValueEditorBuilder.cs | 9 +- .../Builders/DictionaryItemBuilder.cs | 13 ++- .../Builders/DictionaryTranslationBuilder.cs | 25 +++-- .../Builders/DocumentEntitySlimBuilder.cs | 14 ++- .../Builders/EntitySlimBuilder.cs | 3 + .../Builders/Extensions/BuilderExtensions.cs | 3 + .../ContentItemSaveBuilderExtensions.cs | 21 ++-- .../ContentTypeBuilderExtensions.cs | 23 ++--- .../Builders/Extensions/StringExtensions.cs | 5 +- .../Builders/GenericCollectionBuilder.cs | 11 ++- .../Builders/GenericDictionaryBuilder.cs | 14 ++- .../Builders/Interfaces/IAccountBuilder.cs | 5 +- .../Builders/Interfaces/IBuildContentTypes.cs | 5 +- .../Interfaces/IBuildPropertyGroups.cs | 5 +- .../Interfaces/IBuildPropertyTypes.cs | 5 +- .../Builders/Interfaces/IWithAliasBuilder.cs | 5 +- .../Interfaces/IWithCreateDateBuilder.cs | 3 + .../Interfaces/IWithCreatorIdBuilder.cs | 3 + .../Interfaces/IWithCultureInfoBuilder.cs | 5 +- .../Interfaces/IWithDeleteDateBuilder.cs | 3 + .../Interfaces/IWithDescriptionBuilder.cs | 5 +- .../Builders/Interfaces/IWithEmailBuilder.cs | 5 +- .../IWithFailedPasswordAttemptsBuilder.cs | 5 +- .../Builders/Interfaces/IWithIconBuilder.cs | 5 +- .../Builders/Interfaces/IWithIdBuilder.cs | 3 + .../Interfaces/IWithIsApprovedBuilder.cs | 5 +- .../Interfaces/IWithIsContainerBuilder.cs | 5 +- .../Interfaces/IWithIsLockedOutBuilder.cs | 5 +- .../Builders/Interfaces/IWithKeyBuilder.cs | 3 + .../Interfaces/IWithLastLoginDateBuilder.cs | 3 + .../IWithLastPasswordChangeDateBuilder.cs | 3 + .../Builders/Interfaces/IWithLevelBuilder.cs | 5 +- .../Builders/Interfaces/IWithLoginBuilder.cs | 2 +- .../Builders/Interfaces/IWithNameBuilder.cs | 5 +- .../IWithParentContentTypeBuilder.cs | 3 + .../Interfaces/IWithParentIdBuilder.cs | 3 + .../Builders/Interfaces/IWithPathBuilder.cs | 5 +- .../IWithPropertyTypeIdsIncrementingFrom.cs | 5 +- .../Interfaces/IWithPropertyValues.cs | 3 + .../Interfaces/IWithSortOrderBuilder.cs | 5 +- .../Interfaces/IWithSupportsPublishing.cs | 3 + .../Interfaces/IWithThumbnailBuilder.cs | 5 +- .../Interfaces/IWithTrashedBuilder.cs | 5 +- .../Interfaces/IWithUpdateDateBuilder.cs | 3 + .../Builders/LanguageBuilder.cs | 19 ++-- .../Builders/MacroBuilder.cs | 9 +- .../Builders/MacroPropertyBuilder.cs | 12 ++- .../Builders/MediaBuilder.cs | 66 ++++++------- .../Builders/MediaTypeBuilder.cs | 35 +++---- .../Builders/MemberBuilder.cs | 43 ++++---- .../Builders/MemberGroupBuilder.cs | 14 ++- .../Builders/MemberTypeBuilder.cs | 27 ++--- .../Builders/PropertyBuilder.cs | 11 ++- .../Builders/PropertyGroupBuilder.cs | 20 ++-- .../Builders/PropertyTypeBuilder.cs | 26 +++-- .../Builders/RelationBuilder.cs | 11 ++- .../Builders/RelationTypeBuilder.cs | 23 +++-- .../Builders/StylesheetBuilder.cs | 3 + .../Builders/TemplateBuilder.cs | 23 +++-- .../Builders/TreeBuilder.cs | 5 +- .../Builders/UserBuilder.cs | 40 ++++---- .../Builders/UserGroupBuilder.cs | 39 ++++---- .../Builders/XmlDocumentBuilder.cs | 5 +- 76 files changed, 652 insertions(+), 408 deletions(-) diff --git a/src/Umbraco.Tests.Common/Builders/AuditEntryBuilder.cs b/src/Umbraco.Tests.Common/Builders/AuditEntryBuilder.cs index 6efdc8aca8..447aab3fe9 100644 --- a/src/Umbraco.Tests.Common/Builders/AuditEntryBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/AuditEntryBuilder.cs @@ -1,5 +1,7 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; -using System.Globalization; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; @@ -7,7 +9,8 @@ namespace Umbraco.Tests.Common.Builders { public class AuditEntryBuilder : AuditEntryBuilder { - public AuditEntryBuilder() : base(null) + public AuditEntryBuilder() + : base(null) { } } @@ -34,10 +37,59 @@ namespace Umbraco.Tests.Common.Builders private DateTime? _eventDateUtc = null; private int? _performingUserId = null; - public AuditEntryBuilder(TParent parentBuilder) : base(parentBuilder) + public AuditEntryBuilder(TParent parentBuilder) + : base(parentBuilder) { } + public AuditEntryBuilder WithAffectedDetails(string affectedDetails) + { + _affectedDetails = affectedDetails; + return this; + } + + public AuditEntryBuilder WithAffectedUserId(int affectedUserId) + { + _affectedUserId = affectedUserId; + return this; + } + + public AuditEntryBuilder WithEventDetails(string eventDetails) + { + _eventDetails = eventDetails; + return this; + } + + public AuditEntryBuilder WithEventType(string eventType) + { + _eventType = eventType; + return this; + } + + public AuditEntryBuilder WithPerformingDetails(string performingDetails) + { + _performingDetails = performingDetails; + return this; + } + + public AuditEntryBuilder WithPerformingIp(string performingIp) + { + _performingIp = performingIp; + return this; + } + + public AuditEntryBuilder WithEventDate(DateTime eventDateUtc) + { + _eventDateUtc = eventDateUtc; + return this; + } + + public AuditEntryBuilder WithPerformingUserId(int performingUserId) + { + _performingUserId = performingUserId; + return this; + } + DateTime? IWithCreateDateBuilder.CreateDate { get => _createDate; @@ -68,22 +120,20 @@ namespace Umbraco.Tests.Common.Builders set => _updateDate = value; } - - public override IAuditEntry Build() { var id = _id ?? 0; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; - var deleteDate = _deleteDate ?? null; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; + DateTime? deleteDate = _deleteDate ?? null; var affectedDetails = _affectedDetails ?? Guid.NewGuid().ToString(); var affectedUserId = _affectedUserId ?? -1; var eventDetails = _eventDetails ?? Guid.NewGuid().ToString(); var eventType = _eventType ?? "umbraco/user"; var performingDetails = _performingDetails ?? Guid.NewGuid().ToString(); var performingIp = _performingIp ?? "127.0.0.1"; - var eventDateUtc = _eventDateUtc ?? DateTime.UtcNow; + DateTime eventDateUtc = _eventDateUtc ?? DateTime.UtcNow; var performingUserId = _performingUserId ?? -1; return new AuditEntry diff --git a/src/Umbraco.Tests.Common/Builders/BuilderBase.cs b/src/Umbraco.Tests.Common/Builders/BuilderBase.cs index bb8255bbc0..102e5eaf05 100644 --- a/src/Umbraco.Tests.Common/Builders/BuilderBase.cs +++ b/src/Umbraco.Tests.Common/Builders/BuilderBase.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders diff --git a/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs b/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs index fcd9691e1f..feb87f9556 100644 --- a/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs +++ b/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs @@ -1,18 +1,14 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + namespace Umbraco.Tests.Common.Builders { public abstract class ChildBuilderBase : BuilderBase { private readonly TParent _parentBuilder; - protected ChildBuilderBase(TParent parentBuilder) - { - _parentBuilder = parentBuilder; - } - - public TParent Done() - { - return _parentBuilder; - } + protected ChildBuilderBase(TParent parentBuilder) => _parentBuilder = parentBuilder; + public TParent Done() => _parentBuilder; } } diff --git a/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs b/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs index f80529ccab..5c9459bf47 100644 --- a/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs @@ -1,7 +1,8 @@ -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; +// Copyright (c) Umbraco. +// See LICENSE for more details. +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Tests.Common.Builders { @@ -9,7 +10,8 @@ namespace Umbraco.Tests.Common.Builders { private IDictionary _defaultConfiguration; - public ConfigurationEditorBuilder(TParent parentBuilder) : base(parentBuilder) + public ConfigurationEditorBuilder(TParent parentBuilder) + : base(parentBuilder) { } @@ -21,7 +23,7 @@ namespace Umbraco.Tests.Common.Builders public override IConfigurationEditor Build() { - var defaultConfiguration = _defaultConfiguration ?? new Dictionary(); + IDictionary defaultConfiguration = _defaultConfiguration ?? new Dictionary(); return new ConfigurationEditor() { diff --git a/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs index 8cd95e8baf..bd7e3b5b3f 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs @@ -1,11 +1,14 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.Globalization; -using Umbraco.Core.Models; -using Umbraco.Tests.Common.Builders.Interfaces; -using Umbraco.Tests.Common.Builders.Extensions; -using Umbraco.Tests.Testing; using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; +using Umbraco.Tests.Testing; namespace Umbraco.Tests.Common.Builders { @@ -44,7 +47,7 @@ namespace Umbraco.Tests.Common.Builders private bool? _trashed; private CultureInfo _cultureInfo; private IContentType _contentType; - private IDictionary _cultureNames = new Dictionary(); + private readonly IDictionary _cultureNames = new Dictionary(); private object _propertyValues; private string _propertyValuesCulture; private string _propertyValuesSegment; @@ -105,11 +108,11 @@ namespace Umbraco.Tests.Common.Builders { var id = _id ?? 0; var versionId = _versionId ?? 0; - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var parentId = _parentId ?? -1; - var parent = _parent ?? null; - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + IContent parent = _parent ?? null; + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var name = _name ?? Guid.NewGuid().ToString(); var creatorId = _creatorId ?? 0; var level = _level ?? 1; @@ -126,7 +129,7 @@ namespace Umbraco.Tests.Common.Builders throw new InvalidOperationException("A content item cannot be constructed without providing a content type. Use AddContentType() or WithContentType()."); } - var contentType = _contentType ?? _contentTypeBuilder.Build(); + IContentType contentType = _contentType ?? _contentTypeBuilder.Build(); Content content; if (parent != null) @@ -149,7 +152,7 @@ namespace Umbraco.Tests.Common.Builders content.SortOrder = sortOrder; content.Trashed = trashed; - foreach (var cultureName in _cultureNames) + foreach (KeyValuePair cultureName in _cultureNames) { content.SetCultureName(cultureName.Value, cultureName.Key); } @@ -158,8 +161,8 @@ namespace Umbraco.Tests.Common.Builders { if (_propertyDataBuilder != null) { - var propertyData = _propertyDataBuilder.Build(); - foreach (var keyValuePair in propertyData) + IDictionary propertyData = _propertyDataBuilder.Build(); + foreach (KeyValuePair keyValuePair in propertyData) { content.SetValue(keyValuePair.Key, keyValuePair.Value); } @@ -175,47 +178,44 @@ namespace Umbraco.Tests.Common.Builders return content; } - public static Content CreateBasicContent(IContentType contentType, int id = 0) - { - return new ContentBuilder() + public static Content CreateBasicContent(IContentType contentType, int id = 0) => + new ContentBuilder() .WithId(id) .WithContentType(contentType) .WithName("Home") .Build(); - } - public static Content CreateSimpleContent(IContentType contentType) - { - return new ContentBuilder() + public static Content CreateSimpleContent(IContentType contentType) => + new ContentBuilder() .WithContentType(contentType) .WithName("Home") .WithPropertyValues(new - { - title = "Welcome to our Home page", - bodyText = "This is the welcome message on the first page", - author = "John Doe" - }) + { + title = "Welcome to our Home page", + bodyText = "This is the welcome message on the first page", + author = "John Doe" + }) .Build(); - } - public static Content CreateSimpleContent(IContentType contentType, string name, int parentId = -1, string culture = null, string segment = null) - { - return new ContentBuilder() + public static Content CreateSimpleContent(IContentType contentType, string name, int parentId = -1, string culture = null, string segment = null) => + new ContentBuilder() .WithContentType(contentType) .WithName(name) .WithParentId(parentId) - .WithPropertyValues(new + .WithPropertyValues( + new { title = "Welcome to our Home page", bodyText = "This is the welcome message on the first page", author = "John Doe" - }, culture, segment) + }, + culture, + segment) .Build(); - } public static Content CreateSimpleContent(IContentType contentType, string name, IContent parent, string culture = null, string segment = null, bool setPropertyValues = true) { - var builder = new ContentBuilder() + ContentBuilder builder = new ContentBuilder() .WithContentType(contentType) .WithName(name) .WithParent(parent); @@ -228,15 +228,18 @@ namespace Umbraco.Tests.Common.Builders if (setPropertyValues) { - builder = builder.WithPropertyValues(new - { - title = name + " Subpage", - bodyText = "This is a subpage", - author = "John Doe" - }, culture, segment); + builder = builder.WithPropertyValues( + new + { + title = name + " Subpage", + bodyText = "This is a subpage", + author = "John Doe" + }, + culture, + segment); } - var content = builder.Build(); + Content content = builder.Build(); content.ResetDirtyProperties(false); @@ -247,7 +250,7 @@ namespace Umbraco.Tests.Common.Builders { var list = new List(); - for (int i = 0; i < amount; i++) + for (var i = 0; i < amount; i++) { var name = "Textpage No-" + i; var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 }; @@ -269,14 +272,15 @@ namespace Umbraco.Tests.Common.Builders return list; } - public static Content CreateTextpageContent(IContentType contentType, string name, int parentId) - { - return new ContentBuilder() + + public static Content CreateTextpageContent(IContentType contentType, string name, int parentId) => + new ContentBuilder() .WithId(0) .WithContentType(contentType) .WithName(name) .WithParentId(parentId) - .WithPropertyValues(new + .WithPropertyValues( + new { title = name + " textpage", bodyText = string.Format("This is a textpage based on the {0} ContentType", contentType.Alias), @@ -284,7 +288,6 @@ namespace Umbraco.Tests.Common.Builders description = "This is the meta description for a textpage" }) .Build(); - } public static IEnumerable CreateMultipleTextpageContent(IContentType contentType, int parentId, int amount) { @@ -293,7 +296,7 @@ namespace Umbraco.Tests.Common.Builders for (var i = 0; i < amount; i++) { var name = "Textpage No-" + i; - var content = new ContentBuilder() + Content content = new ContentBuilder() .WithName(name) .WithParentId(parentId) .WithContentType(contentType) @@ -314,7 +317,7 @@ namespace Umbraco.Tests.Common.Builders public static Content CreateAllTypesContent(IContentType contentType, string name, int parentId) { - var content = new ContentBuilder() + Content content = new ContentBuilder() .WithName(name) .WithParentId(parentId) .WithContentType(contentType) diff --git a/src/Umbraco.Tests.Common/Builders/ContentItemSaveBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentItemSaveBuilder.cs index 13196a038b..5eadd01608 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentItemSaveBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentItemSaveBuilder.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Linq; -using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; using Umbraco.Web.Models.ContentEditing; @@ -29,8 +31,8 @@ namespace Umbraco.Tests.Common.Builders var id = _id ?? 0; var parentId = _parentId ?? -1; var contentTypeAlias = _contentTypeAlias ?? null; - var action = _action ?? ContentSaveAction.Save; - var variants = _variantBuilders.Select(x => x.Build()); + ContentSaveAction action = _action ?? ContentSaveAction.Save; + IEnumerable variants = _variantBuilders.Select(x => x.Build()); return new TestContentItemSave() { @@ -47,6 +49,7 @@ namespace Umbraco.Tests.Common.Builders get => _id; set => _id = value; } + int? IWithParentIdBuilder.ParentId { get => _parentId; diff --git a/src/Umbraco.Tests.Common/Builders/ContentPropertyBasicBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentPropertyBasicBuilder.cs index 35f76bfd6a..f9ad509be4 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentPropertyBasicBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentPropertyBasicBuilder.cs @@ -1,4 +1,7 @@ -using Umbraco.Tests.Common.Builders.Interfaces; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Tests.Common.Builders.Interfaces; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Tests.Common.Builders @@ -31,9 +34,9 @@ namespace Umbraco.Tests.Common.Builders public ContentPropertyBasicBuilder WithValue(object value) { _value = value; - return this; } + int? IWithIdBuilder.Id { get => _id; diff --git a/src/Umbraco.Tests.Common/Builders/ContentTypeBaseBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentTypeBaseBuilder.cs index aab18d70af..0f00cc53d7 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentTypeBaseBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentTypeBaseBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using Umbraco.Core.Models; @@ -26,7 +29,8 @@ namespace Umbraco.Tests.Common.Builders IWithIconBuilder, IWithThumbnailBuilder, IWithTrashedBuilder, - IWithIsContainerBuilder where TParent : IBuildContentTypes + IWithIsContainerBuilder + where TParent : IBuildContentTypes { private int? _id; private Guid? _key; @@ -48,7 +52,8 @@ namespace Umbraco.Tests.Common.Builders protected IShortStringHelper ShortStringHelper => new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); - public ContentTypeBaseBuilder(TParent parentBuilder) : base(parentBuilder) + public ContentTypeBaseBuilder(TParent parentBuilder) + : base(parentBuilder) { } @@ -88,7 +93,7 @@ namespace Umbraco.Tests.Common.Builders protected void BuildPropertyGroups(ContentTypeCompositionBase contentType, IEnumerable propertyGroups) { - foreach (var propertyGroup in propertyGroups) + foreach (PropertyGroup propertyGroup in propertyGroups) { contentType.PropertyGroups.Add(propertyGroup); } @@ -99,7 +104,7 @@ namespace Umbraco.Tests.Common.Builders if (propertyTypeIdsIncrementingFrom.HasValue) { var i = propertyTypeIdsIncrementingFrom.Value; - foreach (var propertyType in contentType.PropertyTypes) + foreach (IPropertyType propertyType in contentType.PropertyTypes) { propertyType.Id = ++i; } @@ -112,12 +117,12 @@ namespace Umbraco.Tests.Common.Builders // and ensure there are no clashes). contentType.Id = seedId; var itemid = seedId + 1; - foreach (var propertyGroup in contentType.PropertyGroups) + foreach (PropertyGroup propertyGroup in contentType.PropertyGroups) { propertyGroup.Id = itemid++; } - foreach (var propertyType in contentType.PropertyTypes) + foreach (IPropertyType propertyType in contentType.PropertyTypes) { propertyType.Id = itemid++; } diff --git a/src/Umbraco.Tests.Common/Builders/ContentTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentTypeBuilder.cs index fca148f542..ed888eab19 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentTypeBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Linq; using Umbraco.Core; @@ -12,21 +15,23 @@ namespace Umbraco.Tests.Common.Builders IWithPropertyTypeIdsIncrementingFrom, IBuildPropertyTypes { - private List> _propertyGroupBuilders = new List>(); - private List> _noGroupPropertyTypeBuilders = new List>(); - private List _templateBuilders = new List(); - private List _allowedContentTypeBuilders = new List(); + private readonly List> _propertyGroupBuilders = new List>(); + private readonly List> _noGroupPropertyTypeBuilders = new List>(); + private readonly List _templateBuilders = new List(); + private readonly List _allowedContentTypeBuilders = new List(); private int? _propertyTypeIdsIncrementingFrom; private int? _defaultTemplateId; private ContentVariation? _contentVariation; private PropertyTypeCollection _propertyTypeCollection; - public ContentTypeBuilder() : base(null) + public ContentTypeBuilder() + : base(null) { } - public ContentTypeBuilder(ContentBuilder parentBuilder) : base(parentBuilder) + public ContentTypeBuilder(ContentBuilder parentBuilder) + : base(parentBuilder) { } @@ -78,10 +83,10 @@ namespace Umbraco.Tests.Common.Builders public override IContentType Build() { - var contentVariation = _contentVariation ?? ContentVariation.Nothing; + ContentVariation contentVariation = _contentVariation ?? ContentVariation.Nothing; ContentType contentType; - var parent = GetParent(); + IContentTypeComposition parent = GetParent(); if (parent != null) { contentType = new ContentType(ShortStringHelper, (IContentType)parent, GetAlias()); @@ -113,7 +118,7 @@ namespace Umbraco.Tests.Common.Builders if (_propertyTypeCollection != null) { - var propertyGroup = new PropertyGroupBuilder() + PropertyGroup propertyGroup = new PropertyGroupBuilder() .WithName("Content") .WithSortOrder(1) .WithPropertyTypeCollection(_propertyTypeCollection) @@ -153,7 +158,7 @@ namespace Umbraco.Tests.Common.Builders public static ContentType CreateSimpleContentType2(string alias, string name, IContentType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") { - var builder = CreateSimpleContentTypeHelper(alias, name, parent, randomizeAliases: randomizeAliases, propertyGroupName: propertyGroupName); + ContentTypeBuilder builder = CreateSimpleContentTypeHelper(alias, name, parent, randomizeAliases: randomizeAliases, propertyGroupName: propertyGroupName); builder.AddPropertyType() .WithAlias(RandomAlias("gen", randomizeAliases)) @@ -167,14 +172,12 @@ namespace Umbraco.Tests.Common.Builders return (ContentType)builder.Build(); } - public static ContentType CreateSimpleContentType(string alias = null, string name = null, IContentType parent = null, PropertyTypeCollection propertyTypeCollection = null, bool randomizeAliases = false, string propertyGroupName = "Content", bool mandatoryProperties = false, int defaultTemplateId = 0) - { - return (ContentType)CreateSimpleContentTypeHelper(alias, name, parent, propertyTypeCollection, randomizeAliases, propertyGroupName, mandatoryProperties, defaultTemplateId).Build(); - } + public static ContentType CreateSimpleContentType(string alias = null, string name = null, IContentType parent = null, PropertyTypeCollection propertyTypeCollection = null, bool randomizeAliases = false, string propertyGroupName = "Content", bool mandatoryProperties = false, int defaultTemplateId = 0) => + (ContentType)CreateSimpleContentTypeHelper(alias, name, parent, propertyTypeCollection, randomizeAliases, propertyGroupName, mandatoryProperties, defaultTemplateId).Build(); public static ContentTypeBuilder CreateSimpleContentTypeHelper(string alias = null, string name = null, IContentType parent = null, PropertyTypeCollection propertyTypeCollection = null, bool randomizeAliases = false, string propertyGroupName = "Content", bool mandatoryProperties = false, int defaultTemplateId = 0) { - var builder = new ContentTypeBuilder() + ContentTypeBuilder builder = new ContentTypeBuilder() .WithAlias(alias ?? "simple") .WithName(name ?? "Simple Page") .WithParentContentType(parent); @@ -228,9 +231,9 @@ namespace Umbraco.Tests.Common.Builders public static ContentType CreateSimpleTagsContentType(string alias, string name, IContentType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content", int defaultTemplateId = 1) { - var contentType = CreateSimpleContentType(alias, name, parent, randomizeAliases: randomizeAliases, propertyGroupName: propertyGroupName, defaultTemplateId: defaultTemplateId); + ContentType contentType = CreateSimpleContentType(alias, name, parent, randomizeAliases: randomizeAliases, propertyGroupName: propertyGroupName, defaultTemplateId: defaultTemplateId); - var propertyType = new PropertyTypeBuilder() + PropertyType propertyType = new PropertyTypeBuilder() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Tags) .WithValueStorageType(ValueStorageType.Nvarchar) .WithAlias(RandomAlias("tags", randomizeAliases)) diff --git a/src/Umbraco.Tests.Common/Builders/ContentTypeSortBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentTypeSortBuilder.cs index 42c3ebcc1b..b1e3c494f1 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentTypeSortBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentTypeSortBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Extensions; @@ -15,7 +18,8 @@ namespace Umbraco.Tests.Common.Builders private string _alias; private int? _sortOrder; - public ContentTypeSortBuilder() : base(null) + public ContentTypeSortBuilder() + : base(null) { } @@ -44,7 +48,7 @@ namespace Umbraco.Tests.Common.Builders set => _alias = value; } - int? IWithSortOrderBuilder.SortOrder + int? IWithSortOrderBuilder.SortOrder { get => _sortOrder; set => _sortOrder = value; diff --git a/src/Umbraco.Tests.Common/Builders/ContentVariantSaveBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentVariantSaveBuilder.cs index 4b7b4b3d2b..3408e6a244 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentVariantSaveBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentVariantSaveBuilder.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Globalization; using System.Linq; using Umbraco.Tests.Common.Builders.Interfaces; @@ -10,18 +13,30 @@ namespace Umbraco.Tests.Common.Builders IWithNameBuilder, IWithCultureInfoBuilder { - private List>> _propertyBuilders = new List>>(); - + private readonly List>> _propertyBuilders = new List>>(); private string _name; private CultureInfo _cultureInfo; private bool? _save = null; private bool? _publish = null; - public ContentVariantSaveBuilder(TParent parentBuilder) : base(parentBuilder) + public ContentVariantSaveBuilder(TParent parentBuilder) + : base(parentBuilder) { } + public ContentVariantSaveBuilder WithSave(bool save) + { + _save = save; + return this; + } + + public ContentVariantSaveBuilder WithPublish(bool publish) + { + _publish = publish; + return this; + } + public ContentPropertyBasicBuilder> AddProperty() { var builder = new ContentPropertyBasicBuilder>(this); @@ -35,7 +50,7 @@ namespace Umbraco.Tests.Common.Builders var culture = _cultureInfo?.Name ?? null; var save = _save ?? true; var publish = _publish ?? true; - var properties = _propertyBuilders.Select(x => x.Build()); + IEnumerable properties = _propertyBuilders.Select(x => x.Build()); return new ContentVariantSave() { diff --git a/src/Umbraco.Tests.Common/Builders/DataEditorBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataEditorBuilder.cs index f7185d1620..a781c6ccea 100644 --- a/src/Umbraco.Tests.Common/Builders/DataEditorBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DataEditorBuilder.cs @@ -1,7 +1,9 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using Microsoft.Extensions.Logging.Abstractions; using Moq; -using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Strings; @@ -10,11 +12,12 @@ namespace Umbraco.Tests.Common.Builders { public class DataEditorBuilder : ChildBuilderBase { - private ConfigurationEditorBuilder> _explicitConfigurationEditorBuilder; - private DataValueEditorBuilder> _explicitValueEditorBuilder; + private readonly ConfigurationEditorBuilder> _explicitConfigurationEditorBuilder; + private readonly DataValueEditorBuilder> _explicitValueEditorBuilder; private IDictionary _defaultConfiguration; - public DataEditorBuilder(TParent parentBuilder) : base(parentBuilder) + public DataEditorBuilder(TParent parentBuilder) + : base(parentBuilder) { _explicitConfigurationEditorBuilder = new ConfigurationEditorBuilder>(this); _explicitValueEditorBuilder = new DataValueEditorBuilder>(this); @@ -34,17 +37,16 @@ namespace Umbraco.Tests.Common.Builders public override IDataEditor Build() { - var defaultConfiguration = _defaultConfiguration ?? new Dictionary(); - var explicitConfigurationEditor = _explicitConfigurationEditorBuilder.Build(); - var explicitValueEditor = _explicitValueEditorBuilder.Build(); + IDictionary defaultConfiguration = _defaultConfiguration ?? new Dictionary(); + IConfigurationEditor explicitConfigurationEditor = _explicitConfigurationEditorBuilder.Build(); + IDataValueEditor explicitValueEditor = _explicitValueEditorBuilder.Build(); return new DataEditor( NullLoggerFactory.Instance, Mock.Of(), Mock.Of(), Mock.Of(), - Mock.Of() - ) + Mock.Of()) { DefaultConfiguration = defaultConfiguration, ExplicitConfigurationEditor = explicitConfigurationEditor, diff --git a/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs index 3ed7334367..d853b2a5c2 100644 --- a/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -20,7 +23,7 @@ namespace Umbraco.Tests.Common.Builders IWithPathBuilder, IWithSortOrderBuilder { - private DataEditorBuilder _dataEditorBuilder; + private readonly DataEditorBuilder _dataEditorBuilder; private int? _id; private int? _parentId; private Guid? _key; @@ -35,10 +38,7 @@ namespace Umbraco.Tests.Common.Builders private ValueStorageType? _databaseType; private int? _sortOrder; - public DataTypeBuilder() - { - _dataEditorBuilder = new DataEditorBuilder(this); - } + public DataTypeBuilder() => _dataEditorBuilder = new DataEditorBuilder(this); public DataTypeBuilder WithDatabaseType(ValueStorageType databaseType) { @@ -46,25 +46,22 @@ namespace Umbraco.Tests.Common.Builders return this; } - public DataEditorBuilder AddEditor() - { - return _dataEditorBuilder; - } + public DataEditorBuilder AddEditor() => _dataEditorBuilder; public override DataType Build() { - var editor = _dataEditorBuilder.Build(); + Core.PropertyEditors.IDataEditor editor = _dataEditorBuilder.Build(); var parentId = _parentId ?? -1; var id = _id ?? 1; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; - var deleteDate = _deleteDate ?? null; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; + DateTime? deleteDate = _deleteDate ?? null; var name = _name ?? Guid.NewGuid().ToString(); var level = _level ?? 0; var path = _path ?? $"-1,{id}"; var creatorId = _creatorId ?? 1; - var databaseType = _databaseType ?? ValueStorageType.Ntext; + ValueStorageType databaseType = _databaseType ?? ValueStorageType.Ntext; var sortOrder = _sortOrder ?? 0; var serializer = new ConfigurationEditorJsonSerializer(); diff --git a/src/Umbraco.Tests.Common/Builders/DataValueEditorBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataValueEditorBuilder.cs index ecc2649c02..cee0931060 100644 --- a/src/Umbraco.Tests.Common/Builders/DataValueEditorBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DataValueEditorBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Moq; using Umbraco.Core.PropertyEditors; @@ -13,7 +16,8 @@ namespace Umbraco.Tests.Common.Builders private bool? _hideLabel; private string _valueType; - public DataValueEditorBuilder(TParent parentBuilder) : base(parentBuilder) + public DataValueEditorBuilder(TParent parentBuilder) + : base(parentBuilder) { } @@ -52,8 +56,7 @@ namespace Umbraco.Tests.Common.Builders Mock.Of(), Mock.Of(), Mock.Of(), - Mock.Of() - ) + Mock.Of()) { Configuration = configuration, View = view, diff --git a/src/Umbraco.Tests.Common/Builders/DictionaryItemBuilder.cs b/src/Umbraco.Tests.Common/Builders/DictionaryItemBuilder.cs index 206bccba80..9ad4c4178e 100644 --- a/src/Umbraco.Tests.Common/Builders/DictionaryItemBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DictionaryItemBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.Linq; @@ -57,12 +60,12 @@ namespace Umbraco.Tests.Common.Builders public override DictionaryItem Build() { - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; - var deleteDate = _deleteDate ?? null; + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; + DateTime? deleteDate = _deleteDate ?? null; var id = _id ?? 1; - var key = _key ?? Guid.NewGuid(); - var parentId = _parentId ?? null; + Guid key = _key ?? Guid.NewGuid(); + Guid? parentId = _parentId ?? null; var itemKey = _itemKey ?? Guid.NewGuid().ToString(); var result = new DictionaryItem(itemKey) diff --git a/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs b/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs index 444afe6ddb..6029097307 100644 --- a/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; @@ -12,7 +15,7 @@ namespace Umbraco.Tests.Common.Builders IWithDeleteDateBuilder, IWithKeyBuilder { - private LanguageBuilder _languageBuilder; + private readonly LanguageBuilder _languageBuilder; private DateTime? _createDate; private DateTime? _deleteDate; private int? _id; @@ -20,15 +23,11 @@ namespace Umbraco.Tests.Common.Builders private DateTime? _updateDate; private string _value; - public DictionaryTranslationBuilder() : base(null) - { - _languageBuilder = new LanguageBuilder(this); - } + public DictionaryTranslationBuilder() + : base(null) => _languageBuilder = new LanguageBuilder(this); - public DictionaryTranslationBuilder(DictionaryItemBuilder parentBuilder) : base(parentBuilder) - { - _languageBuilder = new LanguageBuilder(this); - } + public DictionaryTranslationBuilder(DictionaryItemBuilder parentBuilder) + : base(parentBuilder) => _languageBuilder = new LanguageBuilder(this); public LanguageBuilder AddLanguage() => _languageBuilder; @@ -40,11 +39,11 @@ namespace Umbraco.Tests.Common.Builders public override IDictionaryTranslation Build() { - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; - var deleteDate = _deleteDate ?? null; + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; + DateTime? deleteDate = _deleteDate ?? null; var id = _id ?? 1; - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var result = new DictionaryTranslation( _languageBuilder.Build(), diff --git a/src/Umbraco.Tests.Common/Builders/DocumentEntitySlimBuilder.cs b/src/Umbraco.Tests.Common/Builders/DocumentEntitySlimBuilder.cs index 84ed5fec17..490f94f789 100644 --- a/src/Umbraco.Tests.Common/Builders/DocumentEntitySlimBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DocumentEntitySlimBuilder.cs @@ -1,4 +1,8 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; +using System.Collections.Generic; using Umbraco.Core.Models.Entities; using Umbraco.Tests.Common.Builders.Interfaces; @@ -75,9 +79,9 @@ namespace Umbraco.Tests.Common.Builders public override DocumentEntitySlim Build() { var id = _id ?? 1; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var name = _name ?? Guid.NewGuid().ToString(); var creatorId = _creatorId ?? 1; var level = _level ?? 1; @@ -111,8 +115,8 @@ namespace Umbraco.Tests.Common.Builders if (_additionalDataBuilder != null) { - var additionalData = _additionalDataBuilder.Build(); - foreach (var kvp in additionalData) + IDictionary additionalData = _additionalDataBuilder.Build(); + foreach (KeyValuePair kvp in additionalData) { documentEntitySlim.AdditionalData.Add(kvp.Key, kvp.Value); } diff --git a/src/Umbraco.Tests.Common/Builders/EntitySlimBuilder.cs b/src/Umbraco.Tests.Common/Builders/EntitySlimBuilder.cs index 844c765a9d..ce35bb21b1 100644 --- a/src/Umbraco.Tests.Common/Builders/EntitySlimBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/EntitySlimBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using Umbraco.Core.Models.Entities; using Umbraco.Tests.Common.Builders.Interfaces; diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs index 1af2dc5344..c93b150647 100644 --- a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs +++ b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Globalization; using Umbraco.Core.Models; diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/ContentItemSaveBuilderExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/ContentItemSaveBuilderExtensions.cs index c402049745..04e95bd8a4 100644 --- a/src/Umbraco.Tests.Common/Builders/Extensions/ContentItemSaveBuilderExtensions.cs +++ b/src/Umbraco.Tests.Common/Builders/Extensions/ContentItemSaveBuilderExtensions.cs @@ -1,11 +1,12 @@ -using System.Linq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using Umbraco.Core.Models; namespace Umbraco.Tests.Common.Builders.Extensions { public static class ContentItemSaveBuilderExtensions { - public static ContentItemSaveBuilder WithContent(this ContentItemSaveBuilder builder, IContent content) { builder.WithId(content.Id); @@ -13,43 +14,39 @@ namespace Umbraco.Tests.Common.Builders.Extensions if (content.CultureInfos.Count == 0) { - var variantBuilder = builder.AddVariant(); + ContentVariantSaveBuilder variantBuilder = builder.AddVariant(); variantBuilder.WithName(content.Name); - foreach (var contentProperty in content.Properties) + foreach (IProperty contentProperty in content.Properties) { AddInvariantProperty(variantBuilder, contentProperty); } } else { - foreach (var contentCultureInfos in content.CultureInfos) + foreach (ContentCultureInfos contentCultureInfos in content.CultureInfos) { - var variantBuilder = builder.AddVariant(); + ContentVariantSaveBuilder variantBuilder = builder.AddVariant(); variantBuilder.WithName(contentCultureInfos.Name); variantBuilder.WithCultureInfo(contentCultureInfos.Culture); - foreach (var contentProperty in content.Properties) + foreach (IProperty contentProperty in content.Properties) { AddInvariantProperty(variantBuilder, contentProperty); } } } - - return builder; } - private static void AddInvariantProperty(ContentVariantSaveBuilder variantBuilder, IProperty contentProperty) - { + private static void AddInvariantProperty(ContentVariantSaveBuilder variantBuilder, IProperty contentProperty) => variantBuilder .AddProperty() .WithId(contentProperty.Id) .WithAlias(contentProperty.Alias) .WithValue(contentProperty.GetValue()) .Done(); - } } } diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/ContentTypeBuilderExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/ContentTypeBuilderExtensions.cs index f39b09e899..4ff0bae60c 100644 --- a/src/Umbraco.Tests.Common/Builders/Extensions/ContentTypeBuilderExtensions.cs +++ b/src/Umbraco.Tests.Common/Builders/Extensions/ContentTypeBuilderExtensions.cs @@ -1,13 +1,15 @@ -using Umbraco.Core; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core; using Umbraco.Core.Models; namespace Umbraco.Tests.Common.Builders.Extensions { public static class ContentTypeBuilderExtensions { - public static ContentType BuildSimpleContentType(this ContentTypeBuilder builder) - { - return (ContentType)builder + public static ContentType BuildSimpleContentType(this ContentTypeBuilder builder) => + (ContentType)builder .WithId(10) .WithAlias("textPage") .WithName("Text Page") @@ -64,11 +66,9 @@ namespace Umbraco.Tests.Common.Builders.Extensions .WithSortOrder(9) .Done() .Build(); - } - public static MediaType BuildImageMediaType(this MediaTypeBuilder builder) - { - return (MediaType)builder + public static MediaType BuildImageMediaType(this MediaTypeBuilder builder) => + (MediaType)builder .WithId(10) .WithAlias(Constants.Conventions.MediaTypes.Image) .WithName("Image") @@ -77,11 +77,9 @@ namespace Umbraco.Tests.Common.Builders.Extensions .WithPropertyTypeIdsIncrementingFrom(200) .WithMediaPropertyGroup() .Build(); - } - public static MemberType BuildSimpleMemberType(this MemberTypeBuilder builder) - { - return (MemberType)builder + public static MemberType BuildSimpleMemberType(this MemberTypeBuilder builder) => + (MemberType)builder .WithId(10) .WithAlias("memberType") .WithName("Member type") @@ -105,6 +103,5 @@ namespace Umbraco.Tests.Common.Builders.Extensions .WithMemberCanEditProperty("title", true) .WithMemberCanViewProperty("bodyText", true) .Build(); - } } } diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs index b426cabaa6..e0fef2647f 100644 --- a/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs +++ b/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Extensions +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Extensions { public static class StringExtensions { diff --git a/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs b/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs index 0c45f6a599..7fc58e4961 100644 --- a/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Linq; @@ -8,14 +11,12 @@ namespace Umbraco.Tests.Common.Builders { private readonly IList _collection; - public GenericCollectionBuilder(TBuilder parentBuilder) : base(parentBuilder) - { - _collection = new List(); - } + public GenericCollectionBuilder(TBuilder parentBuilder) + : base(parentBuilder) => _collection = new List(); public override IEnumerable Build() { - var collection = _collection?.ToList() ?? Enumerable.Empty(); + IEnumerable collection = _collection?.ToList() ?? Enumerable.Empty(); return collection; } diff --git a/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs b/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs index f4cb7c6a30..3d7823b612 100644 --- a/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; namespace Umbraco.Tests.Common.Builders @@ -7,17 +10,12 @@ namespace Umbraco.Tests.Common.Builders { private readonly IDictionary _dictionary; - public GenericDictionaryBuilder(TBuilder parentBuilder) : base(parentBuilder) - { - _dictionary = new Dictionary(); - } + public GenericDictionaryBuilder(TBuilder parentBuilder) + : base(parentBuilder) => _dictionary = new Dictionary(); - public override IDictionary Build() - { - return _dictionary == null + public override IDictionary Build() => _dictionary == null ? new Dictionary() : new Dictionary(_dictionary); - } public GenericDictionaryBuilder WithKeyValue(TKey key, TValue value) { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IAccountBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IAccountBuilder.cs index 77fdd30547..1249209418 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IAccountBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IAccountBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IAccountBuilder : IWithLoginBuilder, IWithEmailBuilder, diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildContentTypes.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildContentTypes.cs index 428613dc5d..740da59a10 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildContentTypes.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildContentTypes.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IBuildContentTypes { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyGroups.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyGroups.cs index 0391061a84..756aa19744 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyGroups.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyGroups.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IBuildPropertyGroups { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyTypes.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyTypes.cs index e0eb9e19d5..91a7c10041 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyTypes.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildPropertyTypes.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IBuildPropertyTypes { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithAliasBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithAliasBuilder.cs index 78bbbddec9..cf4db5382b 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithAliasBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithAliasBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithAliasBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreateDateBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreateDateBuilder.cs index 47acaa9a52..46745c4428 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreateDateBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreateDateBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs index ae7712cf9e..0f3e11a4de 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCultureInfoBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCultureInfoBuilder.cs index a60fe3c23c..bcb74c5c94 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCultureInfoBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCultureInfoBuilder.cs @@ -1,4 +1,7 @@ -using System.Globalization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Globalization; namespace Umbraco.Tests.Common.Builders.Interfaces { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDeleteDateBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDeleteDateBuilder.cs index 0fdeb6d69d..25042be231 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDeleteDateBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDeleteDateBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs index 1a155073b3..98d14d81bc 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithDescriptionBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithEmailBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithEmailBuilder.cs index cb3be57e62..4dd5708aaf 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithEmailBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithEmailBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithEmailBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithFailedPasswordAttemptsBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithFailedPasswordAttemptsBuilder.cs index d39647b28e..7669a7609e 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithFailedPasswordAttemptsBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithFailedPasswordAttemptsBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithFailedPasswordAttemptsBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs index 5de5224e18..a58c8c554b 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithIconBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIdBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIdBuilder.cs index c13343df15..8f99388086 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIdBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIdBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithIdBuilder diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsApprovedBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsApprovedBuilder.cs index c27c34d3a2..2645bc8071 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsApprovedBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsApprovedBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithIsApprovedBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsContainerBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsContainerBuilder.cs index 7daee8afb9..a74f2b658f 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsContainerBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsContainerBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithIsContainerBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsLockedOutBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsLockedOutBuilder.cs index 55577ed312..d10db7d881 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsLockedOutBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIsLockedOutBuilder.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; namespace Umbraco.Tests.Common.Builders.Interfaces { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithKeyBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithKeyBuilder.cs index a71bd2d114..a709dff734 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithKeyBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithKeyBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastLoginDateBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastLoginDateBuilder.cs index 0355786927..9b969a210e 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastLoginDateBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastLoginDateBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastPasswordChangeDateBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastPasswordChangeDateBuilder.cs index 833da022d4..ffd7019404 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastPasswordChangeDateBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLastPasswordChangeDateBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs index dc6ee239ab..51d08e9143 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithLevelBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLoginBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLoginBuilder.cs index 5c11dd7d05..5b844869f0 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLoginBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLoginBuilder.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithLoginBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithNameBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithNameBuilder.cs index d2ccb8dbbc..17962dc678 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithNameBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithNameBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithNameBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentContentTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentContentTypeBuilder.cs index a9812654ef..c5357164a5 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentContentTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentContentTypeBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using Umbraco.Core.Models; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs index 33d13b7ef1..edba880af8 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithParentIdBuilder diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs index ed632c4e7d..9fb99bc825 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithPathBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyTypeIdsIncrementingFrom.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyTypeIdsIncrementingFrom.cs index 6e12803e5c..215b0d3791 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyTypeIdsIncrementingFrom.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyTypeIdsIncrementingFrom.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithPropertyTypeIdsIncrementingFrom { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyValues.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyValues.cs index c5a6c35724..06ac06070c 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyValues.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPropertyValues.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithPropertyValues diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs index 3202c243fb..8b23fd2b95 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithSortOrderBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSupportsPublishing.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSupportsPublishing.cs index 03bf74aa06..4b9f9e805b 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSupportsPublishing.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSupportsPublishing.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs index ce5b10e274..59b4fbff81 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithThumbnailBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs index 119e6a6e52..fe155aa07a 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.Common.Builders.Interfaces +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.Common.Builders.Interfaces { public interface IWithTrashedBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithUpdateDateBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithUpdateDateBuilder.cs index 80a5aa4f61..9c01286179 100644 --- a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithUpdateDateBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithUpdateDateBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; namespace Umbraco.Tests.Common.Builders.Interfaces diff --git a/src/Umbraco.Tests.Common/Builders/LanguageBuilder.cs b/src/Umbraco.Tests.Common/Builders/LanguageBuilder.cs index 4cd924fa3a..653d729dfd 100644 --- a/src/Umbraco.Tests.Common/Builders/LanguageBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/LanguageBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Globalization; using Umbraco.Core.Configuration.Models; @@ -8,7 +11,8 @@ namespace Umbraco.Tests.Common.Builders { public class LanguageBuilder : LanguageBuilder { - public LanguageBuilder() : base(null) + public LanguageBuilder() + : base(null) { } } @@ -33,7 +37,8 @@ namespace Umbraco.Tests.Common.Builders private Guid? _key; private DateTime? _updateDate; - public LanguageBuilder(TParent parentBuilder) : base(parentBuilder) + public LanguageBuilder(TParent parentBuilder) + : base(parentBuilder) { } @@ -63,13 +68,13 @@ namespace Umbraco.Tests.Common.Builders public override ILanguage Build() { - var cultureInfo = _cultureInfo ?? CultureInfo.GetCultureInfo("en-US"); + CultureInfo cultureInfo = _cultureInfo ?? CultureInfo.GetCultureInfo("en-US"); var cultureName = _cultureName ?? cultureInfo.EnglishName; var globalSettings = new GlobalSettings { DefaultUILanguage = cultureInfo.Name }; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; - var deleteDate = _deleteDate ?? null; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; + DateTime? deleteDate = _deleteDate ?? null; var fallbackLanguageId = _fallbackLanguageId ?? null; var isDefault = _isDefault ?? false; var isMandatory = _isMandatory ?? false; diff --git a/src/Umbraco.Tests.Common/Builders/MacroBuilder.cs b/src/Umbraco.Tests.Common/Builders/MacroBuilder.cs index 66e81ef8ce..4206dcc3de 100644 --- a/src/Umbraco.Tests.Common/Builders/MacroBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MacroBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.Linq; @@ -15,7 +18,7 @@ namespace Umbraco.Tests.Common.Builders IWithAliasBuilder, IWithNameBuilder { - private List _propertyBuilders = new List(); + private readonly List _propertyBuilders = new List(); private int? _id; private Guid? _key; @@ -78,7 +81,7 @@ namespace Umbraco.Tests.Common.Builders var id = _id ?? 1; var name = _name ?? Guid.NewGuid().ToString(); var alias = _alias ?? name.ToCamelCase(); - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var useInEditor = _useInEditor ?? false; var cacheDuration = _cacheDuration ?? 0; var cacheByPage = _cacheByPage ?? false; @@ -90,7 +93,7 @@ namespace Umbraco.Tests.Common.Builders var macro = new Macro(shortStringHelper, id, key, useInEditor, cacheDuration, alias, name, cacheByPage, cacheByMember, dontRender, macroSource); - foreach (var property in _propertyBuilders.Select(x => x.Build())) + foreach (IMacroProperty property in _propertyBuilders.Select(x => x.Build())) { macro.Properties.Add(property); } diff --git a/src/Umbraco.Tests.Common/Builders/MacroPropertyBuilder.cs b/src/Umbraco.Tests.Common/Builders/MacroPropertyBuilder.cs index ce80a056e9..15532b9cc9 100644 --- a/src/Umbraco.Tests.Common/Builders/MacroPropertyBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MacroPropertyBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Extensions; @@ -20,7 +23,8 @@ namespace Umbraco.Tests.Common.Builders private int? _sortOrder; private string _editorAlias; - public MacroPropertyBuilder(MacroBuilder parentBuilder) : base(parentBuilder) + public MacroPropertyBuilder(MacroBuilder parentBuilder) + : base(parentBuilder) { } @@ -28,20 +32,20 @@ namespace Umbraco.Tests.Common.Builders { _editorAlias = editorAlias; return this; - } + } public override IMacroProperty Build() { var id = _id ?? 1; var name = _name ?? Guid.NewGuid().ToString(); var alias = _alias ?? name.ToCamelCase(); - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var sortOrder = _sortOrder ?? 0; var editorAlias = _editorAlias ?? string.Empty; return new MacroProperty(id, key, alias, name, sortOrder, editorAlias); } - + int? IWithIdBuilder.Id { get => _id; diff --git a/src/Umbraco.Tests.Common/Builders/MediaBuilder.cs b/src/Umbraco.Tests.Common/Builders/MediaBuilder.cs index d9ca6480bc..a2afe1c964 100644 --- a/src/Umbraco.Tests.Common/Builders/MediaBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MediaBuilder.cs @@ -1,8 +1,12 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; -using Umbraco.Core.Models; -using Umbraco.Tests.Common.Builders.Interfaces; -using Umbraco.Tests.Common.Builders.Extensions; +using System.Collections.Generic; using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; using Umbraco.Tests.Testing; namespace Umbraco.Tests.Common.Builders @@ -68,10 +72,10 @@ namespace Umbraco.Tests.Common.Builders public override Media Build() { var id = _id ?? 0; - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var parentId = _parentId ?? -1; - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var name = _name ?? Guid.NewGuid().ToString(); var creatorId = _creatorId ?? 0; var level = _level ?? 1; @@ -87,7 +91,7 @@ namespace Umbraco.Tests.Common.Builders throw new InvalidOperationException("A media item cannot be constructed without providing a media type. Use AddMediaType() or WithMediaType()."); } - var mediaType = _mediaType ?? _mediaTypeBuilder.Build(); + IMediaType mediaType = _mediaType ?? _mediaTypeBuilder.Build(); var media = new Media(name, parentId, mediaType) { @@ -106,8 +110,8 @@ namespace Umbraco.Tests.Common.Builders { if (_propertyDataBuilder != null) { - var propertyData = _propertyDataBuilder.Build(); - foreach (var keyValuePair in propertyData) + IDictionary propertyData = _propertyDataBuilder.Build(); + foreach (KeyValuePair keyValuePair in propertyData) { media.SetValue(keyValuePair.Key, keyValuePair.Value); } @@ -123,35 +127,27 @@ namespace Umbraco.Tests.Common.Builders return media; } - public static Media CreateSimpleMedia(IMediaType mediaType, string name, int parentId, int id = 0) - { - return new MediaBuilder() + public static Media CreateSimpleMedia(IMediaType mediaType, string name, int parentId, int id = 0) => + new MediaBuilder() .WithId(id) .WithName(name) .WithMediaType(mediaType) .WithParentId(parentId) .WithPropertyValues(new - { - title = name + " Subpage", - bodyText = "This is a subpage", - author = "John Doe" - }) + { + title = name + " Subpage", + bodyText = "This is a subpage", + author = "John Doe" + }) .Build(); - } - public static Media CreateMediaImage(IMediaType mediaType, int parentId) - { - return CreateMediaImage(mediaType, parentId, "/media/test-image.png"); - } + public static Media CreateMediaImage(IMediaType mediaType, int parentId) => + CreateMediaImage(mediaType, parentId, "/media/test-image.png"); - public static Media CreateMediaImageWithCrop(IMediaType mediaType, int parentId) - { - return CreateMediaImage(mediaType, parentId, "{src: '/media/test-image.png', crops: []}"); - } + public static Media CreateMediaImageWithCrop(IMediaType mediaType, int parentId) => + CreateMediaImage(mediaType, parentId, "{src: '/media/test-image.png', crops: []}"); - private static Media CreateMediaImage(IMediaType mediaType, int parentId, string fileValue) - { - return new MediaBuilder() + private static Media CreateMediaImage(IMediaType mediaType, int parentId, string fileValue) => new MediaBuilder() .WithMediaType(mediaType) .WithName("Test Image") .WithParentId(parentId) @@ -163,20 +159,14 @@ namespace Umbraco.Tests.Common.Builders .WithKeyValue(Constants.Conventions.Media.Extension, "png") .Done() .Build(); - } - public static Media CreateMediaFolder(IMediaType mediaType, int parentId) - { - return new MediaBuilder() + public static Media CreateMediaFolder(IMediaType mediaType, int parentId) => new MediaBuilder() .WithMediaType(mediaType) .WithName("Test Folder") .WithParentId(parentId) .Build(); - } - public static Media CreateMediaFile(IMediaType mediaType, int parentId) - { - return new MediaBuilder() + public static Media CreateMediaFile(IMediaType mediaType, int parentId) => new MediaBuilder() .WithMediaType(mediaType) .WithName("Test File") .WithParentId(parentId) @@ -186,7 +176,6 @@ namespace Umbraco.Tests.Common.Builders .WithKeyValue(Constants.Conventions.Media.Extension, "png") .Done() .Build(); - } int? IWithIdBuilder.Id { @@ -247,6 +236,7 @@ namespace Umbraco.Tests.Common.Builders get => _sortOrder; set => _sortOrder = value; } + int? IWithParentIdBuilder.ParentId { get => _parentId; diff --git a/src/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs index 3cbb80bde4..668dbbc961 100644 --- a/src/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Linq; using Umbraco.Core; @@ -11,20 +14,22 @@ namespace Umbraco.Tests.Common.Builders : ContentTypeBaseBuilder, IWithPropertyTypeIdsIncrementingFrom { - private List> _propertyGroupBuilders = new List>(); + private readonly List> _propertyGroupBuilders = new List>(); private int? _propertyTypeIdsIncrementingFrom; - public MediaTypeBuilder() : base(null) + public MediaTypeBuilder() + : base(null) { } - public MediaTypeBuilder(MediaBuilder parentBuilder) : base(parentBuilder) + public MediaTypeBuilder(MediaBuilder parentBuilder) + : base(parentBuilder) { } public MediaTypeBuilder WithMediaPropertyGroup() { - var builder = new PropertyGroupBuilder(this) + PropertyGroupBuilder builder = new PropertyGroupBuilder(this) .WithId(99) .WithName("Media") .WithSortOrder(1) @@ -82,7 +87,7 @@ namespace Umbraco.Tests.Common.Builders public override IMediaType Build() { MediaType mediaType; - var parent = GetParent(); + IContentTypeComposition parent = GetParent(); if (parent != null) { mediaType = new MediaType(ShortStringHelper, (IMediaType)parent, GetAlias()); @@ -122,7 +127,7 @@ namespace Umbraco.Tests.Common.Builders public static MediaType CreateSimpleMediaType(string alias, string name, IMediaType parent = null, bool randomizeAliases = false, string propertyGroupName = "Content") { var builder = new MediaTypeBuilder(); - var mediaType = builder + IMediaType mediaType = builder .WithAlias(alias) .WithName(name) .WithParentContentType(parent) @@ -156,20 +161,16 @@ namespace Umbraco.Tests.Common.Builders return (MediaType)mediaType; } - public static MediaType CreateImageMediaType(string alias = Constants.Conventions.MediaTypes.Image) - { - return CreateImageMediaType(alias ?? "Image", Constants.PropertyEditors.Aliases.UploadField, -90); - } + public static MediaType CreateImageMediaType(string alias = Constants.Conventions.MediaTypes.Image) => + CreateImageMediaType(alias ?? "Image", Constants.PropertyEditors.Aliases.UploadField, -90); - public static MediaType CreateImageMediaTypeWithCrop(string alias = Constants.Conventions.MediaTypes.Image) - { - return CreateImageMediaType(alias ?? "Image", Constants.PropertyEditors.Aliases.ImageCropper, 1043); - } + public static MediaType CreateImageMediaTypeWithCrop(string alias = Constants.Conventions.MediaTypes.Image) => + CreateImageMediaType(alias ?? "Image", Constants.PropertyEditors.Aliases.ImageCropper, 1043); private static MediaType CreateImageMediaType(string alias, string imageFieldPropertyEditorAlias, int imageFieldDataTypeId) { var builder = new MediaTypeBuilder(); - var mediaType = builder + IMediaType mediaType = builder .WithAlias(alias) .WithName("Image") .AddPropertyGroup() @@ -222,7 +223,7 @@ namespace Umbraco.Tests.Common.Builders public static MediaType CreateVideoMediaType() { var builder = new MediaTypeBuilder(); - var mediaType = builder + IMediaType mediaType = builder .WithAlias("video") .WithName("Video") .AddPropertyGroup() @@ -237,7 +238,7 @@ namespace Umbraco.Tests.Common.Builders .WithAlias("videoFile") .WithName("Video file") .WithSortOrder(1) - .Done() + .Done() .Done() .Build(); diff --git a/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs index 001956b66e..3eacfaaff0 100644 --- a/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs @@ -1,8 +1,11 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; -using Umbraco.Core.Models; -using Umbraco.Tests.Common.Builders.Interfaces; -using Umbraco.Tests.Common.Builders.Extensions; using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; namespace Umbraco.Tests.Common.Builders { @@ -95,9 +98,9 @@ namespace Umbraco.Tests.Common.Builders public override Member Build() { var id = _id ?? 0; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var name = _name ?? Guid.NewGuid().ToString(); var creatorId = _creatorId ?? 0; var level = _level ?? 1; @@ -110,16 +113,16 @@ namespace Umbraco.Tests.Common.Builders var failedPasswordAttempts = _failedPasswordAttempts ?? 0; var isApproved = _isApproved ?? false; var isLockedOut = _isLockedOut ?? false; - var lastLockoutDate = _lastLockoutDate ?? DateTime.Now; - var lastLoginDate = _lastLoginDate ?? DateTime.Now; - var lastPasswordChangeDate = _lastPasswordChangeDate ?? DateTime.Now; + DateTime lastLockoutDate = _lastLockoutDate ?? DateTime.Now; + DateTime lastLoginDate = _lastLoginDate ?? DateTime.Now; + DateTime lastPasswordChangeDate = _lastPasswordChangeDate ?? DateTime.Now; if (_memberTypeBuilder is null && _memberType is null) { throw new InvalidOperationException("A member cannot be constructed without providing a member type. Use AddMemberType() or WithMemberType()."); } - var memberType = _memberType ?? _memberTypeBuilder.Build(); + IMemberType memberType = _memberType ?? _memberTypeBuilder.Build(); var member = new Member(name, email, username, rawPasswordValue, memberType) { @@ -137,7 +140,7 @@ namespace Umbraco.Tests.Common.Builders if (_propertyIdsIncrementingFrom.HasValue) { var i = _propertyIdsIncrementingFrom.Value; - foreach (var property in member.Properties) + foreach (IProperty property in member.Properties) { property.Id = ++i; } @@ -157,8 +160,8 @@ namespace Umbraco.Tests.Common.Builders if (_additionalDataBuilder != null) { - var additionalData = _additionalDataBuilder.Build(); - foreach (var kvp in additionalData) + IDictionary additionalData = _additionalDataBuilder.Build(); + foreach (KeyValuePair kvp in additionalData) { member.AdditionalData.Add(kvp.Key, kvp.Value); } @@ -166,8 +169,8 @@ namespace Umbraco.Tests.Common.Builders if (_propertyDataBuilder != null) { - var propertyData = _propertyDataBuilder.Build(); - foreach (var kvp in propertyData) + IDictionary propertyData = _propertyDataBuilder.Build(); + foreach (KeyValuePair kvp in propertyData) { member.SetValue(kvp.Key, kvp.Value); } @@ -178,7 +181,7 @@ namespace Umbraco.Tests.Common.Builders return member; } - public static IEnumerable CreateSimpleMembers(IMemberType memberType, int amount, Action onCreating = null) + public static IEnumerable CreateSimpleMembers(IMemberType memberType, int amount) { var list = new List(); @@ -186,13 +189,12 @@ namespace Umbraco.Tests.Common.Builders { var name = "Member No-" + i; - var builder = new MemberBuilder() + MemberBuilder builder = new MemberBuilder() .WithMemberType(memberType) .WithName(name) .WithEmail("test" + i + "@test.com") .WithLogin("test" + i, "test" + i); - builder = builder .AddPropertyData() .WithKeyValue("title", name + " member" + i) @@ -205,9 +207,10 @@ namespace Umbraco.Tests.Common.Builders return list; } + public static Member CreateSimpleMember(IMemberType memberType, string name, string email, string password, string username, Guid? key = null) { - var builder = new MemberBuilder() + MemberBuilder builder = new MemberBuilder() .WithMemberType(memberType) .WithName(name) .WithEmail(email) @@ -236,7 +239,7 @@ namespace Umbraco.Tests.Common.Builders for (var i = 0; i < amount; i++) { var name = "Member No-" + i; - var member = new MemberBuilder() + Member member = new MemberBuilder() .WithMemberType(memberType) .WithName(name) .WithEmail("test" + i + "@test.com") diff --git a/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs index bfd7f30a14..41bc4eb5c4 100644 --- a/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs @@ -1,4 +1,8 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; @@ -32,9 +36,9 @@ namespace Umbraco.Tests.Common.Builders public override MemberGroup Build() { var id = _id ?? 1; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var name = _name ?? Guid.NewGuid().ToString(); var creatorId = _creatorId ?? 1; @@ -50,8 +54,8 @@ namespace Umbraco.Tests.Common.Builders if (_additionalDataBuilder != null) { - var additionalData = _additionalDataBuilder.Build(); - foreach (var kvp in additionalData) + IDictionary additionalData = _additionalDataBuilder.Build(); + foreach (KeyValuePair kvp in additionalData) { memberGroup.AdditionalData.Add(kvp.Key, kvp.Value); } diff --git a/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs index e111ae8cb4..480a07890a 100644 --- a/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Linq; using Umbraco.Core; @@ -11,22 +14,24 @@ namespace Umbraco.Tests.Common.Builders : ContentTypeBaseBuilder, IWithPropertyTypeIdsIncrementingFrom { - private List> _propertyGroupBuilders = new List>(); - private Dictionary _memberCanEditProperties = new Dictionary(); - private Dictionary _memberCanViewProperties = new Dictionary(); + private readonly List> _propertyGroupBuilders = new List>(); + private readonly Dictionary _memberCanEditProperties = new Dictionary(); + private readonly Dictionary _memberCanViewProperties = new Dictionary(); private int? _propertyTypeIdsIncrementingFrom; - public MemberTypeBuilder() : base(null) + public MemberTypeBuilder() + : base(null) { } - public MemberTypeBuilder(MemberBuilder parentBuilder) : base(parentBuilder) + public MemberTypeBuilder(MemberBuilder parentBuilder) + : base(parentBuilder) { } public MemberTypeBuilder WithMembershipPropertyGroup() { - var builder = new PropertyGroupBuilder(this) + PropertyGroupBuilder builder = new PropertyGroupBuilder(this) .WithId(99) .WithName(Constants.Conventions.Member.StandardPropertiesGroupName) .AddPropertyType() @@ -118,12 +123,12 @@ namespace Umbraco.Tests.Common.Builders BuildPropertyGroups(memberType, _propertyGroupBuilders.Select(x => x.Build())); BuildPropertyTypeIds(memberType, _propertyTypeIdsIncrementingFrom); - foreach (var kvp in _memberCanEditProperties) + foreach (KeyValuePair kvp in _memberCanEditProperties) { memberType.SetMemberCanEditProperty(kvp.Key, kvp.Value); } - foreach (var kvp in _memberCanViewProperties) + foreach (KeyValuePair kvp in _memberCanViewProperties) { memberType.SetMemberCanViewProperty(kvp.Key, kvp.Value); } @@ -136,7 +141,7 @@ namespace Umbraco.Tests.Common.Builders public static MemberType CreateSimpleMemberType(string alias = null, string name = null) { var builder = new MemberTypeBuilder(); - var memberType = builder + IMemberType memberType = builder .WithAlias(alias) .WithName(name) .AddPropertyGroup() @@ -160,8 +165,8 @@ namespace Umbraco.Tests.Common.Builders .Done() .Done() .Build(); - - // Ensure that nothing is marked as dirty + + // Ensure that nothing is marked as dirty. memberType.ResetDirtyProperties(false); return (MemberType)memberType; diff --git a/src/Umbraco.Tests.Common/Builders/PropertyBuilder.cs b/src/Umbraco.Tests.Common/Builders/PropertyBuilder.cs index a79141b11c..f6e3ab2557 100644 --- a/src/Umbraco.Tests.Common/Builders/PropertyBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/PropertyBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; @@ -38,16 +41,16 @@ namespace Umbraco.Tests.Common.Builders public override IProperty Build() { var id = _id ?? 1; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; if (_propertyTypeBuilder is null && _propertyType is null) { throw new InvalidOperationException("A property cannot be constructed without providing a property type. Use AddPropertyType() or WithPropertyType()."); } - var propertyType = _propertyType ?? _propertyTypeBuilder.Build(); + IPropertyType propertyType = _propertyType ?? _propertyTypeBuilder.Build(); // Needs to be within collection to support publishing. var propertyTypeCollection = new PropertyTypeCollection(true, new[] { propertyType }); diff --git a/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs index 8fe6e1463d..be177a3138 100644 --- a/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.Linq; @@ -8,7 +11,8 @@ namespace Umbraco.Tests.Common.Builders { public class PropertyGroupBuilder : PropertyGroupBuilder { - public PropertyGroupBuilder() : base(null) + public PropertyGroupBuilder() + : base(null) { } } @@ -26,7 +30,8 @@ namespace Umbraco.Tests.Common.Builders IWithUpdateDateBuilder, IWithNameBuilder, IWithSortOrderBuilder, - IWithSupportsPublishing where TParent: IBuildPropertyGroups + IWithSupportsPublishing + where TParent : IBuildPropertyGroups { private readonly List>> _propertyTypeBuilders = new List>>(); @@ -39,7 +44,8 @@ namespace Umbraco.Tests.Common.Builders private bool? _supportsPublishing; private PropertyTypeCollection _propertyTypeCollection; - public PropertyGroupBuilder(TParent parentBuilder) : base(parentBuilder) + public PropertyGroupBuilder(TParent parentBuilder) + : base(parentBuilder) { } @@ -59,9 +65,9 @@ namespace Umbraco.Tests.Common.Builders public override PropertyGroup Build() { var id = _id ?? 0; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var name = _name ?? Guid.NewGuid().ToString(); var sortOrder = _sortOrder ?? 0; var supportsPublishing = _supportsPublishing ?? false; @@ -74,7 +80,7 @@ namespace Umbraco.Tests.Common.Builders else { propertyTypeCollection = new PropertyTypeCollection(supportsPublishing); - foreach (var propertyType in _propertyTypeBuilders.Select(x => x.Build())) + foreach (PropertyType propertyType in _propertyTypeBuilders.Select(x => x.Build())) { propertyTypeCollection.Add(propertyType); } diff --git a/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs index e4e9b4d621..4dc8ca8d50 100644 --- a/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core; using Umbraco.Core.Models; @@ -9,7 +12,8 @@ namespace Umbraco.Tests.Common.Builders { public class PropertyTypeBuilder : PropertyTypeBuilder { - public PropertyTypeBuilder() : base(null) + public PropertyTypeBuilder() + : base(null) { } } @@ -28,7 +32,8 @@ namespace Umbraco.Tests.Common.Builders IWithUpdateDateBuilder, IWithSortOrderBuilder, IWithDescriptionBuilder, - IWithSupportsPublishing where TParent : IBuildPropertyTypes + IWithSupportsPublishing + where TParent : IBuildPropertyTypes { private int? _id; private Guid? _key; @@ -49,7 +54,8 @@ namespace Umbraco.Tests.Common.Builders private bool? _supportsPublishing; private ContentVariation? _variations; - public PropertyTypeBuilder(TParent parentBuilder) : base(parentBuilder) + public PropertyTypeBuilder(TParent parentBuilder) + : base(parentBuilder) { } @@ -100,23 +106,23 @@ namespace Umbraco.Tests.Common.Builders public override PropertyType Build() { var id = _id ?? 0; - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var propertyEditorAlias = _propertyEditorAlias ?? Constants.PropertyEditors.Aliases.TextBox; - var valueStorageType = _valueStorageType ?? ValueStorageType.Nvarchar; + ValueStorageType valueStorageType = _valueStorageType ?? ValueStorageType.Nvarchar; var name = _name ?? Guid.NewGuid().ToString(); var alias = _alias ?? name.ToCamelCase(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var sortOrder = _sortOrder ?? 0; var dataTypeId = _dataTypeId ?? -88; var description = _description ?? string.Empty; - var propertyGroupId = _propertyGroupId ?? null; + Lazy propertyGroupId = _propertyGroupId ?? null; var mandatory = _mandatory ?? false; var mandatoryMessage = _mandatoryMessage ?? string.Empty; var validationRegExp = _validationRegExp ?? string.Empty; var validationRegExpMessage = _validationRegExpMessage ?? string.Empty; var supportsPublishing = _supportsPublishing ?? false; - var variations = _variations ?? ContentVariation.Nothing; + ContentVariation variations = _variations ?? ContentVariation.Nothing; var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); @@ -142,7 +148,7 @@ namespace Umbraco.Tests.Common.Builders return propertyType; } - + int? IWithIdBuilder.Id { get => _id; diff --git a/src/Umbraco.Tests.Common/Builders/RelationBuilder.cs b/src/Umbraco.Tests.Common/Builders/RelationBuilder.cs index 9c29897de4..8824e9b20e 100644 --- a/src/Umbraco.Tests.Common/Builders/RelationBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/RelationBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; @@ -55,9 +58,9 @@ namespace Umbraco.Tests.Common.Builders var id = _id ?? 0; var parentId = _parentId ?? 0; var childId = _childId ?? 0; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var comment = _comment ?? string.Empty; if (_relationTypeBuilder == null && _relationType == null) @@ -65,7 +68,7 @@ namespace Umbraco.Tests.Common.Builders throw new InvalidOperationException("Cannot construct a Relation without a RelationType. Use AddRelationType() or WithRelationType()."); } - var relationType = _relationType ?? _relationTypeBuilder.Build(); + IRelationType relationType = _relationType ?? _relationTypeBuilder.Build(); return new Relation(parentId, childId, relationType) { diff --git a/src/Umbraco.Tests.Common/Builders/RelationTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/RelationTypeBuilder.cs index 7c47c1dad1..4b1953322a 100644 --- a/src/Umbraco.Tests.Common/Builders/RelationTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/RelationTypeBuilder.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; @@ -25,11 +28,13 @@ namespace Umbraco.Tests.Common.Builders private Guid? _parentObjectType; private DateTime? _updateDate; - public RelationTypeBuilder() : base(null) + public RelationTypeBuilder() + : base(null) { } - public RelationTypeBuilder(RelationBuilder parentBuilder) : base(parentBuilder) + public RelationTypeBuilder(RelationBuilder parentBuilder) + : base(parentBuilder) { } @@ -55,14 +60,14 @@ namespace Umbraco.Tests.Common.Builders { var alias = _alias ?? Guid.NewGuid().ToString(); var name = _name ?? Guid.NewGuid().ToString(); - var parentObjectType = _parentObjectType ?? null; - var childObjectType = _childObjectType ?? null; + Guid? parentObjectType = _parentObjectType ?? null; + Guid? childObjectType = _childObjectType ?? null; var id = _id ?? 0; - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var isBidirectional = _isBidirectional ?? false; - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; - var deleteDate = _deleteDate ?? null; + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; + DateTime? deleteDate = _deleteDate ?? null; return new RelationType(name, alias, isBidirectional, parentObjectType, childObjectType) { diff --git a/src/Umbraco.Tests.Common/Builders/StylesheetBuilder.cs b/src/Umbraco.Tests.Common/Builders/StylesheetBuilder.cs index ed871b9c31..b293cb7bb8 100644 --- a/src/Umbraco.Tests.Common/Builders/StylesheetBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/StylesheetBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using Umbraco.Core.Models; namespace Umbraco.Tests.Common.Builders diff --git a/src/Umbraco.Tests.Common/Builders/TemplateBuilder.cs b/src/Umbraco.Tests.Common/Builders/TemplateBuilder.cs index 2fd86b60b7..35a32a8d9f 100644 --- a/src/Umbraco.Tests.Common/Builders/TemplateBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/TemplateBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Umbraco.Core.Models; using Umbraco.Core.Strings; @@ -28,11 +31,13 @@ namespace Umbraco.Tests.Common.Builders private string _masterTemplateAlias; private Lazy _masterTemplateId; - public TemplateBuilder() : base(null) + public TemplateBuilder() + : base(null) { } - public TemplateBuilder(ContentTypeBuilder parentBuilder) : base(parentBuilder) + public TemplateBuilder(ContentTypeBuilder parentBuilder) + : base(parentBuilder) { } @@ -53,16 +58,16 @@ namespace Umbraco.Tests.Common.Builders public override ITemplate Build() { var id = _id ?? 0; - var key = _key ?? Guid.NewGuid(); + Guid key = _key ?? Guid.NewGuid(); var name = _name ?? Guid.NewGuid().ToString(); var alias = _alias ?? name.ToCamelCase(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var path = _path ?? $"-1,{id}"; var content = _content; var isMasterTemplate = _isMasterTemplate ?? false; var masterTemplateAlias = _masterTemplateAlias ?? string.Empty; - var masterTemplateId = _masterTemplateId ?? new Lazy(() => -1); + Lazy masterTemplateId = _masterTemplateId ?? new Lazy(() => -1); var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); @@ -82,13 +87,11 @@ namespace Umbraco.Tests.Common.Builders return template; } - public static Template CreateTextPageTemplate(string alias = "textPage") - { - return (Template)new TemplateBuilder() + public static Template CreateTextPageTemplate(string alias = "textPage") => + (Template)new TemplateBuilder() .WithAlias(alias) .WithName("Text page") .Build(); - } int? IWithIdBuilder.Id { diff --git a/src/Umbraco.Tests.Common/Builders/TreeBuilder.cs b/src/Umbraco.Tests.Common/Builders/TreeBuilder.cs index 072e838d5a..29a78ef4d7 100644 --- a/src/Umbraco.Tests.Common/Builders/TreeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/TreeBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using Umbraco.Core; using Umbraco.Tests.Common.Builders.Interfaces; using Umbraco.Web.Trees; @@ -54,7 +57,7 @@ namespace Umbraco.Tests.Common.Builders var sectionAlias = _sectionAlias ?? Constants.Applications.Content; var group = _group ?? string.Empty; var title = _title ?? string.Empty; - var treeUse = _treeUse ?? TreeUse.Main; + TreeUse treeUse = _treeUse ?? TreeUse.Main; var isSingleNode = _isSingleNode ?? false; return new Tree(sortOrder, sectionAlias, group, alias, title, treeUse, typeof(SampleTreeController), isSingleNode); diff --git a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs index 0995622841..14ec8f6a99 100644 --- a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs @@ -1,16 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Tests.Common.Builders.Interfaces; using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; namespace Umbraco.Tests.Common.Builders { public class UserBuilder : UserBuilder { - public UserBuilder() : base(null) + public UserBuilder() + : base(null) { } } @@ -48,7 +52,8 @@ namespace Umbraco.Tests.Common.Builders private int[] _startMediaIds; private readonly List>> _userGroupBuilders = new List>>(); - public UserBuilder(TParent parentBuilder) : base(parentBuilder) + public UserBuilder(TParent parentBuilder) + : base(parentBuilder) { } @@ -124,11 +129,6 @@ namespace Umbraco.Tests.Common.Builders return this; } - /// - /// Will suffix the name, email and username for testing - /// - /// - /// public UserBuilder WithSuffix(string suffix) { _suffix = suffix; @@ -147,9 +147,9 @@ namespace Umbraco.Tests.Common.Builders var id = _id ?? 0; var defaultLang = _defaultLang ?? "en"; var globalSettings = new GlobalSettings { DefaultUILanguage = defaultLang }; - var key = _key ?? Guid.NewGuid(); - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; + Guid key = _key ?? Guid.NewGuid(); + DateTime createDate = _createDate ?? DateTime.Now; + DateTime updateDate = _updateDate ?? DateTime.Now; var name = _name ?? "TestUser" + _suffix; var language = _language ?? globalSettings.DefaultUILanguage; var username = _username ?? "TestUser" + _suffix; @@ -158,14 +158,14 @@ namespace Umbraco.Tests.Common.Builders var failedPasswordAttempts = _failedPasswordAttempts ?? 0; var isApproved = _isApproved ?? false; var isLockedOut = _isLockedOut ?? false; - var lastLockoutDate = _lastLockoutDate ?? DateTime.Now; - var lastLoginDate = _lastLoginDate ?? DateTime.Now; - var lastPasswordChangeDate = _lastPasswordChangeDate ?? DateTime.Now; + DateTime lastLockoutDate = _lastLockoutDate ?? DateTime.Now; + DateTime lastLoginDate = _lastLoginDate ?? DateTime.Now; + DateTime lastPasswordChangeDate = _lastPasswordChangeDate ?? DateTime.Now; var comments = _comments ?? string.Empty; var sessionTimeout = _sessionTimeout ?? 0; var startContentIds = _startContentIds ?? new[] { -1 }; var startMediaIds = _startMediaIds ?? new[] { -1 }; - var groups = _userGroupBuilders.Select(x => x.Build()); + IEnumerable groups = _userGroupBuilders.Select(x => x.Build()); var result = new User( globalSettings, @@ -190,7 +190,7 @@ namespace Umbraco.Tests.Common.Builders StartContentIds = startContentIds, StartMediaIds = startMediaIds, }; - foreach (var readOnlyUserGroup in groups) + foreach (IUserGroup readOnlyUserGroup in groups) { result.AddGroup(readOnlyUserGroup.ToReadOnlyGroup()); } @@ -205,7 +205,7 @@ namespace Umbraco.Tests.Common.Builders for (var i = 0; i < amount; i++) { var name = "User No-" + i; - var user = new UserBuilder() + User user = new UserBuilder() .WithName(name) .WithEmail("test" + i + "@test.com") .WithLogin("test" + i, "test" + i) @@ -221,15 +221,13 @@ namespace Umbraco.Tests.Common.Builders return list; } - public static User CreateUser(string suffix = "") - { - return new UserBuilder() + public static User CreateUser(string suffix = "") => + new UserBuilder() .WithIsApproved(true) .WithName("TestUser" + suffix) .WithLogin("TestUser" + suffix, "testing") .WithEmail("test" + suffix + "@test.com") .Build(); - } int? IWithIdBuilder.Id { diff --git a/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs index 9785c27218..ef1733dc7d 100644 --- a/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs @@ -1,16 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Linq; using Moq; using Umbraco.Core.Models.Membership; using Umbraco.Core.Strings; -using Umbraco.Tests.Common.Builders.Interfaces; using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; namespace Umbraco.Tests.Common.Builders { public class UserGroupBuilder : UserGroupBuilder { - public UserGroupBuilder() : base(null) + public UserGroupBuilder() + : base(null) { } } @@ -33,15 +37,16 @@ namespace Umbraco.Tests.Common.Builders private int? _startMediaId; private int? _userCount; - public UserGroupBuilder(TParent parentBuilder) : base(parentBuilder) + public UserGroupBuilder(TParent parentBuilder) + : base(parentBuilder) { } /// - /// Will suffix the name and alias for testing + /// Will suffix the name, email and username for testing. /// - /// - /// + /// Suffix to add to user group properties. + /// Current builder instance. public UserGroupBuilder WithSuffix(string suffix) { _suffix = suffix; @@ -84,9 +89,8 @@ namespace Umbraco.Tests.Common.Builders return this; } - public IReadOnlyUserGroup BuildReadOnly(IUserGroup userGroup) - { - return Mock.Of(x => + public IReadOnlyUserGroup BuildReadOnly(IUserGroup userGroup) => + Mock.Of(x => x.Permissions == userGroup.Permissions && x.Alias == userGroup.Alias && x.Icon == userGroup.Icon && @@ -95,7 +99,6 @@ namespace Umbraco.Tests.Common.Builders x.StartMediaId == userGroup.StartMediaId && x.AllowedSections == userGroup.AllowedSections && x.Id == userGroup.Id); - } public override IUserGroup Build() { @@ -109,10 +112,12 @@ namespace Umbraco.Tests.Common.Builders var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); - var userGroup = new UserGroup(shortStringHelper, userCount, alias, name, _permissions, icon); - userGroup.Id = id; - userGroup.StartContentId = startContentId; - userGroup.StartMediaId = startMediaId; + var userGroup = new UserGroup(shortStringHelper, userCount, alias, name, _permissions, icon) + { + Id = id, + StartContentId = startContentId, + StartMediaId = startMediaId + }; foreach (var section in _allowedSections) { @@ -122,15 +127,13 @@ namespace Umbraco.Tests.Common.Builders return userGroup; } - public static UserGroup CreateUserGroup(string alias = "testGroup", string name = "Test Group", string suffix = "", string[] permissions = null, string[] allowedSections = null) - { - return (UserGroup)new UserGroupBuilder() + public static UserGroup CreateUserGroup(string alias = "testGroup", string name = "Test Group", string suffix = "", string[] permissions = null, string[] allowedSections = null) => + (UserGroup)new UserGroupBuilder() .WithAlias(alias + suffix) .WithName(name + suffix) .WithPermissions(permissions ?? new[] { "A", "B", "C" }) .WithAllowedSections(allowedSections ?? new[] { "content", "media" }) .Build(); - } int? IWithIdBuilder.Id { diff --git a/src/Umbraco.Tests.Common/Builders/XmlDocumentBuilder.cs b/src/Umbraco.Tests.Common/Builders/XmlDocumentBuilder.cs index d5250c3739..431b86c57c 100644 --- a/src/Umbraco.Tests.Common/Builders/XmlDocumentBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/XmlDocumentBuilder.cs @@ -1,4 +1,7 @@ -using System.Xml; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Xml; namespace Umbraco.Tests.Common.Builders { From 61cbb84dd300b26f1876b23b10588cc4d82017ad Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sun, 6 Dec 2020 09:13:29 +0100 Subject: [PATCH 55/63] Adhered to linting rules for authorization policy handlers, requirements and tests. --- .../Authorization/AdminUsersHandlerTests.cs | 57 +++++------ .../Authorization/BackOfficeHandlerTests.cs | 35 ++++--- ...entPermissionsPublishBranchHandlerTests.cs | 41 ++++---- ...ntentPermissionsQueryStringHandlerTests.cs | 97 +++++++++---------- .../ContentPermissionsResourceHandlerTests.cs | 41 ++++---- .../DenyLocalLoginHandlerTests.cs | 18 ++-- ...MediaPermissionsQueryStringHandlerTests.cs | 83 ++++++++-------- .../MediaPermissionsResourceHandlerTests.cs | 41 ++++---- .../Authorization/SectionHandlerTests.cs | 31 +++--- .../Authorization/TreeHandlerTests.cs | 36 ++++--- .../Authorization/UserGroupHandlerTests.cs | 51 +++++----- .../Authorization/AdminUsersHandler.cs | 41 +++++--- .../Authorization/AdminUsersRequirement.cs | 17 +++- .../Authorization/BackOfficeHandler.cs | 3 + .../Authorization/BackOfficeRequirement.cs | 17 +++- .../ContentPermissionsPublishBranchHandler.cs | 36 +++++-- ...tentPermissionsPublishBranchRequirement.cs | 17 +++- .../ContentPermissionsQueryStringHandler.cs | 32 +++--- ...ontentPermissionsQueryStringRequirement.cs | 31 ++++-- .../ContentPermissionsResource.cs | 42 ++++++-- .../ContentPermissionsResourceHandler.cs | 23 +++-- .../ContentPermissionsResourceRequirement.cs | 7 +- .../Authorization/DenyLocalLoginHandler.cs | 25 ++--- .../DenyLocalLoginRequirement.cs | 7 +- .../MediaPermissionsQueryStringHandler.cs | 34 ++++--- .../MediaPermissionsQueryStringRequirement.cs | 20 +++- .../Authorization/MediaPermissionsResource.cs | 5 +- .../MediaPermissionsResourceHandler.cs | 25 +++-- .../MediaPermissionsResourceRequirement.cs | 7 +- ...tSatisfyRequirementAuthorizationHandler.cs | 36 ++++--- .../PermissionsQueryStringHandler.cs | 40 ++++++-- .../Authorization/SectionHandler.cs | 25 +++-- .../Authorization/SectionRequirement.cs | 15 ++- .../Authorization/TreeHandler.cs | 37 ++++--- .../Authorization/TreeRequirement.cs | 16 ++- .../Authorization/UserGroupHandler.cs | 52 ++++++---- .../Authorization/UserGroupRequirement.cs | 17 +++- 37 files changed, 697 insertions(+), 461 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/AdminUsersHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/AdminUsersHandlerTests.cs index 059508d561..c05373f5fb 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/AdminUsersHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/AdminUsersHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -32,8 +35,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Missing_QueryString_Value_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringName: "xxx"); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + AdminUsersHandler sut = CreateHandler(queryStringName: "xxx"); await sut.HandleAsync(authHandlerContext); @@ -43,8 +46,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Non_Integer_QueryString_Value_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: "xxx"); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + AdminUsersHandler sut = CreateHandler(queryStringValue: "xxx"); await sut.HandleAsync(authHandlerContext); @@ -54,8 +57,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Editing_Single_Admin_User_By_Admin_User_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: Admin2UserId.ToString(), editingWithAdmin: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + AdminUsersHandler sut = CreateHandler(queryStringValue: Admin2UserId.ToString(), editingWithAdmin: true); await sut.HandleAsync(authHandlerContext); @@ -65,8 +68,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Editing_Single_Admin_User_By_Non_Admin_User_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: Admin2UserId.ToString()); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + AdminUsersHandler sut = CreateHandler(queryStringValue: Admin2UserId.ToString()); await sut.HandleAsync(authHandlerContext); @@ -76,8 +79,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Editing_Single_Non_Admin_User_By_Non_Admin_User_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: NonAdmin2UserId.ToString()); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + AdminUsersHandler sut = CreateHandler(queryStringValue: NonAdmin2UserId.ToString()); await sut.HandleAsync(authHandlerContext); @@ -87,8 +90,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Editing_Multiple_Users_Including_Admins_By_Admin_User_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(queryStringName: MultipleUserEditQueryStringName); - var sut = CreateHandler(queryStringName: MultipleUserEditQueryStringName, queryStringValue: $"{Admin2UserId},{NonAdmin2UserId}", editingWithAdmin: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(queryStringName: MultipleUserEditQueryStringName); + AdminUsersHandler sut = CreateHandler(queryStringName: MultipleUserEditQueryStringName, queryStringValue: $"{Admin2UserId},{NonAdmin2UserId}", editingWithAdmin: true); await sut.HandleAsync(authHandlerContext); @@ -98,8 +101,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Editing_Multiple_Users_Including_Admins_By_Non_Admin_User_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(queryStringName: MultipleUserEditQueryStringName); - var sut = CreateHandler(queryStringName: MultipleUserEditQueryStringName, queryStringValue: $"{Admin2UserId},{NonAdmin2UserId}"); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(queryStringName: MultipleUserEditQueryStringName); + AdminUsersHandler sut = CreateHandler(queryStringName: MultipleUserEditQueryStringName, queryStringValue: $"{Admin2UserId},{NonAdmin2UserId}"); await sut.HandleAsync(authHandlerContext); @@ -109,8 +112,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Editing_Multiple_Users_Not_Including_Admins_By_Non_Admin_User_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(queryStringName: MultipleUserEditQueryStringName); - var sut = CreateHandler(queryStringName: MultipleUserEditQueryStringName, queryStringValue: $"{NonAdmin2UserId},{NonAdmin3UserId}"); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(queryStringName: MultipleUserEditQueryStringName); + AdminUsersHandler sut = CreateHandler(queryStringName: MultipleUserEditQueryStringName, queryStringValue: $"{NonAdmin2UserId},{NonAdmin3UserId}"); await sut.HandleAsync(authHandlerContext); @@ -121,15 +124,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new AdminUsersRequirement(queryStringName); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } private AdminUsersHandler CreateHandler(string queryStringName = SingleUserEditQueryStringName, string queryStringValue = "", bool editingWithAdmin = false) { - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringName, queryStringValue); - CreateMockUserServiceAndSecurityAccessor(editingWithAdmin, out var mockUserService, out var mockBackOfficeSecurityAccessor); - var userEditorAuthorizationHelper = CreateUserEditorAuthorizationHelper(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringName, queryStringValue); + CreateMockUserServiceAndSecurityAccessor(editingWithAdmin, out Mock mockUserService, out Mock mockBackOfficeSecurityAccessor); + UserEditorAuthorizationHelper userEditorAuthorizationHelper = CreateUserEditorAuthorizationHelper(); return new AdminUsersHandler(mockHttpContextAccessor.Object, mockUserService.Object, mockBackOfficeSecurityAccessor.Object, userEditorAuthorizationHelper); } @@ -152,11 +155,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { mockUserService = new Mock(); var globalSettings = new GlobalSettings(); - var adminUser1 = CreateUser(Admin1UserId, mockUserService, true); - var adminUser2 = CreateUser(Admin2UserId, mockUserService, true); - var nonAdminUser1 = CreateUser(NonAdmin1UserId, mockUserService); - var nonAdminUser2 = CreateUser(NonAdmin2UserId, mockUserService); - var nonAdminUser3 = CreateUser(NonAdmin3UserId, mockUserService); + User adminUser1 = CreateUser(Admin1UserId, mockUserService, true); + User adminUser2 = CreateUser(Admin2UserId, mockUserService, true); + User nonAdminUser1 = CreateUser(NonAdmin1UserId, mockUserService); + User nonAdminUser2 = CreateUser(NonAdmin2UserId, mockUserService); + User nonAdminUser3 = CreateUser(NonAdmin3UserId, mockUserService); // Single user requests have been setup in the create user operations, but // we also need to mock the responses when multiple users are being editing. @@ -175,7 +178,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static User CreateUser(int id, Mock mockUserService, bool isAdmin = false) { - var user = new UserBuilder() + User user = new UserBuilder() .WithId(id) .AddUserGroup() .WithAlias(isAdmin ? Constants.Security.AdminGroupAlias : Constants.Security.EditorGroupAlias) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/BackOfficeHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/BackOfficeHandlerTests.cs index c959de86de..5cb59a7002 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/BackOfficeHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/BackOfficeHandlerTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; @@ -17,8 +20,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Runtime_State_Install_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(runtimeLevel: RuntimeLevel.Install); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + BackOfficeHandler sut = CreateHandler(runtimeLevel: RuntimeLevel.Install); await sut.HandleAsync(authHandlerContext); @@ -28,8 +31,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Runtime_State_Upgrade_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(runtimeLevel: RuntimeLevel.Upgrade); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + BackOfficeHandler sut = CreateHandler(runtimeLevel: RuntimeLevel.Upgrade); await sut.HandleAsync(authHandlerContext); @@ -39,8 +42,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Unauthenticated_User_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + BackOfficeHandler sut = CreateHandler(); await sut.HandleAsync(authHandlerContext); @@ -50,8 +53,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Authenticated_User_Is_Not_Authorized_When_Not_Approved_And_Approval_Required() { - var authHandlerContext = CreateAuthorizationHandlerContext(requireApproval: true); - var sut = CreateHandler(currentUserIsAuthenticated: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(requireApproval: true); + BackOfficeHandler sut = CreateHandler(currentUserIsAuthenticated: true); await sut.HandleAsync(authHandlerContext); @@ -61,8 +64,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Authenticated_User_Is_Authorized_When_Not_Approved_And_Approval_Not_Required() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(currentUserIsAuthenticated: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + BackOfficeHandler sut = CreateHandler(currentUserIsAuthenticated: true); await sut.HandleAsync(authHandlerContext); @@ -72,8 +75,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Authenticated_User_Is_Authorized_When_Approved_And_Approval_Required() { - var authHandlerContext = CreateAuthorizationHandlerContext(requireApproval: true); - var sut = CreateHandler(currentUserIsAuthenticated: true, currentUserIsApproved: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(requireApproval: true); + BackOfficeHandler sut = CreateHandler(currentUserIsAuthenticated: true, currentUserIsApproved: true); await sut.HandleAsync(authHandlerContext); @@ -84,20 +87,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new BackOfficeRequirement(requireApproval); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } private BackOfficeHandler CreateHandler(RuntimeLevel runtimeLevel = RuntimeLevel.Run, bool currentUserIsAuthenticated = false, bool currentUserIsApproved = false) { - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(currentUserIsAuthenticated, currentUserIsApproved); - var mockRuntimeState = CreateMockRuntimeState(runtimeLevel); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(currentUserIsAuthenticated, currentUserIsApproved); + Mock mockRuntimeState = CreateMockRuntimeState(runtimeLevel); return new BackOfficeHandler(mockBackOfficeSecurityAccessor.Object, mockRuntimeState.Object); } private static Mock CreateMockBackOfficeSecurityAccessor(bool currentUserIsAuthenticated, bool currentUserIsApproved) { - var user = new UserBuilder() + global::Umbraco.Core.Models.Membership.User user = new UserBuilder() .WithIsApproved(currentUserIsApproved) .Build(); var mockBackOfficeSecurityAccessor = new Mock(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandlerTests.cs index ca73f234b9..ce3a960b6f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -25,13 +28,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_With_Access_To_All_Descendent_Nodes_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockUserService = CreateMockUserService(NodeId, new Dictionary + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockUserService = CreateMockUserService(NodeId, new Dictionary { { DescendentNodeId1, new string[] { "A" } }, { DescendentNodeId2, new string[] { "A" } } }); - var sut = CreateHandler(mockUserService.Object, NodeId); + ContentPermissionsPublishBranchHandler sut = CreateHandler(mockUserService.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -42,13 +45,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Without_Access_To_One_Descendent_Node_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockUserService = CreateMockUserService(NodeId, new Dictionary + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockUserService = CreateMockUserService(NodeId, new Dictionary { { DescendentNodeId1, new string[] { "A" } }, { DescendentNodeId2, new string[] { "B" } } }); - var sut = CreateHandler(mockUserService.Object, NodeId); + ContentPermissionsPublishBranchHandler sut = CreateHandler(mockUserService.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -59,13 +62,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Without_Access_To_First_Descendent_Node_Is_Not_Authorized_And_Checks_Exit_Early() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockUserService = CreateMockUserService(NodeId, new Dictionary + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockUserService = CreateMockUserService(NodeId, new Dictionary { { DescendentNodeId1, new string[] { "B" } }, { DescendentNodeId2, new string[] { "A" } } }); - var sut = CreateHandler(mockUserService.Object, NodeId); + ContentPermissionsPublishBranchHandler sut = CreateHandler(mockUserService.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -77,7 +80,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new ContentPermissionsPublishBranchRequirement('A'); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = CreateContent(NodeId); + IContent resource = CreateContent(NodeId); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } @@ -97,9 +100,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private ContentPermissionsPublishBranchHandler CreateHandler(IUserService userService, int nodeId) { - var mockEntityService = CreateMockEntityService(); - var contentPermissions = CreateContentPermissions(mockEntityService.Object, userService, nodeId); - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(); + Mock mockEntityService = CreateMockEntityService(); + ContentPermissions contentPermissions = CreateContentPermissions(mockEntityService.Object, userService, nodeId); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(); return new ContentPermissionsPublishBranchHandler(mockEntityService.Object, contentPermissions, mockBackOfficeSecurityAccessor.Object); } @@ -129,13 +132,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static IContent CreateContent(int nodeId) { - var contentType = ContentTypeBuilder.CreateBasicContentType(); + ContentType contentType = ContentTypeBuilder.CreateBasicContentType(); return ContentBuilder.CreateBasicContent(contentType, nodeId); } private static Mock CreateMockBackOfficeSecurityAccessor() { - var user = CreateUser(); + User user = CreateUser(); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(user); var mockBackOfficeSecurityAccessor = new Mock(); @@ -143,10 +146,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser() - { - return new UserBuilder() + private static User CreateUser() => + new UserBuilder() .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandlerTests.cs index 10549a6d7d..6f1a4b9e73 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandlerTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; @@ -21,15 +24,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { private const string QueryStringName = "id"; private const int NodeId = 1000; - private static readonly Guid NodeGuid = Guid.NewGuid(); - private static readonly Udi NodeUdi = UdiParser.Parse($"umb://document/{NodeGuid.ToString().ToLowerInvariant().Replace("-", string.Empty)}"); + private static readonly Guid s_nodeGuid = Guid.NewGuid(); + private static readonly Udi s_nodeUdi = UdiParser.Parse($"umb://document/{s_nodeGuid.ToString().ToLowerInvariant().Replace("-", string.Empty)}"); [Test] public async Task Node_Id_From_Requirement_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -39,9 +42,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Id_From_Requirement_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); await sut.HandleAsync(authHandlerContext); @@ -52,9 +55,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Id_Missing_From_Requirement_And_QueryString_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringName: "xxx"); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringName: "xxx"); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -64,9 +67,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Integer_Id_From_QueryString_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -77,9 +80,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Integer_Id_From_QueryString_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); await sut.HandleAsync(authHandlerContext); @@ -90,9 +93,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Udi_Id_From_QueryString_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeUdi.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeUdi.ToString()); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -103,9 +106,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Udi_Id_From_QueryString_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeUdi.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeUdi.ToString()); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); await sut.HandleAsync(authHandlerContext); @@ -116,9 +119,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Guid_Id_From_QueryString_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeGuid.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeGuid.ToString()); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -129,9 +132,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Guid_Id_From_QueryString_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeGuid.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeGuid.ToString()); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "B" }); await sut.HandleAsync(authHandlerContext); @@ -142,9 +145,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Invalid_Id_From_QueryString_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: "invalid"); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: "invalid"); + ContentPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -154,11 +157,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static AuthorizationHandlerContext CreateAuthorizationHandlerContext(int? nodeId = null) { const char Permission = 'A'; - var requirement = nodeId.HasValue + ContentPermissionsQueryStringRequirement requirement = nodeId.HasValue ? new ContentPermissionsQueryStringRequirement(nodeId.Value, Permission) : new ContentPermissionsQueryStringRequirement(Permission, QueryStringName); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } @@ -180,9 +183,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private ContentPermissionsQueryStringHandler CreateHandler(IHttpContextAccessor httpContextAccessor, int nodeId, string[] permissionsForPath) { - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(); - var mockEntityService = CreateMockEntityService(); - var contentPermissions = CreateContentPermissions(mockEntityService.Object, nodeId, permissionsForPath); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(); + Mock mockEntityService = CreateMockEntityService(); + ContentPermissions contentPermissions = CreateContentPermissions(mockEntityService.Object, nodeId, permissionsForPath); return new ContentPermissionsQueryStringHandler(mockBackOfficeSecurityAccessor.Object, httpContextAccessor, mockEntityService.Object, contentPermissions); } @@ -190,17 +193,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var mockEntityService = new Mock(); mockEntityService - .Setup(x => x.GetId(It.Is(y => y == NodeUdi))) + .Setup(x => x.GetId(It.Is(y => y == s_nodeUdi))) .Returns(Attempt.Succeed(NodeId)); mockEntityService - .Setup(x => x.GetId(It.Is(y => y == NodeGuid), It.Is(y => y == UmbracoObjectTypes.Document))) + .Setup(x => x.GetId(It.Is(y => y == s_nodeGuid), It.Is(y => y == UmbracoObjectTypes.Document))) .Returns(Attempt.Succeed(NodeId)); return mockEntityService; } private static Mock CreateMockBackOfficeSecurityAccessor() { - var user = CreateUser(); + User user = CreateUser(); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(user); var mockBackOfficeSecurityAccessor = new Mock(); @@ -208,11 +211,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser() - { - return new UserBuilder() + private static User CreateUser() => + new UserBuilder() .Build(); - } private static ContentPermissions CreateContentPermissions(IEntityService entityService, int nodeId, string[] permissionsForPath) { @@ -232,13 +233,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static IContent CreateContent(int nodeId) { - var contentType = ContentTypeBuilder.CreateBasicContentType(); + ContentType contentType = ContentTypeBuilder.CreateBasicContentType(); return ContentBuilder.CreateBasicContent(contentType, nodeId); } - private static void AssertContentCached(Mock mockHttpContextAccessor) - { + private static void AssertContentCached(Mock mockHttpContextAccessor) => Assert.AreEqual(NodeId, ((IContent)mockHttpContextAccessor.Object.HttpContext.Items[typeof(IContent).ToString()]).Id); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandlerTests.cs index ea6dd7f0bd..1ac74d99ba 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -21,8 +24,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Node_Id_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); - var sut = CreateHandler(NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); + ContentPermissionsResourceHandler sut = CreateHandler(NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -32,8 +35,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Content_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId); - var sut = CreateHandler(NodeId, new string[] { "A" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId); + ContentPermissionsResourceHandler sut = CreateHandler(NodeId, new string[] { "A" }); await sut.HandleAsync(authHandlerContext); @@ -43,8 +46,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Node_Id_Withou_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); - var sut = CreateHandler(NodeId, new string[] { "B" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); + ContentPermissionsResourceHandler sut = CreateHandler(NodeId, new string[] { "B" }); await sut.HandleAsync(authHandlerContext); @@ -54,8 +57,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Content_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId); - var sut = CreateHandler(NodeId, new string[] { "B" }); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId); + ContentPermissionsResourceHandler sut = CreateHandler(NodeId, new string[] { "B" }); await sut.HandleAsync(authHandlerContext); @@ -66,9 +69,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new ContentPermissionsResourceRequirement(); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var content = CreateContent(nodeId); - var permissions = new List { 'A' }.AsReadOnly(); - var resource = createWithNodeId + IContent content = CreateContent(nodeId); + System.Collections.ObjectModel.ReadOnlyCollection permissions = new List { 'A' }.AsReadOnly(); + ContentPermissionsResource resource = createWithNodeId ? new ContentPermissionsResource(content, nodeId, permissions) : new ContentPermissionsResource(content, permissions); return new AuthorizationHandlerContext(new List { requirement }, user, resource); @@ -76,20 +79,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static IContent CreateContent(int nodeId) { - var contentType = ContentTypeBuilder.CreateBasicContentType(); + ContentType contentType = ContentTypeBuilder.CreateBasicContentType(); return ContentBuilder.CreateBasicContent(contentType, nodeId); } private ContentPermissionsResourceHandler CreateHandler(int nodeId, string[] permissionsForPath) { - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(); - var contentPermissions = CreateContentPermissions(nodeId, permissionsForPath); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(); + ContentPermissions contentPermissions = CreateContentPermissions(nodeId, permissionsForPath); return new ContentPermissionsResourceHandler(mockBackOfficeSecurityAccessor.Object, contentPermissions); } private static Mock CreateMockBackOfficeSecurityAccessor() { - var user = CreateUser(); + User user = CreateUser(); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(user); var mockBackOfficeSecurityAccessor = new Mock(); @@ -97,11 +100,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser() - { - return new UserBuilder() + private static User CreateUser() => + new UserBuilder() .Build(); - } private static ContentPermissions CreateContentPermissions(int nodeId, string[] permissionsForPath) { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandlerTests.cs index 602843b128..830954aaf6 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -6,7 +9,6 @@ using Moq; using NUnit.Framework; using Umbraco.Web.BackOffice.Authorization; using Umbraco.Web.BackOffice.Security; -using Umbraco.Web.Common.Security; namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { @@ -15,8 +17,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task With_Deny_Local_Login_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(denyLocalLogin: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + DenyLocalLoginHandler sut = CreateHandler(denyLocalLogin: true); await sut.HandleAsync(authHandlerContext); @@ -26,8 +28,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Without_Deny_Local_Login_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + DenyLocalLoginHandler sut = CreateHandler(); await sut.HandleAsync(authHandlerContext); @@ -38,13 +40,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new DenyLocalLoginRequirement(); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } private DenyLocalLoginHandler CreateHandler(bool denyLocalLogin = false) { - var mockBackOfficeExternalLoginProviders = CreateMockBackOfficeExternalLoginProviders(denyLocalLogin); + Mock mockBackOfficeExternalLoginProviders = CreateMockBackOfficeExternalLoginProviders(denyLocalLogin); return new DenyLocalLoginHandler(mockBackOfficeExternalLoginProviders.Object); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandlerTests.cs index 80e2cddb75..fb2eec93c8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandlerTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; @@ -21,15 +24,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { private const string QueryStringName = "id"; private const int NodeId = 1000; - private static readonly Guid NodeGuid = Guid.NewGuid(); - private static readonly Udi NodeUdi = UdiParser.Parse($"umb://document/{NodeGuid.ToString().ToLowerInvariant().Replace("-", string.Empty)}"); + private static readonly Guid s_nodeGuid = Guid.NewGuid(); + private static readonly Udi s_nodeUdi = UdiParser.Parse($"umb://document/{s_nodeGuid.ToString().ToLowerInvariant().Replace("-", string.Empty)}"); [Test] public async Task Node_Id_Missing_From_QueryString_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringName: "xxx"); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringName: "xxx"); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -39,9 +42,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Integer_Id_From_QueryString_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -52,9 +55,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Integer_Id_From_QueryString_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, startMediaId: 1001); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeId.ToString()); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, startMediaId: 1001); await sut.HandleAsync(authHandlerContext); @@ -65,9 +68,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Udi_Id_From_QueryString_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeUdi.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeUdi.ToString()); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -78,9 +81,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Udi_Id_From_QueryString_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeUdi.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, startMediaId: 1001); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeUdi.ToString()); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, startMediaId: 1001); await sut.HandleAsync(authHandlerContext); @@ -91,9 +94,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Guid_Id_From_QueryString_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeGuid.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeGuid.ToString()); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -104,9 +107,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Guid_Id_From_QueryString_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: NodeGuid.ToString()); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, startMediaId: 1001); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: s_nodeGuid.ToString()); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId, startMediaId: 1001); await sut.HandleAsync(authHandlerContext); @@ -117,9 +120,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Node_Invalid_Id_From_QueryString_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: "invalid"); - var sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue: "invalid"); + MediaPermissionsQueryStringHandler sut = CreateHandler(mockHttpContextAccessor.Object, NodeId); await sut.HandleAsync(authHandlerContext); @@ -130,7 +133,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new MediaPermissionsQueryStringRequirement(QueryStringName); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } @@ -152,9 +155,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private MediaPermissionsQueryStringHandler CreateHandler(IHttpContextAccessor httpContextAccessor, int nodeId, int startMediaId = -1) { - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(startMediaId); - var mockEntityService = CreateMockEntityService(); - var mediaPermissions = CreateMediaPermissions(mockEntityService.Object, nodeId); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(startMediaId); + Mock mockEntityService = CreateMockEntityService(); + MediaPermissions mediaPermissions = CreateMediaPermissions(mockEntityService.Object, nodeId); return new MediaPermissionsQueryStringHandler(mockBackOfficeSecurityAccessor.Object, httpContextAccessor, mockEntityService.Object, mediaPermissions); } @@ -162,17 +165,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var mockEntityService = new Mock(); mockEntityService - .Setup(x => x.GetId(It.Is(y => y == NodeUdi))) + .Setup(x => x.GetId(It.Is(y => y == s_nodeUdi))) .Returns(Attempt.Succeed(NodeId)); mockEntityService - .Setup(x => x.GetId(It.Is(y => y == NodeGuid), It.Is(y => y == UmbracoObjectTypes.Document))) + .Setup(x => x.GetId(It.Is(y => y == s_nodeGuid), It.Is(y => y == UmbracoObjectTypes.Document))) .Returns(Attempt.Succeed(NodeId)); return mockEntityService; } private static Mock CreateMockBackOfficeSecurityAccessor(int startMediaId) { - var user = CreateUser(startMediaId); + User user = CreateUser(startMediaId); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(user); var mockBackOfficeSecurityAccessor = new Mock(); @@ -180,12 +183,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser(int startMediaId) - { - return new UserBuilder() + private static User CreateUser(int startMediaId) => + new UserBuilder() .WithStartMediaId(startMediaId) .Build(); - } private static MediaPermissions CreateMediaPermissions(IEntityService entityService, int nodeId) { @@ -199,13 +200,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static IMedia CreateMedia(int nodeId) { - var mediaType = MediaTypeBuilder.CreateSimpleMediaType("image", "Image"); + MediaType mediaType = MediaTypeBuilder.CreateSimpleMediaType("image", "Image"); return MediaBuilder.CreateSimpleMedia(mediaType, "Test image", -1, nodeId); } - private static void AssertMediaCached(Mock mockHttpContextAccessor) - { + private static void AssertMediaCached(Mock mockHttpContextAccessor) => Assert.AreEqual(NodeId, ((IMedia)mockHttpContextAccessor.Object.HttpContext.Items[typeof(IMedia).ToString()]).Id); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandlerTests.cs index 19e2a6f06c..de4a7c0b52 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -20,8 +23,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Node_Id_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); - var sut = CreateHandler(NodeId); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); + MediaPermissionsResourceHandler sut = CreateHandler(NodeId); await sut.HandleAsync(authHandlerContext); @@ -31,8 +34,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Media_With_Permission_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId); - var sut = CreateHandler(NodeId); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId); + MediaPermissionsResourceHandler sut = CreateHandler(NodeId); await sut.HandleAsync(authHandlerContext); @@ -42,8 +45,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Node_Id_Withou_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); - var sut = CreateHandler(NodeId, startMediaId: 1001); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId, createWithNodeId: true); + MediaPermissionsResourceHandler sut = CreateHandler(NodeId, startMediaId: 1001); await sut.HandleAsync(authHandlerContext); @@ -53,8 +56,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Resource_With_Media_Without_Permission_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(NodeId); - var sut = CreateHandler(NodeId, startMediaId: 1001); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(NodeId); + MediaPermissionsResourceHandler sut = CreateHandler(NodeId, startMediaId: 1001); await sut.HandleAsync(authHandlerContext); @@ -65,8 +68,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new MediaPermissionsResourceRequirement(); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var media = CreateMedia(nodeId); - var resource = createWithNodeId + IMedia media = CreateMedia(nodeId); + MediaPermissionsResource resource = createWithNodeId ? new MediaPermissionsResource(nodeId) : new MediaPermissionsResource(media); return new AuthorizationHandlerContext(new List { requirement }, user, resource); @@ -74,20 +77,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static IMedia CreateMedia(int nodeId) { - var mediaType = MediaTypeBuilder.CreateSimpleMediaType("image", "Image"); + MediaType mediaType = MediaTypeBuilder.CreateSimpleMediaType("image", "Image"); return MediaBuilder.CreateSimpleMedia(mediaType, "Test image", -1, nodeId); } private MediaPermissionsResourceHandler CreateHandler(int nodeId, int startMediaId = -1) { - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(startMediaId); - var contentPermissions = CreateMediaPermissions(nodeId, new string[0]); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(startMediaId); + MediaPermissions contentPermissions = CreateMediaPermissions(nodeId); return new MediaPermissionsResourceHandler(mockBackOfficeSecurityAccessor.Object, contentPermissions); } private static Mock CreateMockBackOfficeSecurityAccessor(int startMediaId) { - var user = CreateUser(startMediaId); + User user = CreateUser(startMediaId); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(user); var mockBackOfficeSecurityAccessor = new Mock(); @@ -95,14 +98,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser(int startMediaId) - { - return new UserBuilder() + private static User CreateUser(int startMediaId) => + new UserBuilder() .WithStartMediaId(startMediaId) .Build(); - } - private static MediaPermissions CreateMediaPermissions(int nodeId, string[] permissionsForPath) + private static MediaPermissions CreateMediaPermissions(int nodeId) { var mockMediaService = new Mock(); mockMediaService diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/SectionHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/SectionHandlerTests.cs index fb4bb0192e..0435299081 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/SectionHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/SectionHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -17,8 +20,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Unauthorized_User_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + SectionHandler sut = CreateHandler(); await sut.HandleAsync(authHandlerContext); @@ -28,8 +31,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_With_Section_Access_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(userIsAuthorized: true, userCanAccessContentSection: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + SectionHandler sut = CreateHandler(userIsAuthorized: true, userCanAccessContentSection: true); await sut.HandleAsync(authHandlerContext); @@ -39,8 +42,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Without_Section_Access_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(userIsAuthorized: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + SectionHandler sut = CreateHandler(userIsAuthorized: true); await sut.HandleAsync(authHandlerContext); @@ -51,20 +54,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new SectionRequirement(Constants.Applications.Content, Constants.Applications.Media); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } private SectionHandler CreateHandler(bool userIsAuthorized = false, bool userCanAccessContentSection = false) { - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(userIsAuthorized, userCanAccessContentSection); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(userIsAuthorized, userCanAccessContentSection); return new SectionHandler(mockBackOfficeSecurityAccessor.Object); } - + private static Mock CreateMockBackOfficeSecurityAccessor(bool userIsAuthorized, bool userCanAccessContentSection) { - var user = CreateUser(); + User user = CreateUser(); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(userIsAuthorized ? user : null); mockBackOfficeSecurity @@ -78,10 +81,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser() - { - return new UserBuilder() + private static User CreateUser() => + new UserBuilder() .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/TreeHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/TreeHandlerTests.cs index 8c688d10d3..7d91469379 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/TreeHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/TreeHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -23,8 +26,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Unauthorized_User_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + TreeHandler sut = CreateHandler(); await sut.HandleAsync(authHandlerContext); @@ -34,8 +37,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_With_Access_To_Tree_Section_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(userIsAuthorized: true, userCanAccessContentSection: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + TreeHandler sut = CreateHandler(userIsAuthorized: true, userCanAccessContentSection: true); await sut.HandleAsync(authHandlerContext); @@ -45,8 +48,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Without_Access_To_Tree_Section_Is_Not_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(userIsAuthorized: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + TreeHandler sut = CreateHandler(userIsAuthorized: true); await sut.HandleAsync(authHandlerContext); @@ -57,14 +60,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new TreeRequirement(Tree1Alias, Tree2Alias); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } private TreeHandler CreateHandler(bool userIsAuthorized = false, bool userCanAccessContentSection = false) { - var mockTreeService = CreateMockTreeService(); - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(userIsAuthorized, userCanAccessContentSection); + Mock mockTreeService = CreateMockTreeService(); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(userIsAuthorized, userCanAccessContentSection); return new TreeHandler(mockTreeService.Object, mockBackOfficeSecurityAccessor.Object); } @@ -81,17 +84,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockTreeService; } - private static Tree CreateTree(string alias, string sectionAlias) - { - return new TreeBuilder() + private static Tree CreateTree(string alias, string sectionAlias) => + new TreeBuilder() .WithAlias(alias) .WithSectionAlias(sectionAlias) .Build(); - } private static Mock CreateMockBackOfficeSecurityAccessor(bool userIsAuthorized, bool userCanAccessContentSection) { - var user = CreateUser(); + User user = CreateUser(); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(userIsAuthorized ? user : null); mockBackOfficeSecurity @@ -105,10 +106,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser() - { - return new UserBuilder() + private static User CreateUser() => new UserBuilder() .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/UserGroupHandlerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/UserGroupHandlerTests.cs index e9416a3982..33f6dca02f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/UserGroupHandlerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Authorization/UserGroupHandlerTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -30,8 +33,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Missing_QueryString_Value_Is_Authorized() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + UserGroupHandler sut = CreateHandler(); await sut.HandleAsync(authHandlerContext); @@ -41,8 +44,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task Admin_User_Is_Authorised() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: Group1Id.ToString(), userIsAdmin: true); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + UserGroupHandler sut = CreateHandler(queryStringValue: Group1Id.ToString(), userIsAdmin: true); await sut.HandleAsync(authHandlerContext); @@ -52,8 +55,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Matching_Single_Requested_Group_Id_Is_Authorised() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: Group1Id.ToString()); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + UserGroupHandler sut = CreateHandler(queryStringValue: Group1Id.ToString()); await sut.HandleAsync(authHandlerContext); @@ -63,8 +66,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Matching_One_Of_Requested_Group_Ids_Is_Authorised() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: $"{Group1Id},{Group2Id}"); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + UserGroupHandler sut = CreateHandler(queryStringValue: $"{Group1Id},{Group2Id}"); await sut.HandleAsync(authHandlerContext); @@ -74,8 +77,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Not_Matching_Single_Requested_Group_Id_Is_Not_Authorised() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: Group2Id.ToString()); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + UserGroupHandler sut = CreateHandler(queryStringValue: Group2Id.ToString()); await sut.HandleAsync(authHandlerContext); @@ -85,8 +88,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization [Test] public async Task User_Not_Matching_Any_Of_Requested_Group_Ids_Is_Not_Authorised() { - var authHandlerContext = CreateAuthorizationHandlerContext(); - var sut = CreateHandler(queryStringValue: $"{Group2Id},{Group3Id}"); + AuthorizationHandlerContext authHandlerContext = CreateAuthorizationHandlerContext(); + UserGroupHandler sut = CreateHandler(queryStringValue: $"{Group2Id},{Group3Id}"); await sut.HandleAsync(authHandlerContext); @@ -97,21 +100,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization { var requirement = new UserGroupRequirement(QueryStringName); var user = new ClaimsPrincipal(new ClaimsIdentity(new List())); - var resource = new object(); + object resource = new object(); return new AuthorizationHandlerContext(new List { requirement }, user, resource); } private UserGroupHandler CreateHandler(string queryStringValue = "", bool userIsAdmin = false) { - var mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue); + Mock mockHttpContextAccessor = CreateMockHttpContextAccessor(queryStringValue); - var mockUserService = CreateMockUserService(); + Mock mockUserService = CreateMockUserService(); var mockContentService = new Mock(); var mockMediaService = new Mock(); var mockEntityService = new Mock(); - var mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(userIsAdmin); + Mock mockBackOfficeSecurityAccessor = CreateMockBackOfficeSecurityAccessor(userIsAdmin); return new UserGroupHandler(mockHttpContextAccessor.Object, mockUserService.Object, mockContentService.Object, mockMediaService.Object, mockEntityService.Object, mockBackOfficeSecurityAccessor.Object); } @@ -151,7 +154,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization private static Mock CreateMockBackOfficeSecurityAccessor(bool userIsAdmin) { - var user = CreateUser(userIsAdmin); + User user = CreateUser(userIsAdmin); var mockBackOfficeSecurity = new Mock(); mockBackOfficeSecurity.SetupGet(x => x.CurrentUser).Returns(user); var mockBackOfficeSecurityAccessor = new Mock(); @@ -159,21 +162,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Authorization return mockBackOfficeSecurityAccessor; } - private static User CreateUser(bool isAdmin = false) - { - return new UserBuilder() + private static User CreateUser(bool isAdmin = false) => + new UserBuilder() .AddUserGroup() .WithAlias(isAdmin ? Constants.Security.AdminGroupAlias : Group1Alias) .Done() .Build(); - } - private IUserGroup CreateUserGroup(int id, string alias) - { - return new UserGroupBuilder() + private IUserGroup CreateUserGroup(int id, string alias) => + new UserGroupBuilder() .WithId(id) .WithAlias(alias) .Build(); - } } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/AdminUsersHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/AdminUsersHandler.cs index 4d9f47c7dd..a7733b25bd 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/AdminUsersHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/AdminUsersHandler.cs @@ -1,7 +1,12 @@ -using System.Linq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; using Umbraco.Core; using Umbraco.Core.Security; using Umbraco.Core.Services; @@ -14,25 +19,34 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class AdminUsersHandler : MustSatisfyRequirementAuthorizationHandler { - private readonly IHttpContextAccessor _httpContextAcessor; + private readonly IHttpContextAccessor _httpContextAccessor; private readonly IUserService _userService; - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly UserEditorAuthorizationHelper _userEditorAuthorizationHelper; - public AdminUsersHandler(IHttpContextAccessor httpContextAcessor, - IUserService userService, - IBackOfficeSecurityAccessor backofficeSecurityAccessor, - UserEditorAuthorizationHelper userEditorAuthorizationHelper) + /// + /// Initializes a new instance of the class. + /// + /// Accessor for the HTTP context of the current request. + /// Service for user related operations. + /// Accessor for back-office security. + /// Helper for user authorization checks. + public AdminUsersHandler( + IHttpContextAccessor httpContextAccessor, + IUserService userService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + UserEditorAuthorizationHelper userEditorAuthorizationHelper) { - _httpContextAcessor = httpContextAcessor; + _httpContextAccessor = httpContextAccessor; _userService = userService; - _backofficeSecurityAccessor = backofficeSecurityAccessor; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _userEditorAuthorizationHelper = userEditorAuthorizationHelper; } + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, AdminUsersRequirement requirement) { - var queryString = _httpContextAcessor.HttpContext?.Request.Query[requirement.QueryStringName]; + StringValues? queryString = _httpContextAccessor.HttpContext?.Request.Query[requirement.QueryStringName]; if (!queryString.HasValue || !queryString.Value.Any()) { // Must succeed this requirement since we cannot process it. @@ -46,12 +60,13 @@ namespace Umbraco.Web.BackOffice.Authorization } else { - var ids = _httpContextAcessor.HttpContext.Request.Query.Where(x => x.Key == requirement.QueryStringName).ToList(); + var ids = _httpContextAccessor.HttpContext.Request.Query.Where(x => x.Key == requirement.QueryStringName).ToList(); if (ids.Count == 0) { // Must succeed this requirement since we cannot process it. return Task.FromResult(true); } + userIds = ids .Select(x => x.Value.ToString()) .Select(x => x.TryConvertTo()).Where(x => x.Success).Select(x => x.Result).ToArray(); @@ -63,8 +78,8 @@ namespace Umbraco.Web.BackOffice.Authorization return Task.FromResult(true); } - var users = _userService.GetUsersById(userIds); - var isAuth = users.All(user => _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, user, null, null, null) != false); + IEnumerable users = _userService.GetUsersById(userIds); + var isAuth = users.All(user => _userEditorAuthorizationHelper.IsAuthorized(_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, user, null, null, null) != false); return Task.FromResult(isAuth); } diff --git a/src/Umbraco.Web.BackOffice/Authorization/AdminUsersRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/AdminUsersRequirement.cs index d5cd5d80dc..e8a56ee8cb 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/AdminUsersRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/AdminUsersRequirement.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { @@ -7,11 +10,15 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class AdminUsersRequirement : IAuthorizationRequirement { - public AdminUsersRequirement(string queryStringName = "id") - { - QueryStringName = queryStringName; - } + /// + /// Initializes a new instance of the class. + /// + /// Query string name from which to authorize values. + public AdminUsersRequirement(string queryStringName = "id") => QueryStringName = queryStringName; + /// + /// Gets the query string name from which to authorize values. + /// public string QueryStringName { get; } } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/BackOfficeHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/BackOfficeHandler.cs index f536483921..0d7c28f314 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/BackOfficeHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/BackOfficeHandler.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Umbraco.Core; diff --git a/src/Umbraco.Web.BackOffice/Authorization/BackOfficeRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/BackOfficeRequirement.cs index d1b13efe2c..17c5f6e02a 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/BackOfficeRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/BackOfficeRequirement.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { @@ -7,11 +10,15 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class BackOfficeRequirement : IAuthorizationRequirement { - public BackOfficeRequirement(bool requireApproval = true) - { - RequireApproval = requireApproval; - } + /// + /// Initializes a new instance of the class. + /// + /// Flag for whether back-office user approval is required. + public BackOfficeRequirement(bool requireApproval = true) => RequireApproval = requireApproval; + /// + /// Gets a value indicating whether back-office user approval is required. + /// public bool RequireApproval { get; } } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandler.cs index 32eebaa105..bb6dc919be 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchHandler.cs @@ -1,7 +1,10 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -11,7 +14,7 @@ using Umbraco.Core.Services; namespace Umbraco.Web.BackOffice.Authorization { /// - /// The user must have access to all descendant nodes of the content item in order to continue + /// The user must have access to all descendant nodes of the content item in order to continue. /// public class ContentPermissionsPublishBranchHandler : MustSatisfyRequirementAuthorizationHandler { @@ -19,6 +22,12 @@ namespace Umbraco.Web.BackOffice.Authorization private readonly ContentPermissions _contentPermissions; private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + /// + /// Initializes a new instance of the class. + /// + /// Service for entity operations. + /// per for user content authorization checks. + /// Accessor for back-office security. public ContentPermissionsPublishBranchHandler( IEntityService entityService, ContentPermissions contentPermissions, @@ -29,9 +38,10 @@ namespace Umbraco.Web.BackOffice.Authorization _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, ContentPermissionsPublishBranchRequirement requirement, IContent resource) { - var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; + Core.Models.Membership.IUser currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; var denied = new List(); var page = 0; @@ -40,16 +50,22 @@ namespace Umbraco.Web.BackOffice.Authorization while (page * pageSize < total) { - var descendants = _entityService.GetPagedDescendants(resource.Id, UmbracoObjectTypes.Document, page++, pageSize, out total, - // Order by shallowest to deepest, this allows us to check permissions from top to bottom so we can exit - // early if a permission higher up fails. - ordering: Ordering.By("path", Direction.Ascending)); + // Order descendents by shallowest to deepest, this allows us to check permissions from top to bottom so we can exit + // early if a permission higher up fails. + IEnumerable descendants = _entityService.GetPagedDescendants( + resource.Id, + UmbracoObjectTypes.Document, + page++, + pageSize, + out total, + ordering: Ordering.By("path", Direction.Ascending)); - foreach (var c in descendants) + foreach (IEntitySlim c in descendants) { // If this item's path has already been denied or if the user doesn't have access to it, add to the deny list. - if (denied.Any(x => c.Path.StartsWith($"{x.Path},")) - || (_contentPermissions.CheckPermissions(c, + if (denied.Any(x => c.Path.StartsWith($"{x.Path},")) || + (_contentPermissions.CheckPermissions( + c, currentUser, requirement.Permission) == ContentPermissions.ContentAccess.Denied)) { diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchRequirement.cs index 541f861f0d..cfa6f512a2 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsPublishBranchRequirement.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { @@ -7,11 +10,15 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class ContentPermissionsPublishBranchRequirement : IAuthorizationRequirement { - public ContentPermissionsPublishBranchRequirement(char permission) - { - Permission = permission; - } + /// + /// Initializes a new instance of the class. + /// + /// Permission to check. + public ContentPermissionsPublishBranchRequirement(char permission) => Permission = permission; + /// + /// Gets a value for the permission to check. + /// public char Permission { get; } } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandler.cs index 863cbdfc3c..caf94c1cec 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringHandler.cs @@ -1,8 +1,10 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; -using Umbraco.Core; +using Microsoft.Extensions.Primitives; using Umbraco.Core.Models; using Umbraco.Core.Security; using Umbraco.Core.Services; @@ -10,28 +12,33 @@ using Umbraco.Core.Services; namespace Umbraco.Web.BackOffice.Authorization { /// - /// Used to authorize if the user has the correct permission access to the content for the content id specified in a query string + /// Used to authorize if the user has the correct permission access to the content for the content id specified in a query string. /// public class ContentPermissionsQueryStringHandler : PermissionsQueryStringHandler { private readonly ContentPermissions _contentPermissions; + /// + /// Initializes a new instance of the class. + /// + /// Accessor for back-office security. + /// Accessor for the HTTP context of the current request. + /// Service for entity operations. + /// Helper for content authorization checks. public ContentPermissionsQueryStringHandler( - IBackOfficeSecurityAccessor backofficeSecurityAccessor, - IHttpContextAccessor httpContextAccessor, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IHttpContextAccessor httpContextAccessor, IEntityService entityService, ContentPermissions contentPermissions) - : base(backofficeSecurityAccessor, httpContextAccessor, entityService) - { - _contentPermissions = contentPermissions; - } + : base(backOfficeSecurityAccessor, httpContextAccessor, entityService) => _contentPermissions = contentPermissions; + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, ContentPermissionsQueryStringRequirement requirement) { int nodeId; if (requirement.NodeId.HasValue == false) { - if (!HttpContextAccessor.HttpContext.Request.Query.TryGetValue(requirement.QueryStringName, out var routeVal)) + if (!HttpContextAccessor.HttpContext.Request.Query.TryGetValue(requirement.QueryStringName, out StringValues routeVal)) { // Must succeed this requirement since we cannot process it return Task.FromResult(true); @@ -52,8 +59,9 @@ namespace Umbraco.Web.BackOffice.Authorization nodeId = requirement.NodeId.Value; } - var permissionResult = _contentPermissions.CheckPermissions(nodeId, - BackofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, + ContentPermissions.ContentAccess permissionResult = _contentPermissions.CheckPermissions( + nodeId, + BackOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, out IContent contentItem, new[] { requirement.PermissionToCheck }); diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringRequirement.cs index 2d558c569c..ee386295c2 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringRequirement.cs @@ -1,19 +1,20 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { - /// /// An authorization requirement for /// public class ContentPermissionsQueryStringRequirement : IAuthorizationRequirement { - /// - /// Create an authorization requirement for a specific node id + /// Initializes a new instance of the class for a specific node id. /// - /// - /// + /// The node Id. + /// The permission to authorize the current user against. public ContentPermissionsQueryStringRequirement(int nodeId, char permissionToCheck) { NodeId = nodeId; @@ -21,18 +22,30 @@ namespace Umbraco.Web.BackOffice.Authorization } /// - /// Create an authorization requirement for a node id based on a query string parameter + /// Initializes a new instance of the class for a + /// node id based on a query string parameter. /// - /// - /// + /// The querystring parameter name. + /// The permission to authorize the current user against. public ContentPermissionsQueryStringRequirement(char permissionToCheck, string paramName = "id") { QueryStringName = paramName; PermissionToCheck = permissionToCheck; } + /// + /// Gets the specific node Id. + /// public int? NodeId { get; } + + /// + /// Gets the querystring parameter name. + /// public string QueryStringName { get; } + + /// + /// Gets the permission to authorize the current user against. + /// public char PermissionToCheck { get; } } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResource.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResource.cs index 0ec92c7af2..b544d28eb5 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResource.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResource.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Web.BackOffice.Authorization @@ -8,27 +11,54 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class ContentPermissionsResource { + /// + /// Initializes a new instance of the class. + /// + /// The content. + /// The permission to authorize. public ContentPermissionsResource(IContent content, char permissionToCheck) { PermissionsToCheck = new List { permissionToCheck }; Content = content; } - public ContentPermissionsResource(IContent content, IReadOnlyList permissionToCheck) + /// + /// Initializes a new instance of the class. + /// + /// The content. + /// The collection of permissions to authorize. + public ContentPermissionsResource(IContent content, IReadOnlyList permissionsToCheck) { Content = content; - PermissionsToCheck = permissionToCheck; + PermissionsToCheck = permissionsToCheck; } - public ContentPermissionsResource(IContent content, int nodeId, IReadOnlyList permissionToCheck) + /// + /// Initializes a new instance of the class. + /// + /// The content. + /// The node Id. + /// The collection of permissions to authorize. + public ContentPermissionsResource(IContent content, int nodeId, IReadOnlyList permissionsToCheck) { Content = content; NodeId = nodeId; - PermissionsToCheck = permissionToCheck; + PermissionsToCheck = permissionsToCheck; } - public int? NodeId { get; } + /// + /// Gets the node Id. + /// + public int? NodeId { get; } + + /// + /// Gets the collection of permissions to authorize. + /// public IReadOnlyList PermissionsToCheck { get; } + + /// + /// Gets the content. + /// public IContent Content { get; } } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandler.cs index 3bade3c5fe..24e5e5b6ac 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceHandler.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Umbraco.Core.Models; using Umbraco.Core.Security; @@ -10,28 +13,34 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class ContentPermissionsResourceHandler : MustSatisfyRequirementAuthorizationHandler { - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly ContentPermissions _contentPermissions; + /// + /// Initializes a new instance of the class. + /// + /// Accessor for back-office security. + /// Helper for content authorization checks. public ContentPermissionsResourceHandler( - IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, ContentPermissions contentPermissions) { - _backofficeSecurityAccessor = backofficeSecurityAccessor; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _contentPermissions = contentPermissions; } + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, ContentPermissionsResourceRequirement requirement, ContentPermissionsResource resource) { - var permissionResult = resource.NodeId.HasValue + ContentPermissions.ContentAccess permissionResult = resource.NodeId.HasValue ? _contentPermissions.CheckPermissions( resource.NodeId.Value, - _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, + _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, out IContent _, resource.PermissionsToCheck) : _contentPermissions.CheckPermissions( resource.Content, - _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, + _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, resource.PermissionsToCheck); return Task.FromResult(permissionResult != ContentPermissions.ContentAccess.Denied); diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceRequirement.cs index 22b69c93da..fcf2838f33 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsResourceRequirement.cs @@ -1,9 +1,10 @@ -using Microsoft.AspNetCore.Authorization; -using Umbraco.Web.Actions; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { - /// /// An authorization requirement for /// diff --git a/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandler.cs index 96341c5b1f..6c09d00ef7 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandler.cs @@ -1,26 +1,27 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Umbraco.Web.BackOffice.Security; -using Umbraco.Web.Common.Security; namespace Umbraco.Web.BackOffice.Authorization { - /// - /// Ensures the resource cannot be accessed if returns true + /// Ensures the resource cannot be accessed if returns true. /// public class DenyLocalLoginHandler : MustSatisfyRequirementAuthorizationHandler { private readonly IBackOfficeExternalLoginProviders _externalLogins; - public DenyLocalLoginHandler(IBackOfficeExternalLoginProviders externalLogins) - { - _externalLogins = externalLogins; - } + /// + /// Initializes a new instance of the class. + /// + /// Provides access to instances. + public DenyLocalLoginHandler(IBackOfficeExternalLoginProviders externalLogins) => _externalLogins = externalLogins; - protected override Task IsAuthorized(AuthorizationHandlerContext context, DenyLocalLoginRequirement requirement) - { - return Task.FromResult(!_externalLogins.HasDenyLocalLogin()); - } + /// + protected override Task IsAuthorized(AuthorizationHandlerContext context, DenyLocalLoginRequirement requirement) => + Task.FromResult(!_externalLogins.HasDenyLocalLogin()); } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginRequirement.cs index a4f3a7e306..b295c74621 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginRequirement.cs @@ -1,9 +1,12 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { /// - /// Marker requirement for the + /// Marker requirement for the . /// public class DenyLocalLoginRequirement : IAuthorizationRequirement { diff --git a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandler.cs index d6ef44cef1..e55384ab15 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringHandler.cs @@ -1,29 +1,41 @@ -using System.Threading.Tasks; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; using Umbraco.Core.Models; using Umbraco.Core.Security; using Umbraco.Core.Services; namespace Umbraco.Web.BackOffice.Authorization { + /// + /// Used to authorize if the user has the correct permission access to the media for the media id specified in a query string. + /// public class MediaPermissionsQueryStringHandler : PermissionsQueryStringHandler { private readonly MediaPermissions _mediaPermissions; + /// + /// Initializes a new instance of the class. + /// + /// Accessor for back-office security. + /// Accessor for the HTTP context of the current request. + /// Service for entity operations. + /// Helper for media authorization checks. public MediaPermissionsQueryStringHandler( - IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IHttpContextAccessor httpContextAccessor, IEntityService entityService, MediaPermissions mediaPermissions) - : base(backofficeSecurityAccessor, httpContextAccessor, entityService) - { - _mediaPermissions = mediaPermissions; - } + : base(backOfficeSecurityAccessor, httpContextAccessor, entityService) => _mediaPermissions = mediaPermissions; + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, MediaPermissionsQueryStringRequirement requirement) { - if (!HttpContextAccessor.HttpContext.Request.Query.TryGetValue(requirement.QueryStringName, out var routeVal)) + if (!HttpContextAccessor.HttpContext.Request.Query.TryGetValue(requirement.QueryStringName, out StringValues routeVal)) { // Must succeed this requirement since we cannot process it. return Task.FromResult(true); @@ -37,10 +49,10 @@ namespace Umbraco.Web.BackOffice.Authorization return Task.FromResult(true); } - var permissionResult = _mediaPermissions.CheckPermissions( - BackofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, + MediaPermissions.MediaAccess permissionResult = _mediaPermissions.CheckPermissions( + BackOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, nodeId, - out var mediaItem); + out IMedia mediaItem); if (mediaItem != null) { @@ -53,6 +65,6 @@ namespace Umbraco.Web.BackOffice.Authorization MediaPermissions.MediaAccess.Denied => Task.FromResult(false), _ => Task.FromResult(true), }; - } + } } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringRequirement.cs index c7b62a570f..14d9d77834 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsQueryStringRequirement.cs @@ -1,14 +1,24 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { + /// + /// An authorization requirement for + /// public class MediaPermissionsQueryStringRequirement : IAuthorizationRequirement { - public MediaPermissionsQueryStringRequirement(string paramName) - { - QueryStringName = paramName; - } + /// + /// Initializes a new instance of the class. + /// + /// Querystring paramter name. + public MediaPermissionsQueryStringRequirement(string paramName) => QueryStringName = paramName; + /// + /// Gets the querystring paramter name. + /// public string QueryStringName { get; } } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResource.cs b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResource.cs index 5b1ed92f5f..267fbc2b90 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResource.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResource.cs @@ -1,4 +1,7 @@ -using Umbraco.Core.Models; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core.Models; namespace Umbraco.Web.BackOffice.Authorization { diff --git a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandler.cs index 6c5280a19c..613aaaa8d5 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceHandler.cs @@ -1,36 +1,45 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Umbraco.Core.Models; using Umbraco.Core.Security; namespace Umbraco.Web.BackOffice.Authorization { /// - /// Used to authorize if the user has the correct permission access to the content for the specified + /// Used to authorize if the user has the correct permission access to the content for the specified. /// public class MediaPermissionsResourceHandler : MustSatisfyRequirementAuthorizationHandler { - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly MediaPermissions _mediaPermissions; + /// + /// Initializes a new instance of the class. + /// + /// Accessor for back-office security. + /// Helper for media authorization checks. public MediaPermissionsResourceHandler( - IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, MediaPermissions mediaPermissions) { - _backofficeSecurityAccessor = backofficeSecurityAccessor; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _mediaPermissions = mediaPermissions; } + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, MediaPermissionsResourceRequirement requirement, MediaPermissionsResource resource) { - var permissionResult = resource.NodeId.HasValue + MediaPermissions.MediaAccess permissionResult = resource.NodeId.HasValue ? _mediaPermissions.CheckPermissions( - _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, + _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser, resource.NodeId.Value, out _) : _mediaPermissions.CheckPermissions( resource.Media, - _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser); + _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser); return Task.FromResult(permissionResult != MediaPermissions.MediaAccess.Denied); } diff --git a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceRequirement.cs index 3087e4b258..b615a81709 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/MediaPermissionsResourceRequirement.cs @@ -1,13 +1,14 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { - /// /// An authorization requirement for /// public class MediaPermissionsResourceRequirement : IAuthorizationRequirement { - } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/MustSatisfyRequirementAuthorizationHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/MustSatisfyRequirementAuthorizationHandler.cs index 98baacf5ee..b990906942 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/MustSatisfyRequirementAuthorizationHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/MustSatisfyRequirementAuthorizationHandler.cs @@ -1,18 +1,23 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { /// - /// Abstract handler that must satisfy the requirement so Succeed or Fail will be called no matter what + /// Abstract handler that must satisfy the requirement so Succeed or Fail will be called no matter what. /// - /// + /// Authorization requirement. /// /// aspnetcore Authz handlers are not required to satisfy the requirement and generally don't explicitly call Fail when the requirement /// isn't satisfied, however in many simple cases explicitly calling Succeed or Fail is what we want which is what this class is used for. /// - public abstract class MustSatisfyRequirementAuthorizationHandler : AuthorizationHandler where T : IAuthorizationRequirement + public abstract class MustSatisfyRequirementAuthorizationHandler : AuthorizationHandler + where T : IAuthorizationRequirement { + /// protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, T requirement) { var isAuth = await IsAuthorized(context, requirement); @@ -29,23 +34,25 @@ namespace Umbraco.Web.BackOffice.Authorization /// /// Return true if the requirement is succeeded or ignored, return false if the requirement is explicitly not met /// - /// - /// - /// + /// The authorization context. + /// The authorization requirement. + /// True if request is authorized, false if not. protected abstract Task IsAuthorized(AuthorizationHandlerContext context, T requirement); } /// - /// Abstract handler that must satisfy the requirement so Succeed or Fail will be called no matter what + /// Abstract handler that must satisfy the requirement so Succeed or Fail will be called no matter what. /// - /// - /// + /// Authorization requirement. + /// Resource to authorize access to. /// /// aspnetcore Authz handlers are not required to satisfy the requirement and generally don't explicitly call Fail when the requirement /// isn't satisfied, however in many simple cases explicitly calling Succeed or Fail is what we want which is what this class is used for. /// - public abstract class MustSatisfyRequirementAuthorizationHandler : AuthorizationHandler where T : IAuthorizationRequirement + public abstract class MustSatisfyRequirementAuthorizationHandler : AuthorizationHandler + where T : IAuthorizationRequirement { + /// protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, T requirement, TResource resource) { var isAuth = await IsAuthorized(context, requirement, resource); @@ -62,9 +69,10 @@ namespace Umbraco.Web.BackOffice.Authorization /// /// Return true if the requirement is succeeded or ignored, return false if the requirement is explicitly not met /// - /// - /// - /// + /// The authorization context. + /// The authorization requirement. + /// The resource to authorize access to. + /// True if request is authorized, false if not. protected abstract Task IsAuthorized(AuthorizationHandlerContext context, T requirement, TResource resource); } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/PermissionsQueryStringHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/PermissionsQueryStringHandler.cs index 49f62dc2f9..47e7c6bb91 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/PermissionsQueryStringHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/PermissionsQueryStringHandler.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Umbraco.Core; @@ -8,25 +11,50 @@ using Umbraco.Core.Services; namespace Umbraco.Web.BackOffice.Authorization { + /// + /// Abstract base class providing common functionality for authorization checks based on querystrings. + /// + /// Authorization requirement public abstract class PermissionsQueryStringHandler : MustSatisfyRequirementAuthorizationHandler where T : IAuthorizationRequirement { + /// + /// Initializes a new instance of the class. + /// + /// Accessor for back-office security. + /// Accessor for the HTTP context of the current request. + /// Service for entity operations. public PermissionsQueryStringHandler( - IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IHttpContextAccessor httpContextAccessor, IEntityService entityService) { - BackofficeSecurityAccessor = backofficeSecurityAccessor; + BackOfficeSecurityAccessor = backOfficeSecurityAccessor; HttpContextAccessor = httpContextAccessor; EntityService = entityService; } - protected IBackOfficeSecurityAccessor BackofficeSecurityAccessor { get; set; } + /// + /// Gets or sets the instance. + /// + protected IBackOfficeSecurityAccessor BackOfficeSecurityAccessor { get; set; } + /// + /// Gets or sets the instance. + /// protected IHttpContextAccessor HttpContextAccessor { get; set; } + /// + /// Gets or sets the instance. + /// protected IEntityService EntityService { get; set; } + /// + /// Attempts to parse a node ID from a string representation found in a querystring value. + /// + /// Querystring value. + /// Output parsed Id. + /// True of node ID could be parased, false it not. protected bool TryParseNodeId(string argument, out int nodeId) { // If the argument is an int, it will parse and can be assigned to nodeId. @@ -38,12 +66,12 @@ namespace Umbraco.Web.BackOffice.Authorization nodeId = parsedId; return true; } - else if (UdiParser.TryParse(argument, true, out var udi)) + else if (UdiParser.TryParse(argument, true, out Udi udi)) { nodeId = EntityService.GetId(udi).Result; return true; } - else if (Guid.TryParse(argument, out var key)) + else if (Guid.TryParse(argument, out Guid key)) { nodeId = EntityService.GetId(key, UmbracoObjectTypes.Document).Result; return true; diff --git a/src/Umbraco.Web.BackOffice/Authorization/SectionHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/SectionHandler.cs index ffa6db220f..4620422742 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/SectionHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/SectionHandler.cs @@ -1,11 +1,13 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Umbraco.Core.Security; namespace Umbraco.Web.BackOffice.Authorization { - /// /// Ensures that the current user has access to the section /// @@ -14,18 +16,21 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class SectionHandler : MustSatisfyRequirementAuthorizationHandler { - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - public SectionHandler(IBackOfficeSecurityAccessor backofficeSecurityAccessor) - { - _backofficeSecurityAccessor = backofficeSecurityAccessor; - } + /// + /// Initializes a new instance of the class. + /// + /// Accessor for back-office security. + public SectionHandler(IBackOfficeSecurityAccessor backOfficeSecurityAccessor) => _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, SectionRequirement requirement) { - var authorized = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser != null - && requirement.SectionAliases.Any(app => _backofficeSecurityAccessor.BackOfficeSecurity.UserHasSectionAccess( - app, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser)); + var authorized = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser != null && + requirement.SectionAliases + .Any(app => _backOfficeSecurityAccessor.BackOfficeSecurity.UserHasSectionAccess( + app, _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser)); return Task.FromResult(authorized); } diff --git a/src/Umbraco.Web.BackOffice/Authorization/SectionRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/SectionRequirement.cs index 033eccdd18..a7e3aa5e2c 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/SectionRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/SectionRequirement.cs @@ -1,5 +1,8 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { @@ -9,10 +12,14 @@ namespace Umbraco.Web.BackOffice.Authorization public class SectionRequirement : IAuthorizationRequirement { /// - /// The aliases for sections that the user will need access to + /// Initializes a new instance of the class. + /// + /// Aliases for sections that the user will need access to. + public SectionRequirement(params string[] aliases) => SectionAliases = aliases; + + /// + /// Gets the aliases for sections that the user will need access to. /// public IReadOnlyCollection SectionAliases { get; } - - public SectionRequirement(params string[] aliases) => SectionAliases = aliases; } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/TreeHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/TreeHandler.cs index f151247850..f847f0981d 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/TreeHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/TreeHandler.cs @@ -1,14 +1,16 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Linq; -using Umbraco.Core; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Umbraco.Core; using Umbraco.Core.Security; using Umbraco.Web.Services; namespace Umbraco.Web.BackOffice.Authorization { - /// /// Ensures that the current user has access to the section for which the specified tree(s) belongs /// @@ -18,40 +20,35 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class TreeHandler : MustSatisfyRequirementAuthorizationHandler { - private readonly ITreeService _treeService; - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; /// - /// Constructor to set authorization to be based on a tree alias for which application security will be applied + /// Initializes a new instance of the class. /// - /// - /// - /// - /// If the user has access to the application that the treeAlias is specified in, they will be authorized. - /// Multiple trees may be specified. - /// - public TreeHandler(ITreeService treeService, IBackOfficeSecurityAccessor backofficeSecurityAccessor) + /// Service for section tree operations. + /// Accessor for back-office security. + public TreeHandler(ITreeService treeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) { _treeService = treeService ?? throw new ArgumentNullException(nameof(treeService)); - _backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor)); + _backOfficeSecurityAccessor = backOfficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backOfficeSecurityAccessor)); } + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, TreeRequirement requirement) { - var apps = requirement.TreeAliases.Select(x => _treeService - .GetByAlias(x)) + var apps = requirement.TreeAliases + .Select(x => _treeService.GetByAlias(x)) .WhereNotNull() .Select(x => x.SectionAlias) .Distinct() .ToArray(); - var isAuth = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser != null - && apps.Any(app => _backofficeSecurityAccessor.BackOfficeSecurity.UserHasSectionAccess( - app, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser)); + var isAuth = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser != null && + apps.Any(app => _backOfficeSecurityAccessor.BackOfficeSecurity.UserHasSectionAccess( + app, _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser)); return Task.FromResult(isAuth); } - } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/TreeRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/TreeRequirement.cs index 7261cee51b..1d8671d3c9 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/TreeRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/TreeRequirement.cs @@ -1,19 +1,25 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { - /// /// Authorization requirements for /// public class TreeRequirement : IAuthorizationRequirement { /// - /// The aliases for trees that the user will need access to + /// Initializes a new instance of the class. + /// + /// The aliases for trees that the user will need access to. + public TreeRequirement(params string[] aliases) => TreeAliases = aliases; + + /// + /// Gets the aliases for trees that the user will need access to. /// public IReadOnlyCollection TreeAliases { get; } - - public TreeRequirement(params string[] aliases) => TreeAliases = aliases; } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/UserGroupHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/UserGroupHandler.cs index a693bc592b..92045fcdfb 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/UserGroupHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/UserGroupHandler.cs @@ -1,8 +1,14 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; using Umbraco.Core; +using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.BackOffice.Controllers; @@ -14,40 +20,51 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class UserGroupHandler : MustSatisfyRequirementAuthorizationHandler { - private readonly IHttpContextAccessor _httpContextAcessor; + private readonly IHttpContextAccessor _httpContextAccessor; private readonly IUserService _userService; private readonly IContentService _contentService; private readonly IMediaService _mediaService; private readonly IEntityService _entityService; - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - public UserGroupHandler(IHttpContextAccessor httpContextAcessor, - IUserService userService, - IContentService contentService, - IMediaService mediaService, - IEntityService entityService, - IBackOfficeSecurityAccessor backofficeSecurityAccessor) + /// + /// Initializes a new instance of the class. + /// + /// Accessor for the HTTP context of the current request. + /// Service for user related operations. + /// Service for content related operations. + /// Service for media related operations. + /// Service for entity related operations. + /// Accessor for back-office security. + public UserGroupHandler( + IHttpContextAccessor httpContextAccessor, + IUserService userService, + IContentService contentService, + IMediaService mediaService, + IEntityService entityService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor) { - _httpContextAcessor = httpContextAcessor; + _httpContextAccessor = httpContextAccessor; _userService = userService; _contentService = contentService; _mediaService = mediaService; _entityService = entityService; - _backofficeSecurityAccessor = backofficeSecurityAccessor; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } + /// protected override Task IsAuthorized(AuthorizationHandlerContext context, UserGroupRequirement requirement) { - var currentUser = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser; + IUser currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; - var queryString = _httpContextAcessor.HttpContext?.Request.Query; + IQueryCollection queryString = _httpContextAccessor.HttpContext?.Request.Query; if (queryString == null) { // Must succeed this requirement since we cannot process it. return Task.FromResult(true); - } + } - var ids = queryString.Where(x => x.Key == requirement.QueryStringName).ToArray(); + KeyValuePair[] ids = queryString.Where(x => x.Key == requirement.QueryStringName).ToArray(); if (ids.Length == 0) { // Must succeed this requirement since we cannot process it. @@ -64,10 +81,9 @@ namespace Umbraco.Web.BackOffice.Authorization _mediaService, _entityService); - var isAuth = authHelper.AuthorizeGroupAccess(currentUser, intIds); + Attempt isAuth = authHelper.AuthorizeGroupAccess(currentUser, intIds); return Task.FromResult(isAuth.Success); } - } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/UserGroupRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/UserGroupRequirement.cs index aae5733d96..2a14bb1a78 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/UserGroupRequirement.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/UserGroupRequirement.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Authorization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Authorization { @@ -7,11 +10,15 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class UserGroupRequirement : IAuthorizationRequirement { - public UserGroupRequirement(string queryStringName = "id") - { - QueryStringName = queryStringName; - } + /// + /// Initializes a new instance of the class. + /// + /// Query string name from which to authorize values. + public UserGroupRequirement(string queryStringName = "id") => QueryStringName = queryStringName; + /// + /// Gets the query string name from which to authorize values. + /// public string QueryStringName { get; } } } From 9f8138b2d0d14f0182893562288707e842a3853c Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sun, 6 Dec 2020 10:46:04 +0100 Subject: [PATCH 56/63] Adhered to linting rules configuration models, validators and tests. --- .../Configuration/IPasswordConfiguration.cs | 36 +++++- .../Models/ActiveDirectorySettings.cs | 11 +- .../Configuration/Models/ConnectionStrings.cs | 13 ++- .../Configuration/Models/ContentErrorPage.cs | 35 +++++- .../Models/ContentImagingSettings.cs | 20 +++- .../Models/ContentNotificationSettings.cs | 14 ++- .../Configuration/Models/ContentSettings.cs | 41 ++++++- .../Configuration/Models/CoreDebugSettings.cs | 16 ++- .../Models/DatabaseServerMessengerSettings.cs | 16 ++- .../Models/DatabaseServerRegistrarSettings.cs | 12 +- .../Models/DisabledHealthCheckSettings.cs | 17 ++- .../Models/ExceptionFilterSettings.cs | 11 +- .../Configuration/Models/GlobalSettings.cs | 107 +++++++++++++++--- .../HealthChecksNotificationMethodSettings.cs | 20 +++- .../HealthChecksNotificationSettings.cs | 23 +++- .../Models/HealthChecksSettings.cs | 14 ++- .../Configuration/Models/HostingSettings.cs | 13 ++- .../Models/ImagingAutoFillUploadField.cs | 23 +++- .../Models/ImagingCacheSettings.cs | 23 +++- .../Models/ImagingResizeSettings.cs | 14 ++- .../Configuration/Models/ImagingSettings.cs | 14 ++- .../Models/IndexCreatorSettings.cs | 11 +- .../Configuration/Models/KeepAliveSettings.cs | 12 ++ .../Configuration/Models/LoggingSettings.cs | 11 +- .../MemberPasswordConfigurationSettings.cs | 15 ++- .../Models/ModelsBuilderSettings.cs | 40 +++---- .../Configuration/Models/NuCacheSettings.cs | 13 ++- .../Models/RequestHandlerSettings.cs | 81 ++++++++----- .../Configuration/Models/RuntimeSettings.cs | 14 ++- .../Configuration/Models/SecuritySettings.cs | 32 +++++- .../Configuration/Models/SmtpSettings.cs | 54 ++++++++- .../Configuration/Models/TourSettings.cs | 11 +- .../Models/TypeFinderSettings.cs | 11 +- .../UserPasswordConfigurationSettings.cs | 15 ++- .../Validation/ConfigurationValidatorBase.cs | 34 +++++- .../Validation/ContentSettingsValidator.cs | 24 ++-- .../Validation/GlobalSettingsValidator.cs | 15 ++- .../HealthChecksSettingsValidator.cs | 26 +++-- .../RequestHandlerSettingsValidator.cs | 9 +- .../Models/Validation/ValidatableEntryBase.cs | 8 +- .../Models/WebRoutingSettings.cs | 32 +++++- src/Umbraco.Infrastructure/Scoping/Scope.cs | 2 +- .../GlobalSettingsValidatorTests.cs | 20 ++-- .../HealthChecksSettingsValidatorTests.cs | 20 ++-- .../RequestHandlerSettingsValidatorTests.cs | 10 +- 45 files changed, 840 insertions(+), 173 deletions(-) diff --git a/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs b/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs index 6a5fd8e73f..0c143f22e6 100644 --- a/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs @@ -1,19 +1,49 @@ -namespace Umbraco.Core.Configuration -{ +// Copyright (c) Umbraco. +// See LICENSE for more details. +namespace Umbraco.Core.Configuration +{ /// /// Password configuration /// public interface IPasswordConfiguration { + /// + /// Gets a value for the minimum required length for the password. + /// int RequiredLength { get; } + + /// + /// Gets a value indicating whether at least one non-letter or digit is required for the password. + /// bool RequireNonLetterOrDigit { get; } + + /// + /// Gets a value indicating whether at least one digit is required for the password. + /// bool RequireDigit { get; } + + /// + /// Gets a value indicating whether at least one lower-case character is required for the password. + /// bool RequireLowercase { get; } + + /// + /// Gets a value indicating whether at least one upper-case character is required for the password. + /// bool RequireUppercase { get; } + + /// + /// Gets a value for the password hash algorithm type. + /// string HashAlgorithmType { get; } - // TODO: This doesn't really belong here + /// + /// Gets a value for the maximum failed access attempts before lockout. + /// + /// + /// TODO: This doesn't really belong here + /// int MaxFailedAccessAttemptsBeforeLockout { get; } } } diff --git a/src/Umbraco.Core/Configuration/Models/ActiveDirectorySettings.cs b/src/Umbraco.Core/Configuration/Models/ActiveDirectorySettings.cs index 0fe541416a..135b8b763c 100644 --- a/src/Umbraco.Core/Configuration/Models/ActiveDirectorySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ActiveDirectorySettings.cs @@ -1,7 +1,16 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for active directory settings. + /// public class ActiveDirectorySettings { + /// + /// Gets or sets a value for the Active Directory domain. + /// public string Domain { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs b/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs index d21cba4486..52b8a02478 100644 --- a/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs +++ b/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs @@ -1,19 +1,30 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for connection strings. + /// public class ConnectionStrings { // Backing field for UmbracoConnectionString to load from configuration value with key umbracoDbDSN. // Attributes cannot be applied to map from keys that don't match, and have chosen to retain the key name // used in configuration for older Umbraco versions. // See: https://stackoverflow.com/a/54607296/489433 +#pragma warning disable SA1300 // Element should begin with upper-case letter +#pragma warning disable IDE1006 // Naming Styles private string umbracoDbDSN +#pragma warning restore IDE1006 // Naming Styles +#pragma warning restore SA1300 // Element should begin with upper-case letter { get => UmbracoConnectionString?.ConnectionString; set => UmbracoConnectionString = new ConfigConnectionString(Constants.System.UmbracoConnectionName, value); } + /// + /// Gets or sets a value for the Umbraco database connection string.. + /// public ConfigConnectionString UmbracoConnectionString { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs b/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs index 6a362c93a3..842b2e6fb7 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs @@ -1,30 +1,55 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.ComponentModel.DataAnnotations; using Umbraco.Core.Configuration.Models.Validation; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration for a content error page. + /// public class ContentErrorPage : ValidatableEntryBase { + /// + /// Gets or sets a value for the content Id. + /// public int ContentId { get; set; } + /// + /// Gets or sets a value for the content key. + /// public Guid ContentKey { get; set; } + /// + /// Gets or sets a value for the content XPath. + /// public string ContentXPath { get; set; } + /// + /// Gets a value indicating whether the field is populated. + /// public bool HasContentId => ContentId != 0; + /// + /// Gets a value indicating whether the field is populated. + /// public bool HasContentKey => ContentKey != Guid.Empty; + /// + /// Gets a value indicating whether the field is populated. + /// public bool HasContentXPath => !string.IsNullOrEmpty(ContentXPath); + /// + /// Gets or sets a value for the content culture. + /// [Required] public string Culture { get; set; } - internal override bool IsValid() - { - return base.IsValid() && + internal override bool IsValid() => + base.IsValid() && ((HasContentId ? 1 : 0) + (HasContentKey ? 1 : 0) + (HasContentXPath ? 1 : 0) == 1); - } } } diff --git a/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs index 7c1e570426..d31c5cb6d7 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs @@ -1,9 +1,15 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for content imaging settings. + /// public class ContentImagingSettings { - private static readonly ImagingAutoFillUploadField[] DefaultImagingAutoFillUploadField = -{ + private static readonly ImagingAutoFillUploadField[] s_defaultImagingAutoFillUploadField = + { new ImagingAutoFillUploadField { Alias = Constants.Conventions.Media.File, @@ -14,8 +20,14 @@ } }; + /// + /// Gets or sets a value for the collection of accepted image file extensions. + /// public string[] ImageFileTypes { get; set; } = new[] { "jpeg", "jpg", "gif", "bmp", "png", "tiff", "tif" }; - public ImagingAutoFillUploadField[] AutoFillImageProperties { get; set; } = DefaultImagingAutoFillUploadField; + /// + /// Gets or sets a value for the imaging autofill following media file upload fields. + /// + public ImagingAutoFillUploadField[] AutoFillImageProperties { get; set; } = s_defaultImagingAutoFillUploadField; } } diff --git a/src/Umbraco.Core/Configuration/Models/ContentNotificationSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentNotificationSettings.cs index ab1c10ff77..48a131adfa 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentNotificationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentNotificationSettings.cs @@ -1,9 +1,21 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for content notification settings. + /// public class ContentNotificationSettings { + /// + /// Gets or sets a value for the email address for notifications. + /// public string Email { get; set; } + /// + /// Gets or sets a value indicating whether HTML email notifications should be disabled. + /// public bool DisableHtmlEmail { get; set; } = false; } } diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs index c26e6d403a..55881cd8db 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs @@ -1,10 +1,15 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; -using Umbraco.Core.Configuration.Models.Validation; using Umbraco.Core.Macros; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for content settings. + /// public class ContentSettings { private const string DefaultPreviewBadge = @@ -142,26 +147,54 @@ namespace Umbraco.Core.Configuration.Models "; + /// + /// Gets or sets a value for the content notification settings. + /// public ContentNotificationSettings Notifications { get; set; } = new ContentNotificationSettings(); + /// + /// Gets or sets a value for the content imaging settings. + /// public ContentImagingSettings Imaging { get; set; } = new ContentImagingSettings(); + /// + /// Gets or sets a value indicating whether URLs should be resolved from text strings. + /// public bool ResolveUrlsFromTextString { get; set; } = false; + /// + /// Gets or sets a value for the collection of error pages. + /// public ContentErrorPage[] Error404Collection { get; set; } = Array.Empty(); + /// + /// Gets or sets a value for the preview badge mark-up. + /// public string PreviewBadge { get; set; } = DefaultPreviewBadge; + /// + /// Gets or sets a value for the macro error behaviour. + /// public MacroErrorBehaviour MacroErrors { get; set; } = MacroErrorBehaviour.Inline; + /// + /// Gets or sets a value for the collection of file extensions that are disallowed for upload. + /// public IEnumerable DisallowedUploadFiles { get; set; } = new[] { "ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd" }; + /// + /// Gets or sets a value for the collection of file extensions that are allowed for upload. + /// public IEnumerable AllowedUploadFiles { get; set; } = Array.Empty(); + /// + /// Gets or sets a value indicating whether deprecated property editors should be shown. + /// public bool ShowDeprecatedPropertyEditors { get; set; } = false; + /// + /// Gets or sets a value for the pate to the login screen background image. + /// public string LoginBackgroundImage { get; set; } = "assets/img/login.jpg"; - - } } diff --git a/src/Umbraco.Core/Configuration/Models/CoreDebugSettings.cs b/src/Umbraco.Core/Configuration/Models/CoreDebugSettings.cs index 2b13609509..a263fb648a 100644 --- a/src/Umbraco.Core/Configuration/Models/CoreDebugSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/CoreDebugSettings.cs @@ -1,9 +1,21 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for core debug settings. + /// public class CoreDebugSettings { - public bool LogUncompletedScopes { get; set; } = false; + /// + /// Gets or sets a value indicating whether incompleted scopes should be logged. + /// + public bool LogIncompletedScopes { get; set; } = false; + /// + /// Gets or sets a value indicating whether memory dumps on thread abort should be taken. + /// public bool DumpOnTimeoutThreadAbort { get; set; } = false; } } diff --git a/src/Umbraco.Core/Configuration/Models/DatabaseServerMessengerSettings.cs b/src/Umbraco.Core/Configuration/Models/DatabaseServerMessengerSettings.cs index 32b957d5d0..8ad87bbb4e 100644 --- a/src/Umbraco.Core/Configuration/Models/DatabaseServerMessengerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/DatabaseServerMessengerSettings.cs @@ -1,26 +1,32 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for database server messaging settings. + /// public class DatabaseServerMessengerSettings { /// - /// The maximum number of instructions that can be processed at startup; otherwise the server cold-boots (rebuilds its caches). + /// Gets or sets a value for the maximum number of instructions that can be processed at startup; otherwise the server cold-boots (rebuilds its caches). /// public int MaxProcessingInstructionCount { get; set; } = 1000; /// - /// The time to keep instructions in the database; records older than this number will be pruned. + /// Gets or sets a value for the time to keep instructions in the database; records older than this number will be pruned. /// public TimeSpan TimeToRetainInstructions { get; set; } = TimeSpan.FromDays(2); /// - /// The time to wait between each sync operations. + /// Gets or sets a value for the time to wait between each sync operations. /// public TimeSpan TimeBetweenSyncOperations { get; set; } = TimeSpan.FromSeconds(5); /// - /// The time to wait between each prune operations. + /// Gets or sets a value for the time to wait between each prune operations. /// public TimeSpan TimeBetweenPruneOperations { get; set; } = TimeSpan.FromMinutes(1); } diff --git a/src/Umbraco.Core/Configuration/Models/DatabaseServerRegistrarSettings.cs b/src/Umbraco.Core/Configuration/Models/DatabaseServerRegistrarSettings.cs index 1aa670fae6..ae2502d8af 100644 --- a/src/Umbraco.Core/Configuration/Models/DatabaseServerRegistrarSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/DatabaseServerRegistrarSettings.cs @@ -1,16 +1,22 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for database server registrar settings. + /// public class DatabaseServerRegistrarSettings { /// - /// The amount of time to wait between calls to the database on the background thread. + /// Gets or sets a value for the amount of time to wait between calls to the database on the background thread. /// public TimeSpan WaitTimeBetweenCalls { get; set; } = TimeSpan.FromMinutes(1); /// - /// The time span to wait before considering a server stale, after it has last been accessed. + /// Gets or sets a value for the time span to wait before considering a server stale, after it has last been accessed. /// public TimeSpan StaleServerTimeout { get; set; } = TimeSpan.FromMinutes(2); } diff --git a/src/Umbraco.Core/Configuration/Models/DisabledHealthCheckSettings.cs b/src/Umbraco.Core/Configuration/Models/DisabledHealthCheckSettings.cs index 1d96a9027f..38c71fd83f 100644 --- a/src/Umbraco.Core/Configuration/Models/DisabledHealthCheckSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/DisabledHealthCheckSettings.cs @@ -1,13 +1,28 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for disabled healthcheck settings. + /// public class DisabledHealthCheckSettings { + /// + /// Gets or sets a value for the healthcheck Id to disable. + /// public Guid Id { get; set; } + /// + /// Gets or sets a value for the date the healthcheck was disabled. + /// public DateTime DisabledOn { get; set; } + /// + /// Gets or sets a value for Id of the user that disabled the healthcheck. + /// public int DisabledBy { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/ExceptionFilterSettings.cs b/src/Umbraco.Core/Configuration/Models/ExceptionFilterSettings.cs index 6b8f74bef0..1a1362ff21 100644 --- a/src/Umbraco.Core/Configuration/Models/ExceptionFilterSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ExceptionFilterSettings.cs @@ -1,7 +1,16 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for exception filter settings. + /// public class ExceptionFilterSettings { + /// + /// Gets or sets a value indicating whether the exception filter is disabled. + /// public bool Disabled { get; set; } = false; } } diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs index b6e5cf2b4d..eb13427d2b 100644 --- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs @@ -1,68 +1,141 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { /// - /// The GlobalSettings Class contains general settings information for the entire Umbraco instance based on information - /// from web.config appsettings + /// Typed configuration options for global settings. /// public class GlobalSettings { internal const string - StaticReservedPaths = "~/app_plugins/,~/install/,~/mini-profiler-resources/,~/umbraco/,"; //must end with a comma! + StaticReservedPaths = "~/app_plugins/,~/install/,~/mini-profiler-resources/,~/umbraco/,"; // must end with a comma! internal const string - StaticReservedUrls = "~/config/splashes/noNodes.aspx,~/.well-known,"; //must end with a comma! + StaticReservedUrls = "~/config/splashes/noNodes.aspx,~/.well-known,"; // must end with a comma! + /// + /// Gets or sets a value for the reserved URLs. + /// public string ReservedUrls { get; set; } = StaticReservedUrls; + /// + /// Gets or sets a value for the reserved paths. + /// public string ReservedPaths { get; set; } = StaticReservedPaths; - // TODO: https://github.com/umbraco/Umbraco-CMS/issues/4238 - stop having version in web.config appSettings - // TODO: previously this would throw on set, but presumably we can't do that if we do still want this in config. + /// + /// Gets or sets a value for the configuration status. + /// + /// + /// TODO: https://github.com/umbraco/Umbraco-CMS/issues/4238 - stop having version in web.config appSettings + /// TODO: previously this would throw on set, but presumably we can't do that if we do still want this in config. + /// public string ConfigurationStatus { get; set; } + /// + /// Gets or sets a value for the timeout in minutes. + /// public int TimeOutInMinutes { get; set; } = 20; + /// + /// Gets or sets a value for the default UI language. + /// public string DefaultUILanguage { get; set; } = "en-US"; + /// + /// Gets or sets a value indicating whether to hide the top level node from the path. + /// public bool HideTopLevelNodeFromPath { get; set; } = false; + /// + /// Gets or sets a value indicating whether HTTPS should be used. + /// public bool UseHttps { get; set; } = false; + /// + /// Gets or sets a value for the version check period in days. + /// public int VersionCheckPeriod { get; set; } = 7; + /// + /// Gets or sets a value for the Umbraco back-office path. + /// public string UmbracoPath { get; set; } = "~/umbraco"; - // TODO: Umbraco cannot be hard coded here that is what UmbracoPath is for - // so this should not be a normal get set it has to have dynamic ability to return the correct - // path given UmbracoPath if this hasn't been explicitly set. + /// + /// Gets or sets a value for the Umbraco icons path. + /// + /// + /// TODO: Umbraco cannot be hard coded here that is what UmbracoPath is for + /// so this should not be a normal get set it has to have dynamic ability to return the correct + /// path given UmbracoPath if this hasn't been explicitly set. + /// public string IconsPath { get; set; } = $"~/umbraco/assets/icons"; + /// + /// Gets or sets a value for the Umbraco CSS path. + /// public string UmbracoCssPath { get; set; } = "~/css"; + /// + /// Gets or sets a value for the Umbraco scripts path. + /// public string UmbracoScriptsPath { get; set; } = "~/scripts"; + /// + /// Gets or sets a value for the Umbraco media path. + /// public string UmbracoMediaPath { get; set; } = "~/media"; + /// + /// Gets or sets a value indicating whether to install the database when it is missing. + /// public bool InstallMissingDatabase { get; set; } = false; + /// + /// Gets or sets a value indicating whether to install the database when it is empty. + /// public bool InstallEmptyDatabase { get; set; } = false; + /// + /// Gets or sets a value indicating whether to disable the election for a single server. + /// public bool DisableElectionForSingleServer { get; set; } = false; - public DatabaseServerRegistrarSettings DatabaseServerRegistrar { get; set; } = new DatabaseServerRegistrarSettings(); - - public DatabaseServerMessengerSettings DatabaseServerMessenger { get; set; } = new DatabaseServerMessengerSettings(); - - public string RegisterType { get; set; } = string.Empty; - + /// + /// Gets or sets a value for the database factory server version. + /// public string DatabaseFactoryServerVersion { get; set; } = string.Empty; + /// + /// Gets or sets a value for the main dom lock. + /// public string MainDomLock { get; set; } = string.Empty; + /// + /// Gets or sets a value for the path to the no content view. + /// public string NoNodesViewPath { get; set; } = "~/umbraco/UmbracoWebsite/NoNodes.cshtml"; - public bool IsSmtpServerConfigured => !string.IsNullOrWhiteSpace(Smtp?.Host); + /// + /// Gets or sets a value for the database server registrar settings. + /// + public DatabaseServerRegistrarSettings DatabaseServerRegistrar { get; set; } = new DatabaseServerRegistrarSettings(); + /// + /// Gets or sets a value for the database server messenger settings. + /// + public DatabaseServerMessengerSettings DatabaseServerMessenger { get; set; } = new DatabaseServerMessengerSettings(); + + /// + /// Gets or sets a value for the SMTP settings. + /// public SmtpSettings Smtp { get; set; } + + /// + /// Gets a value indicating whether SMTP is configured. + /// + public bool IsSmtpServerConfigured => !string.IsNullOrWhiteSpace(Smtp?.Host); } } diff --git a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs index 1cfc4f9168..b4bffab4d6 100644 --- a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs @@ -1,16 +1,34 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using Umbraco.Core.HealthCheck; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for healthcheck notification method settings. + /// public class HealthChecksNotificationMethodSettings { + /// + /// Gets or sets a value indicating whether the health check notification method is enabled. + /// public bool Enabled { get; set; } = false; + /// + /// Gets or sets a value for the health check notifications reporting verbosity. + /// public HealthCheckNotificationVerbosity Verbosity { get; set; } = HealthCheckNotificationVerbosity.Summary; + /// + /// Gets or sets a value indicating whether the health check notifications should occur on failures only. + /// public bool FailureOnly { get; set; } = false; + /// + /// Gets or sets a value providing provider specific settings for the health check notification method. + /// public IDictionary Settings { get; set; } = new Dictionary(); } } diff --git a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs index 052b5a4997..3e52a70b29 100644 --- a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs @@ -1,19 +1,40 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for healthcheck notification settings. + /// public class HealthChecksNotificationSettings { + /// + /// Gets or sets a value indicating whether health check notifications are enabled. + /// public bool Enabled { get; set; } = false; + /// + /// Gets or sets a value for the first run time of a healthcheck notification in crontab format. + /// public string FirstRunTime { get; set; } = string.Empty; + /// + /// Gets or sets a value for the period of the healthcheck notification. + /// public TimeSpan Period { get; set; } = TimeSpan.FromHours(24); + /// + /// Gets or sets a value for the collection of health check notification methods. + /// public IDictionary NotificationMethods { get; set; } = new Dictionary(); + /// + /// Gets or sets a value for the collection of health checks that are disabled for notifications. + /// public IEnumerable DisabledChecks { get; set; } = Enumerable.Empty(); } } diff --git a/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs b/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs index 705611b0c1..e3ae9a3f96 100644 --- a/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs @@ -1,12 +1,24 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Linq; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for healthchecks settings. + /// public class HealthChecksSettings { + /// + /// Gets or sets a value for the collection of healthchecks that are disabled. + /// public IEnumerable DisabledChecks { get; set; } = Enumerable.Empty(); + /// + /// Gets or sets a value for the healthcheck notification settings. + /// public HealthChecksNotificationSettings Notification { get; set; } = new HealthChecksNotificationSettings(); } } diff --git a/src/Umbraco.Core/Configuration/Models/HostingSettings.cs b/src/Umbraco.Core/Configuration/Models/HostingSettings.cs index b003315c56..0478e9b7e1 100644 --- a/src/Umbraco.Core/Configuration/Models/HostingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HostingSettings.cs @@ -1,18 +1,25 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for hosting settings. + /// public class HostingSettings { + /// + /// Gets or sets a value for the application virtual path. + /// public string ApplicationVirtualPath { get; set; } /// - /// Gets the configuration for the location of temporary files. + /// Gets or sets a value for the location of temporary files. /// public LocalTempStorage LocalTempStorageLocation { get; set; } = LocalTempStorage.Default; /// - /// Gets a value indicating whether umbraco is running in [debug mode]. + /// Gets or sets a value indicating whether umbraco is running in [debug mode]. /// /// true if [debug mode]; otherwise, false. public bool Debug { get; set; } = false; diff --git a/src/Umbraco.Core/Configuration/Models/ImagingAutoFillUploadField.cs b/src/Umbraco.Core/Configuration/Models/ImagingAutoFillUploadField.cs index f58e2bb4f8..999bcf2dff 100644 --- a/src/Umbraco.Core/Configuration/Models/ImagingAutoFillUploadField.cs +++ b/src/Umbraco.Core/Configuration/Models/ImagingAutoFillUploadField.cs @@ -1,22 +1,43 @@ -using System.ComponentModel.DataAnnotations; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.ComponentModel.DataAnnotations; using Umbraco.Core.Configuration.Models.Validation; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for image autofill upload settings. + /// public class ImagingAutoFillUploadField : ValidatableEntryBase { + /// + /// Gets or sets a value for the alias of the image upload property. + /// [Required] public string Alias { get; set; } + /// + /// Gets or sets a value for the width field alias of the image upload property. + /// [Required] public string WidthFieldAlias { get; set; } + /// + /// Gets or sets a value for the height field alias of the image upload property. + /// [Required] public string HeightFieldAlias { get; set; } + /// + /// Gets or sets a value for the length field alias of the image upload property. + /// [Required] public string LengthFieldAlias { get; set; } + /// + /// Gets or sets a value for the extension field alias of the image upload property. + /// [Required] public string ExtensionFieldAlias { get; set; } } diff --git a/src/Umbraco.Core/Configuration/Models/ImagingCacheSettings.cs b/src/Umbraco.Core/Configuration/Models/ImagingCacheSettings.cs index 44f5ae89b6..0a3e723722 100644 --- a/src/Umbraco.Core/Configuration/Models/ImagingCacheSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ImagingCacheSettings.cs @@ -1,17 +1,34 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.IO; -using System.Threading; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for image cache settings. + /// public class ImagingCacheSettings { - public TimeSpan BrowserMaxAge { get; set; } = TimeSpan.FromDays(7); + /// + /// Gets or sets a value for the browser image cache maximum age. + /// + public TimeSpan BrowserMaxAge { get; set; } = TimeSpan.FromDays(7); + /// + /// Gets or sets a value for the image cache maximum age. + /// public TimeSpan CacheMaxAge { get; set; } = TimeSpan.FromDays(365); + /// + /// Gets or sets a value for length of the cached name. + /// public uint CachedNameLength { get; set; } = 8; + /// + /// Gets or sets a value for the cache folder. + /// public string CacheFolder { get; set; } = Path.Combine("..", "umbraco", "mediacache"); } } diff --git a/src/Umbraco.Core/Configuration/Models/ImagingResizeSettings.cs b/src/Umbraco.Core/Configuration/Models/ImagingResizeSettings.cs index f9db53d7dd..c95aad52ad 100644 --- a/src/Umbraco.Core/Configuration/Models/ImagingResizeSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ImagingResizeSettings.cs @@ -1,9 +1,21 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for image resize settings. + /// public class ImagingResizeSettings { + /// + /// Gets or sets a value for the maximim resize width. + /// public int MaxWidth { get; set; } = 5000; + /// + /// Gets or sets a value for the maximim resize height. + /// public int MaxHeight { get; set; } = 5000; } } diff --git a/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs b/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs index 2f253b151b..343e2a040f 100644 --- a/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs @@ -1,9 +1,21 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for imaging settings. + /// public class ImagingSettings { + /// + /// Gets or sets a value for imaging cache settings. + /// public ImagingCacheSettings Cache { get; set; } = new ImagingCacheSettings(); + /// + /// Gets or sets a value for imaging resize settings. + /// public ImagingResizeSettings Resize { get; set; } = new ImagingResizeSettings(); } } diff --git a/src/Umbraco.Core/Configuration/Models/IndexCreatorSettings.cs b/src/Umbraco.Core/Configuration/Models/IndexCreatorSettings.cs index fcc22de9a3..9ea7348211 100644 --- a/src/Umbraco.Core/Configuration/Models/IndexCreatorSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/IndexCreatorSettings.cs @@ -1,7 +1,16 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for index creator settings. + /// public class IndexCreatorSettings { + /// + /// Gets or sets a value for lucene directory factory type. + /// public string LuceneDirectoryFactory { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs b/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs index 2c53407398..57336d35ac 100644 --- a/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs @@ -1,9 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for keep alive settings. + /// public class KeepAliveSettings { + /// + /// Gets or sets a value indicating whether the keep alive task is disabled. + /// public bool DisableKeepAliveTask { get; set; } = false; + /// + /// Gets a value for the keep alive ping URL. + /// public string KeepAlivePingUrl => "{umbracoApplicationUrl}/api/keepalive/ping"; } } diff --git a/src/Umbraco.Core/Configuration/Models/LoggingSettings.cs b/src/Umbraco.Core/Configuration/Models/LoggingSettings.cs index 7d79bb83ae..49f2517f8f 100644 --- a/src/Umbraco.Core/Configuration/Models/LoggingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/LoggingSettings.cs @@ -1,9 +1,18 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for logging settings. + /// public class LoggingSettings { + /// + /// Gets or sets a value for the maximum age of a log file. + /// public TimeSpan MaxLogAge { get; set; } = TimeSpan.FromHours(24); } } diff --git a/src/Umbraco.Core/Configuration/Models/MemberPasswordConfigurationSettings.cs b/src/Umbraco.Core/Configuration/Models/MemberPasswordConfigurationSettings.cs index 52bba6f4b8..abd12ae023 100644 --- a/src/Umbraco.Core/Configuration/Models/MemberPasswordConfigurationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/MemberPasswordConfigurationSettings.cs @@ -1,19 +1,32 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for member password settings. + /// public class MemberPasswordConfigurationSettings : IPasswordConfiguration { + /// public int RequiredLength { get; set; } = 10; + /// public bool RequireNonLetterOrDigit { get; set; } = false; + /// public bool RequireDigit { get; set; } = false; + /// public bool RequireLowercase { get; set; } = false; + /// public bool RequireUppercase { get; set; } = false; + /// public string HashAlgorithmType { get; set; } = Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName; + /// public int MaxFailedAccessAttemptsBeforeLockout { get; set; } = 5; } } diff --git a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs index f0b56561e2..bace0f96c4 100644 --- a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs @@ -1,17 +1,21 @@ -using Umbraco.Configuration; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Configuration; namespace Umbraco.Core.Configuration.Models { /// - /// Represents the models builder configuration. + /// Typed configuration options for models builder settings. /// public class ModelsBuilderSettings { - // TODO: This should not go into App_Data - that folder isn't really a real thing anymore - public static string DefaultModelsDirectory => "~/umbraco/models"; + private bool _flagOutOfDateModels; + + private static string DefaultModelsDirectory => "~/umbraco/models"; /// - /// Gets a value indicating whether the whole models experience is enabled. + /// Gets or sets a value indicating whether the whole models experience is enabled. /// /// /// If this is false then absolutely nothing happens. @@ -20,31 +24,29 @@ namespace Umbraco.Core.Configuration.Models public bool Enable { get; set; } = false; /// - /// Gets the models mode. + /// Gets or sets a value for the models mode. /// public ModelsMode ModelsMode { get; set; } = ModelsMode.Nothing; /// - /// Gets the models namespace. + /// Gets or sets a value for models namespace. /// /// That value could be overriden by other (attribute in user's code...). Return default if no value was supplied. public string ModelsNamespace { get; set; } /// - /// Gets a value indicating whether we should enable the models factory. + /// Gets or sets a value indicating whether we should enable the models factory. /// /// Default value is true because no factory is enabled by default in Umbraco. public bool EnableFactory { get; set; } = true; - private bool _flagOutOfDateModels; - /// - /// Gets a value indicating whether we should flag out-of-date models. + /// Gets or sets a value indicating whether we should flag out-of-date models. /// /// - /// Models become out-of-date when data types or content types are updated. When this - /// setting is activated the ~/App_Data/Models/ood.txt file is then created. When models are - /// generated through the dashboard, the files is cleared. Default value is false. + /// Models become out-of-date when data types or content types are updated. When this + /// setting is activated the ~/App_Data/Models/ood.txt file is then created. When models are + /// generated through the dashboard, the files is cleared. Default value is false. /// public bool FlagOutOfDateModels { @@ -62,22 +64,22 @@ namespace Umbraco.Core.Configuration.Models } /// - /// Gets the models directory. + /// Gets or sets a value for the models directory. /// /// Default is ~/App_Data/Models but that can be changed. public string ModelsDirectory { get; set; } = DefaultModelsDirectory; /// - /// Gets a value indicating whether to accept an unsafe value for ModelsDirectory. + /// Gets or sets a value indicating whether to accept an unsafe value for ModelsDirectory. /// /// - /// An unsafe value is an absolute path, or a relative path pointing outside - /// of the website root. + /// An unsafe value is an absolute path, or a relative path pointing outside + /// of the website root. /// public bool AcceptUnsafeModelsDirectory { get; set; } = false; /// - /// Gets a value indicating the debug log level. + /// Gets or sets a value indicating the debug log level. /// /// 0 means minimal (safe on live site), anything else means more and more details (maybe not safe). public int DebugLevel { get; set; } = 0; diff --git a/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs b/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs index 89a726f30a..f98b1f422e 100644 --- a/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs @@ -1,7 +1,16 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { - public class NuCacheSettings + /// + /// Typed configuration options for NuCache settings. + /// + public class NuCacheSettings { + /// + /// Gets or sets a value defining the BTree block size. + /// public int? BTreeBlockSize { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index d7203b4901..ceea0f9038 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -1,26 +1,32 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using Umbraco.Core.Configuration.UmbracoSettings; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for request handler settings. + /// public class RequestHandlerSettings { internal static readonly CharItem[] DefaultCharCollection = { new CharItem { Char = " ", Replacement = "-" }, - new CharItem { Char = "\"", Replacement = "" }, - new CharItem { Char = "'", Replacement = "" }, - new CharItem { Char = "%", Replacement = "" }, - new CharItem { Char = ".", Replacement = "" }, - new CharItem { Char = ";", Replacement = "" }, - new CharItem { Char = "/", Replacement = "" }, - new CharItem { Char = "\\", Replacement = "" }, - new CharItem { Char = ":", Replacement = "" }, - new CharItem { Char = "#", Replacement = "" }, + new CharItem { Char = "\"", Replacement = string.Empty }, + new CharItem { Char = "'", Replacement = string.Empty }, + new CharItem { Char = "%", Replacement = string.Empty }, + new CharItem { Char = ".", Replacement = string.Empty }, + new CharItem { Char = ";", Replacement = string.Empty }, + new CharItem { Char = "/", Replacement = string.Empty }, + new CharItem { Char = "\\", Replacement = string.Empty }, + new CharItem { Char = ":", Replacement = string.Empty }, + new CharItem { Char = "#", Replacement = string.Empty }, new CharItem { Char = "+", Replacement = "plus" }, new CharItem { Char = "*", Replacement = "star" }, - new CharItem { Char = "&", Replacement = "" }, - new CharItem { Char = "?", Replacement = "" }, + new CharItem { Char = "&", Replacement = string.Empty }, + new CharItem { Char = "?", Replacement = string.Empty }, new CharItem { Char = "æ", Replacement = "ae" }, new CharItem { Char = "ä", Replacement = "ae" }, new CharItem { Char = "ø", Replacement = "oe" }, @@ -29,42 +35,63 @@ namespace Umbraco.Core.Configuration.Models new CharItem { Char = "ü", Replacement = "ue" }, new CharItem { Char = "ß", Replacement = "ss" }, new CharItem { Char = "|", Replacement = "-" }, - new CharItem { Char = "<", Replacement = "" }, - new CharItem { Char = ">", Replacement = "" } + new CharItem { Char = "<", Replacement = string.Empty }, + new CharItem { Char = ">", Replacement = string.Empty } }; + /// + /// Gets or sets a value indicating whether to add a trailing slash to URLs. + /// public bool AddTrailingSlash { get; set; } = true; + /// + /// Gets or sets a value indicating whether to convert URLs to ASCII (valid values: "true", "try" or "false"). + /// public string ConvertUrlsToAscii { get; set; } = "try"; + /// + /// Gets a value indicating whether URLs should be converted to ASCII. + /// public bool ShouldConvertUrlsToAscii => ConvertUrlsToAscii.InvariantEquals("true"); + /// + /// Gets a value indicating whether URLs should be tried to be converted to ASCII. + /// public bool ShouldTryConvertUrlsToAscii => ConvertUrlsToAscii.InvariantEquals("try"); - //We need to special handle ":", as this character is special in keys + // We need to special handle ":", as this character is special in keys // TODO: implement from configuration - //var collection = _configuration.GetSection(Prefix + "CharCollection").GetChildren() - // .Select(x => new CharItem() - // { - // Char = x.GetValue("Char"), - // Replacement = x.GetValue("Replacement"), - // }).ToArray(); + //// var collection = _configuration.GetSection(Prefix + "CharCollection").GetChildren() + //// .Select(x => new CharItem() + //// { + //// Char = x.GetValue("Char"), + //// Replacement = x.GetValue("Replacement"), + //// }).ToArray(); - //if (collection.Any() || _configuration.GetSection("Prefix").GetChildren().Any(x => - // x.Key.Equals("CharCollection", StringComparison.OrdinalIgnoreCase))) - //{ - // return collection; - //} + //// if (collection.Any() || _configuration.GetSection("Prefix").GetChildren().Any(x => + //// x.Key.Equals("CharCollection", StringComparison.OrdinalIgnoreCase))) + //// { + //// return collection; + //// } - // return DefaultCharCollection; + //// return DefaultCharCollection; + + /// + /// Gets or sets a value for the default character collection for replacements. + /// public IEnumerable CharCollection { get; set; } = DefaultCharCollection; + /// + /// Defines a character replacement. + /// public class CharItem : IChar { + /// public string Char { get; set; } + /// public string Replacement { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs b/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs index f93530b490..97af22ea8a 100644 --- a/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs @@ -1,9 +1,21 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for runtime settings. + /// public class RuntimeSettings { + /// + /// Gets or sets a value for the maximum query string length. + /// public int? MaxQueryStringLength { get; set; } + /// + /// Gets or sets a value for the maximum request length. + /// public int? MaxRequestLength { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs index 55bd3a5bf7..25bbbb645d 100644 --- a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs @@ -1,21 +1,51 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for security settings. + /// public class SecuritySettings { + /// + /// Gets or sets a value indicating whether to keep the user logged in. + /// public bool KeepUserLoggedIn { get; set; } = false; + /// + /// Gets or sets a value indicating whether to hide disabled users in the back-office. + /// public bool HideDisabledUsersInBackOffice { get; set; } = false; + /// + /// Gets or sets a value indicating whether to allow user password reset. + /// public bool AllowPasswordReset { get; set; } = true; + /// + /// Gets or sets a value for the authorization cookie name. + /// public string AuthCookieName { get; set; } = "UMB_UCONTEXT"; + /// + /// Gets or sets a value for the authorization cookie domain. + /// public string AuthCookieDomain { get; set; } + /// + /// Gets or sets a value indicating whether the user's email address is to be considered as their username. + /// public bool UsernameIsEmail { get; set; } = true; + /// + /// Gets or sets a value for the user password settings. + /// public UserPasswordConfigurationSettings UserPassword { get; set; } + /// + /// Gets or sets a value for the member password settings. + /// public MemberPasswordConfigurationSettings MemberPassword { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs b/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs index c1ba5edea2..fb4462f76d 100644 --- a/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs @@ -1,4 +1,7 @@ -using System.ComponentModel.DataAnnotations; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.ComponentModel.DataAnnotations; using System.Net.Mail; using Umbraco.Core.Configuration.Models.Validation; @@ -6,35 +9,82 @@ namespace Umbraco.Core.Configuration.Models { /// /// Matches MailKit.Security.SecureSocketOptions and defined locally to avoid having to take - /// thi + /// a dependency on this external library into Umbraco.Core. + /// See: http://www.mimekit.net/docs/html/T_MailKit_Security_SecureSocketOptions.htm /// public enum SecureSocketOptions { + /// + /// No SSL or TLS encryption should be used. + /// None = 0, + + /// + /// Allow the IMailService to decide which SSL or TLS options to use (default). If the server does not support SSL or TLS, then the connection will continue without any encryption. + /// Auto = 1, + + /// + /// The connection should use SSL or TLS encryption immediately. + /// SslOnConnect = 2, + + /// + /// Elevates the connection to use TLS encryption immediately after reading the greeting and capabilities of the server. If the server does not support the STARTTLS extension, then the connection will fail and a NotSupportedException will be thrown. + /// StartTls = 3, + + /// + /// Elevates the connection to use TLS encryption immediately after reading the greeting and capabilities of the server, but only if the server supports the STARTTLS extension. + /// StartTlsWhenAvailable = 4 } + /// + /// Typed configuration options for SMTP settings. + /// public class SmtpSettings : ValidatableEntryBase { + /// + /// Gets or sets a value for the SMTP from address to use for messages. + /// [Required] [EmailAddress] public string From { get; set; } + /// + /// Gets or sets a value for the SMTP host. + /// public string Host { get; set; } + /// + /// Gets or sets a value for the SMTP port. + /// public int Port { get; set; } + /// + /// Gets or sets a value for the secure socket options. + /// public SecureSocketOptions SecureSocketOptions { get; set; } = SecureSocketOptions.Auto; + /// + /// Gets or sets a value for the SMTP pick-up directory. + /// public string PickupDirectoryLocation { get; set; } + /// + /// Gets or sets a value for the SMTP delivery method. + /// public SmtpDeliveryMethod DeliveryMethod { get; set; } = SmtpDeliveryMethod.Network; + /// + /// Gets or sets a value for the SMTP user name. + /// public string Username { get; set; } + /// + /// Gets or sets a value for the SMTP password. + /// public string Password { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/TourSettings.cs b/src/Umbraco.Core/Configuration/Models/TourSettings.cs index 895eff6dee..25c06b9975 100644 --- a/src/Umbraco.Core/Configuration/Models/TourSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/TourSettings.cs @@ -1,7 +1,16 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for tour settings. + /// public class TourSettings { + /// + /// Gets or sets a value indicating whether back-office tours are enabled. + /// public bool EnableTours { get; set; } = true; } } diff --git a/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs b/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs index c5210f6c8e..63295c7259 100644 --- a/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs @@ -1,7 +1,16 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for type finder settings. + /// public class TypeFinderSettings { + /// + /// Gets or sets a value for the assemblies that accept load exceptions during type finder operations. + /// public string AssembliesAcceptingLoadExceptions { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/UserPasswordConfigurationSettings.cs b/src/Umbraco.Core/Configuration/Models/UserPasswordConfigurationSettings.cs index 445a0f545c..09b9200760 100644 --- a/src/Umbraco.Core/Configuration/Models/UserPasswordConfigurationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/UserPasswordConfigurationSettings.cs @@ -1,19 +1,32 @@ -namespace Umbraco.Core.Configuration.Models +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for user password settings. + /// public class UserPasswordConfigurationSettings : IPasswordConfiguration { + /// public int RequiredLength { get; set; } = 10; + /// public bool RequireNonLetterOrDigit { get; set; } = false; + /// public bool RequireDigit { get; set; } = false; + /// public bool RequireLowercase { get; set; } = false; + /// public bool RequireUppercase { get; set; } = false; + /// public string HashAlgorithmType { get; set; } = Constants.Security.AspNetCoreV3PasswordHashAlgorithmName; + /// public int MaxFailedAccessAttemptsBeforeLockout { get; set; } = 5; } } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs b/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs index 6c0af9802b..348c809a91 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs @@ -1,11 +1,24 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.Linq; namespace Umbraco.Core.Configuration.Models.Validation { + /// + /// Base class for configuration validators. + /// public abstract class ConfigurationValidatorBase { + /// + /// Validates that a string is one of a set of valid values. + /// + /// Configuration path from where the setting is found. + /// The value to check. + /// The set of valid values. + /// A message to output if the value does not match. + /// True if valid, false if not. public bool ValidateStringIsOneOfValidValues(string configPath, string value, IEnumerable validValues, out string message) { if (!validValues.InvariantContains(value)) @@ -18,6 +31,14 @@ namespace Umbraco.Core.Configuration.Models.Validation return true; } + /// + /// Validates that a collection of objects are all valid based on their data annotations. + /// + /// Configuration path from where the setting is found. + /// The values to check. + /// Description of validation appended to message if validation fails. + /// A message to output if the value does not match. + /// True if valid, false if not. public bool ValidateCollection(string configPath, IEnumerable values, string validationDescription, out string message) { if (values.Any(x => !x.IsValid())) @@ -30,6 +51,14 @@ namespace Umbraco.Core.Configuration.Models.Validation return true; } + /// + /// Validates a configuration entry is valid if provided. + /// + /// Configuration path from where the setting is found. + /// The value to check. + /// Description of validation appended to message if validation fails. + /// A message to output if the value does not match. + /// True if valid, false if not. public bool ValidateOptionalEntry(string configPath, ValidatableEntryBase value, string validationDescription, out string message) { if (value != null && !value.IsValid()) @@ -41,8 +70,5 @@ namespace Umbraco.Core.Configuration.Models.Validation message = string.Empty; return true; } - - - } } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs index f6e839aa39..fdfd6de59c 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs @@ -1,14 +1,20 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using Microsoft.Extensions.Options; namespace Umbraco.Core.Configuration.Models.Validation { + /// + /// Validator for configuration representated as . + /// public class ContentSettingsValidator : ConfigurationValidatorBase, IValidateOptions { + /// public ValidateOptionsResult Validate(string name, ContentSettings options) { - string message; - if (!ValidateError404Collection(options.Error404Collection, out message)) + if (!ValidateError404Collection(options.Error404Collection, out string message)) { return ValidateOptionsResult.Fail(message); } @@ -21,14 +27,10 @@ namespace Umbraco.Core.Configuration.Models.Validation return ValidateOptionsResult.Success; } - private bool ValidateError404Collection(IEnumerable values, out string message) - { - return ValidateCollection($"{Constants.Configuration.ConfigContent}:{nameof(ContentSettings.Error404Collection)}", values, "Culture and one and only one of ContentId, ContentKey and ContentXPath must be specified for each entry", out message); - } + private bool ValidateError404Collection(IEnumerable values, out string message) => + ValidateCollection($"{Constants.Configuration.ConfigContent}:{nameof(ContentSettings.Error404Collection)}", values, "Culture and one and only one of ContentId, ContentKey and ContentXPath must be specified for each entry", out message); - private bool ValidateAutoFillImageProperties(IEnumerable values, out string message) - { - return ValidateCollection($"{Constants.Configuration.ConfigContent}:{nameof(ContentSettings.Imaging)}:{nameof(ContentSettings.Imaging.AutoFillImageProperties)}", values, "Alias, WidthFieldAlias, HeightFieldAlias, LengthFieldAlias and ExtensionFieldAlias must be specified for each entry", out message); - } + private bool ValidateAutoFillImageProperties(IEnumerable values, out string message) => + ValidateCollection($"{Constants.Configuration.ConfigContent}:{nameof(ContentSettings.Imaging)}:{nameof(ContentSettings.Imaging.AutoFillImageProperties)}", values, "Alias, WidthFieldAlias, HeightFieldAlias, LengthFieldAlias and ExtensionFieldAlias must be specified for each entry", out message); } } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs index ca3fee7999..6bc9dc0a6f 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs @@ -1,10 +1,17 @@ -using Microsoft.Extensions.Options; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Options; namespace Umbraco.Core.Configuration.Models.Validation { + /// + /// Validator for configuration representated as . + /// public class GlobalSettingsValidator : ConfigurationValidatorBase, IValidateOptions { + /// public ValidateOptionsResult Validate(string name, GlobalSettings options) { if (!ValidateSmtpSetting(options.Smtp, out var message)) @@ -15,9 +22,7 @@ namespace Umbraco.Core.Configuration.Models.Validation return ValidateOptionsResult.Success; } - private bool ValidateSmtpSetting(SmtpSettings value, out string message) - { - return ValidateOptionalEntry($"{Constants.Configuration.ConfigGlobal}:{nameof(GlobalSettings.Smtp)}", value, "A valid From email address is required", out message); - } + private bool ValidateSmtpSetting(SmtpSettings value, out string message) => + ValidateOptionalEntry($"{Constants.Configuration.ConfigGlobal}:{nameof(GlobalSettings.Smtp)}", value, "A valid From email address is required", out message); } } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs index f26fce9bd9..449415c37f 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs @@ -1,16 +1,24 @@ -using Microsoft.Extensions.Options; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Options; namespace Umbraco.Core.Configuration.Models.Validation { + /// + /// Validator for configuration representated as . + /// public class HealthChecksSettingsValidator : ConfigurationValidatorBase, IValidateOptions { private readonly ICronTabParser _cronTabParser; - public HealthChecksSettingsValidator(ICronTabParser cronTabParser) - { - _cronTabParser = cronTabParser; - } + /// + /// Initializes a new instance of the class. + /// + /// Helper for parsing crontab expressions. + public HealthChecksSettingsValidator(ICronTabParser cronTabParser) => _cronTabParser = cronTabParser; + /// public ValidateOptionsResult Validate(string name, HealthChecksSettings options) { if (!ValidateNotificationFirstRunTime(options.Notification.FirstRunTime, out var message)) @@ -21,12 +29,10 @@ namespace Umbraco.Core.Configuration.Models.Validation return ValidateOptionsResult.Success; } - private bool ValidateNotificationFirstRunTime(string value, out string message) - { - return ValidateOptionalCronTab($"{Constants.Configuration.ConfigHealthChecks}:{nameof(HealthChecksSettings.Notification)}:{nameof(HealthChecksSettings.Notification.FirstRunTime)}", value, out message); - } + private bool ValidateNotificationFirstRunTime(string value, out string message) => + ValidateOptionalCronTab($"{Constants.Configuration.ConfigHealthChecks}:{nameof(HealthChecksSettings.Notification)}:{nameof(HealthChecksSettings.Notification.FirstRunTime)}", value, out message); - public bool ValidateOptionalCronTab(string configPath, string value, out string message) + private bool ValidateOptionalCronTab(string configPath, string value, out string message) { if (!string.IsNullOrEmpty(value) && !_cronTabParser.IsValidCronTab(value)) { diff --git a/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs index 305fe812f8..647c7438c6 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs @@ -1,9 +1,16 @@ -using Microsoft.Extensions.Options; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Options; namespace Umbraco.Core.Configuration.Models.Validation { + /// + /// Validator for configuration representated as . + /// public class RequestHandlerSettingsValidator : ConfigurationValidatorBase, IValidateOptions { + /// public ValidateOptionsResult Validate(string name, RequestHandlerSettings options) { if (!ValidateConvertUrlsToAscii(options.ConvertUrlsToAscii, out var message)) diff --git a/src/Umbraco.Core/Configuration/Models/Validation/ValidatableEntryBase.cs b/src/Umbraco.Core/Configuration/Models/Validation/ValidatableEntryBase.cs index 32e3c3270b..37380eb9a6 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/ValidatableEntryBase.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/ValidatableEntryBase.cs @@ -1,8 +1,14 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace Umbraco.Core.Configuration.Models.Validation { + /// + /// Provides a base class for configuration models that can be validated based on data annotations. + /// public abstract class ValidatableEntryBase { internal virtual bool IsValid() diff --git a/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs b/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs index 476d58c913..9f06046452 100644 --- a/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs @@ -1,23 +1,53 @@ -using Umbraco.Core.Models.PublishedContent; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core.Configuration.Models { + /// + /// Typed configuration options for web routing settings. + /// public class WebRoutingSettings { + /// + /// Gets or sets a value indicating whether IIS custom errors should be skipped. + /// public bool TrySkipIisCustomErrors { get; set; } = false; + /// + /// Gets or sets a value indicating whether an internal redirect should preserve the template. + /// public bool InternalRedirectPreservesTemplate { get; set; } = false; + /// + /// Gets or sets a value indicating whether the use of alternative templates are disabled. + /// public bool DisableAlternativeTemplates { get; set; } = false; + /// + /// Gets or sets a value indicating whether the use of alternative templates should be validated. + /// public bool ValidateAlternativeTemplates { get; set; } = false; + /// + /// Gets or sets a value indicating whether find content ID by path is disabled. + /// public bool DisableFindContentByIdPath { get; set; } = false; + /// + /// Gets or sets a value indicating whether redirect URL tracking is disabled. + /// public bool DisableRedirectUrlTracking { get; set; } = false; + /// + /// Gets or sets a value for the URL provider mode (). + /// public UrlMode UrlProviderMode { get; set; } = UrlMode.Auto; + /// + /// Gets or sets a value for the Umbraco application URL. + /// public string UmbracoApplicationUrl { get; set; } } } diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index 87986a6318..65e8e343f7 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -496,7 +496,7 @@ namespace Umbraco.Core.Scoping // caching config // true if Umbraco.CoreDebugSettings.LogUncompletedScope appSetting is set to "true" private bool LogUncompletedScopes => (_logUncompletedScopes - ?? (_logUncompletedScopes = _coreDebugSettings.LogUncompletedScopes)).Value; + ?? (_logUncompletedScopes = _coreDebugSettings.LogIncompletedScopes)).Value; /// public void ReadLock(params int[] lockIds) => Database.SqlContext.SqlSyntax.ReadLock(Database, lockIds); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs index 3cc0532db2..f286dd42b0 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs @@ -1,4 +1,8 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Options; +using NUnit.Framework; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models.Validation; @@ -11,8 +15,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation public void Returns_Success_ForValid_Configuration() { var validator = new GlobalSettingsValidator(); - var options = BuildGlobalSettings(); - var result = validator.Validate("settings", options); + GlobalSettings options = BuildGlobalSettings(); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.True(result.Succeeded); } @@ -20,20 +24,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation public void Returns_Fail_For_Configuration_With_Invalid_SmtpFrom_Field() { var validator = new GlobalSettingsValidator(); - var options = BuildGlobalSettings(smtpEmail: "invalid"); - var result = validator.Validate("settings", options); + GlobalSettings options = BuildGlobalSettings(smtpEmail: "invalid"); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.False(result.Succeeded); } - private static GlobalSettings BuildGlobalSettings(string smtpEmail = "test@test.com") - { - return new GlobalSettings + private static GlobalSettings BuildGlobalSettings(string smtpEmail = "test@test.com") => + new GlobalSettings { Smtp = new SmtpSettings { From = smtpEmail, } }; - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidatorTests.cs index 64a8be79a8..9ae5444134 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidatorTests.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Microsoft.Extensions.Options; using NUnit.Framework; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; @@ -13,8 +17,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation public void Returns_Success_ForValid_Configuration() { var validator = new HealthChecksSettingsValidator(new NCronTabParser()); - var options = BuildHealthChecksSettings(); - var result = validator.Validate("settings", options); + HealthChecksSettings options = BuildHealthChecksSettings(); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.True(result.Succeeded); } @@ -22,14 +26,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation public void Returns_Fail_For_Configuration_With_Invalid_Notification_FirstRunTime() { var validator = new HealthChecksSettingsValidator(new NCronTabParser()); - var options = BuildHealthChecksSettings(firstRunTime: "0 3 *"); - var result = validator.Validate("settings", options); + HealthChecksSettings options = BuildHealthChecksSettings(firstRunTime: "0 3 *"); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.False(result.Succeeded); } - private static HealthChecksSettings BuildHealthChecksSettings(string firstRunTime = "0 3 * * *") - { - return new HealthChecksSettings + private static HealthChecksSettings BuildHealthChecksSettings(string firstRunTime = "0 3 * * *") => + new HealthChecksSettings { Notification = new HealthChecksNotificationSettings { @@ -38,6 +41,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation Period = TimeSpan.FromHours(1), } }; - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidatorTests.cs index f579fec695..4a53fbf375 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidatorTests.cs @@ -1,4 +1,8 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Options; +using NUnit.Framework; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models.Validation; @@ -12,7 +16,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation { var validator = new RequestHandlerSettingsValidator(); var options = new RequestHandlerSettings(); - var result = validator.Validate("settings", options); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.True(result.Succeeded); } @@ -21,7 +25,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation { var validator = new RequestHandlerSettingsValidator(); var options = new RequestHandlerSettings { ConvertUrlsToAscii = "invalid" }; - var result = validator.Validate("settings", options); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.False(result.Succeeded); } } From 835d1d0ad3882bcc94b4cf8b241d613f363d23f9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 7 Dec 2020 17:56:40 +1100 Subject: [PATCH 57/63] linting cleanup --- .../CheckIfUserTicketDataIsStaleAttribute.cs | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs index a770a01e4d..913095fcda 100644 --- a/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; @@ -22,7 +23,8 @@ namespace Umbraco.Web.BackOffice.Filters { internal sealed class CheckIfUserTicketDataIsStaleAttribute : TypeFilterAttribute { - public CheckIfUserTicketDataIsStaleAttribute() : base(typeof(CheckIfUserTicketDataIsStaleFilter)) + public CheckIfUserTicketDataIsStaleAttribute() + : base(typeof(CheckIfUserTicketDataIsStaleFilter)) { } @@ -66,9 +68,11 @@ namespace Umbraco.Web.BackOffice.Filters await CheckStaleData(actionContext); - //return if nothing is updated + // return if nothing is updated if (_requestCache.Get(nameof(CheckIfUserTicketDataIsStaleFilter)) is null) + { return; + } await UpdateTokensAndAppendCustomHeaders(actionContext); } @@ -81,7 +85,7 @@ namespace Umbraco.Web.BackOffice.Filters await tokenFilter.OnActionExecutionAsync(actionContext, () => Task.FromResult(new ActionExecutedContext(actionContext, new List(), null))); - //add the header + // add the header AppendUserModifiedHeaderAttribute.AppendHeader(actionContext); } @@ -93,26 +97,36 @@ namespace Umbraco.Web.BackOffice.Filters return; } - //don't execute if it's already been done + // don't execute if it's already been done if (!(_requestCache.Get(nameof(CheckIfUserTicketDataIsStaleFilter)) is null)) + { return; + } - var identity = actionContext.HttpContext.User.Identity as UmbracoBackOfficeIdentity; - if (identity == null) return; + if (actionContext.HttpContext.User.Identity is not UmbracoBackOfficeIdentity identity) + { + return; + } - var userId = identity.Id.TryConvertTo(); - if (userId == false) return; + Attempt userId = identity.Id.TryConvertTo(); + if (userId == false) + { + return; + } - var user = _userService.GetUserById(userId.Result); - if (user == null) return; + IUser user = _userService.GetUserById(userId.Result); + if (user == null) + { + return; + } - //a list of checks to execute, if any of them pass then we resync + // a list of checks to execute, if any of them pass then we resync var checks = new Func[] { () => user.Username != identity.Username, () => { - var culture = user.GetUserCulture(_localizedTextService, _globalSettings.Value); + CultureInfo culture = user.GetUserCulture(_localizedTextService, _globalSettings.Value); return culture != null && culture.ToString() != identity.Culture; }, () => user.AllowedSections.UnsortedSequenceEqual(identity.AllowedApplications) == false, @@ -138,18 +152,15 @@ namespace Umbraco.Web.BackOffice.Filters /// /// This will update the current request IPrincipal to be correct and re-create the auth ticket /// - /// - /// - /// private async Task ReSync(IUser user, ActionExecutingContext actionContext) { - var backOfficeIdentityUser = _umbracoMapper.Map(user); + BackOfficeIdentityUser backOfficeIdentityUser = _umbracoMapper.Map(user); await _backOfficeSignInManager.SignInAsync(backOfficeIdentityUser, isPersistent: true); - //ensure the remainder of the request has the correct principal set + // ensure the remainder of the request has the correct principal set actionContext.HttpContext.SetPrincipalForRequest(ClaimsPrincipal.Current); - //flag that we've made changes + // flag that we've made changes _requestCache.Set(nameof(CheckIfUserTicketDataIsStaleFilter), true); } } From 375e056cef8cc1006135e370053a0d222c8da067 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 7 Dec 2020 18:16:42 +1100 Subject: [PATCH 58/63] Removing old code --- src/Umbraco.Core/Constants-Security.cs | 1 - .../Security/BackOfficeCookieManagerTests.cs | 29 +-- .../CheckIfUserTicketDataIsStaleAttribute.cs | 3 + .../Security/BackOfficeCookieManager.cs | 24 +- .../ConfigureBackOfficeCookieOptions.cs | 7 +- .../Security/AuthenticationExtensions.cs | 242 ------------------ .../Security/FixWindowsAuthMiddlware.cs | 52 ---- ...ForceRenewalCookieAuthenticationHandler.cs | 129 ---------- ...ceRenewalCookieAuthenticationMiddleware.cs | 33 --- src/Umbraco.Web/Umbraco.Web.csproj | 4 - 10 files changed, 19 insertions(+), 505 deletions(-) delete mode 100644 src/Umbraco.Web/Security/AuthenticationExtensions.cs delete mode 100644 src/Umbraco.Web/Security/FixWindowsAuthMiddlware.cs delete mode 100644 src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationHandler.cs delete mode 100644 src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationMiddleware.cs diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 9a4936d42d..d50e5390d2 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -40,7 +40,6 @@ namespace Umbraco.Core public const string BackOfficeTwoFactorRememberMeAuthenticationType = "UmbracoTwoFactorRememberMeCookie"; public const string EmptyPasswordPrefix = "___UIDEMPTYPWORD__"; - public const string ForceReAuthFlag = "umbraco-force-auth"; /// /// The prefix used for external identity providers for their authentication type diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs index 342083039f..80432cdce2 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs @@ -27,8 +27,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security Mock.Of(), runtime, Mock.Of(), - globalSettings, - Mock.Of()); + globalSettings); var result = mgr.ShouldAuthenticateRequest(new Uri("http://localhost/umbraco")); @@ -45,8 +44,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security Mock.Of(), runtime, Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco"), - globalSettings, - Mock.Of()); + globalSettings); var result = mgr.ShouldAuthenticateRequest(new Uri("http://localhost/umbraco")); @@ -66,8 +64,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security Mock.Of(), runtime, Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"), - globalSettings, - Mock.Of()); + globalSettings); var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost{remainingTimeoutSecondsPath}")); Assert.IsTrue(result); @@ -76,23 +73,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security Assert.IsTrue(result); } - [Test] - public void ShouldAuthenticateRequest_Force_Auth() - { - var globalSettings = new GlobalSettings(); - - var runtime = Mock.Of(x => x.Level == RuntimeLevel.Run); - - var mgr = new BackOfficeCookieManager( - Mock.Of(), - runtime, - Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"), - globalSettings, - Mock.Of(x => x.IsAvailable == true && x.Get(Constants.Security.ForceReAuthFlag) == "not null")); - - var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/notbackoffice")); - Assert.IsTrue(result); - } [Test] public void ShouldAuthenticateRequest_Not_Back_Office() @@ -105,8 +85,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security Mock.Of(), runtime, Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"), - globalSettings, - Mock.Of()); + globalSettings); var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/notbackoffice")); Assert.IsFalse(result); diff --git a/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs index a770a01e4d..61cb535ece 100644 --- a/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs @@ -20,6 +20,9 @@ using Umbraco.Web.Common.Security; namespace Umbraco.Web.BackOffice.Filters { + /// + /// + /// internal sealed class CheckIfUserTicketDataIsStaleAttribute : TypeFilterAttribute { public CheckIfUserTicketDataIsStaleAttribute() : base(typeof(CheckIfUserTicketDataIsStaleFilter)) diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs index 8664713c72..513bbd255c 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs @@ -25,31 +25,33 @@ namespace Umbraco.Web.BackOffice.Security private readonly IRuntimeState _runtime; private readonly IHostingEnvironment _hostingEnvironment; private readonly GlobalSettings _globalSettings; - private readonly IRequestCache _requestCache; private readonly string[] _explicitPaths; + /// + /// Initializes a new instance of the class. + /// public BackOfficeCookieManager( IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IHostingEnvironment hostingEnvironment, - GlobalSettings globalSettings, - IRequestCache requestCache) - : this(umbracoContextAccessor, runtime, hostingEnvironment, globalSettings, requestCache, null) + GlobalSettings globalSettings) + : this(umbracoContextAccessor, runtime, hostingEnvironment, globalSettings, null) { } + /// + /// Initializes a new instance of the class. + /// public BackOfficeCookieManager( IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IHostingEnvironment hostingEnvironment, GlobalSettings globalSettings, - IRequestCache requestCache, IEnumerable explicitPaths) { _umbracoContextAccessor = umbracoContextAccessor; _runtime = runtime; _hostingEnvironment = hostingEnvironment; _globalSettings = globalSettings; - _requestCache = requestCache; _explicitPaths = explicitPaths?.ToArray(); } @@ -57,7 +59,6 @@ namespace Umbraco.Web.BackOffice.Security /// Determines if we should authenticate the request /// /// The to check - /// true to check if the has been assigned in the request. /// true if the request should be authenticated /// /// We auth the request when: @@ -65,7 +66,7 @@ namespace Umbraco.Web.BackOffice.Security /// * it is an installer request /// * it is a preview request /// - public bool ShouldAuthenticateRequest(Uri requestUri, bool checkForceAuthTokens = true) + public bool ShouldAuthenticateRequest(Uri requestUri) { // Do not authenticate the request if we are not running (don't have a db, are not configured) - since we will never need // to know a current user in this scenario - we treat it as a new install. Without this we can have some issues @@ -84,11 +85,8 @@ namespace Umbraco.Web.BackOffice.Security return _explicitPaths.Any(x => x.InvariantEquals(requestUri.AbsolutePath)); } - if (// check the explicit flag - (checkForceAuthTokens && _requestCache.IsAvailable && _requestCache.Get(Constants.Security.ForceReAuthFlag) != null) - - // check back office - || requestUri.IsBackOfficeRequest(_globalSettings, _hostingEnvironment) + if (// check back office + requestUri.IsBackOfficeRequest(_globalSettings, _hostingEnvironment) // check installer || requestUri.IsInstallerRequest(_hostingEnvironment)) diff --git a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs index 9f90395ff3..b8568a2f03 100644 --- a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs @@ -33,7 +33,6 @@ namespace Umbraco.Web.BackOffice.Security private readonly IHostingEnvironment _hostingEnvironment; private readonly IRuntimeState _runtimeState; private readonly IDataProtectionProvider _dataProtection; - private readonly IRequestCache _requestCache; private readonly IUserService _userService; private readonly IIpResolver _ipResolver; private readonly ISystemClock _systemClock; @@ -48,7 +47,6 @@ namespace Umbraco.Web.BackOffice.Security /// The /// The /// The - /// The /// The /// The /// The @@ -60,7 +58,6 @@ namespace Umbraco.Web.BackOffice.Security IHostingEnvironment hostingEnvironment, IRuntimeState runtimeState, IDataProtectionProvider dataProtection, - IRequestCache requestCache, IUserService userService, IIpResolver ipResolver, ISystemClock systemClock) @@ -72,7 +69,6 @@ namespace Umbraco.Web.BackOffice.Security _hostingEnvironment = hostingEnvironment; _runtimeState = runtimeState; _dataProtection = dataProtection; - _requestCache = requestCache; _userService = userService; _ipResolver = ipResolver; _systemClock = systemClock; @@ -120,8 +116,7 @@ namespace Umbraco.Web.BackOffice.Security _umbracoContextAccessor, _runtimeState, _hostingEnvironment, - _globalSettings, - _requestCache); // _explicitPaths); TODO: Implement this once we do OAuth somehow + _globalSettings); // _explicitPaths); TODO: Implement this once we do OAuth somehow options.Events = new CookieAuthenticationEvents { diff --git a/src/Umbraco.Web/Security/AuthenticationExtensions.cs b/src/Umbraco.Web/Security/AuthenticationExtensions.cs deleted file mode 100644 index aa0cd6aca2..0000000000 --- a/src/Umbraco.Web/Security/AuthenticationExtensions.cs +++ /dev/null @@ -1,242 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Security.Claims; -using System.Security.Principal; -using System.Threading; -using System.Web; -using Microsoft.AspNetCore.Identity; -using Microsoft.Owin; -using Microsoft.Owin.Security; -using Newtonsoft.Json; -using Umbraco.Core; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Security; -using Umbraco.Extensions; -using Umbraco.Web.Composing; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Security -{ - /// - /// Extensions to create and renew and remove authentication tickets for the Umbraco back office - /// - public static class AuthenticationExtensions - { - /// - /// This will check the ticket to see if it is valid, if it is it will set the current thread's user and culture - /// - /// - /// - /// If true will attempt to renew the ticket - public static bool AuthenticateCurrentRequest(this HttpContextBase http, AuthenticationTicket ticket, bool renewTicket) - { - if (http == null) throw new ArgumentNullException(nameof(http)); - - // if there was a ticket, it's not expired, - it should not be renewed or its renewable - if (ticket?.Properties.ExpiresUtc != null && ticket.Properties.ExpiresUtc.Value > DateTimeOffset.UtcNow && (renewTicket == false || http.RenewUmbracoAuthTicket())) - { - try - { - // get the Umbraco user identity - if (!(ticket.Identity is UmbracoBackOfficeIdentity identity)) - throw new InvalidOperationException("The AuthenticationTicket specified does not contain the correct Identity type"); - - // set the principal object - var principal = new ClaimsPrincipal(identity); - - // It is actually not good enough to set this on the current app Context and the thread, it also needs - // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually - // an underlying fault of asp.net not propagating the User correctly. - if (HttpContext.Current != null) - { - HttpContext.Current.User = principal; - } - http.User = principal; - Thread.CurrentPrincipal = principal; - - // This is a back office request, we will also set the culture/ui culture - Thread.CurrentThread.CurrentCulture = - Thread.CurrentThread.CurrentUICulture = - new System.Globalization.CultureInfo(identity.Culture); - - return true; - } - catch (Exception ex) - { - if (ex is FormatException || ex is JsonReaderException) - { - // this will occur if the cookie data is invalid - - } - else - { - throw; - } - - } - } - - return false; - } - - - /// - /// This will return the current back office identity. - /// - /// - /// - /// Returns the current back office identity if an admin is authenticated otherwise null - /// - public static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContextBase http) - { - if (http == null) throw new ArgumentNullException(nameof(http)); - if (http.User == null) return null; //there's no user at all so no identity - - // If it's already a UmbracoBackOfficeIdentity - var backOfficeIdentity = http.User.GetUmbracoIdentity(); - if (backOfficeIdentity != null) return backOfficeIdentity; - - return null; - } - - /// - /// This will return the current back office identity. - /// - /// - /// - /// Returns the current back office identity if an admin is authenticated otherwise null - /// - internal static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContext http) - { - if (http == null) throw new ArgumentNullException("http"); - return new HttpContextWrapper(http).GetCurrentIdentity(); - } - - /// - /// This will force ticket renewal in the OWIN pipeline - /// - /// - /// - public static bool RenewUmbracoAuthTicket(this HttpContextBase http) - { - if (http == null) throw new ArgumentNullException("http"); - http.Items[Constants.Security.ForceReAuthFlag] = true; - return true; - } - - // NOTE: Migrated to netcore (though in a different way) - public static double GetRemainingAuthSeconds(this HttpContextBase http) - { - if (http == null) throw new ArgumentNullException(nameof(http)); - var ticket = http.GetUmbracoAuthTicket(); - return ticket.GetRemainingAuthSeconds(); - } - - // NOTE: Migrated to netcore (though in a different way) - public static double GetRemainingAuthSeconds(this AuthenticationTicket ticket) - { - var utcExpired = ticket?.Properties.ExpiresUtc; - if (utcExpired == null) return 0; - var secondsRemaining = utcExpired.Value.Subtract(DateTimeOffset.UtcNow).TotalSeconds; - return secondsRemaining; - } - - /// - /// Gets the umbraco auth ticket - /// - /// - /// - public static AuthenticationTicket GetUmbracoAuthTicket(this HttpContextBase http) - { - if (http == null) throw new ArgumentNullException(nameof(http)); - return GetAuthTicket(http, /*Current.Configs.Security() TODO*/new SecuritySettings().AuthCookieName); - } - - internal static AuthenticationTicket GetUmbracoAuthTicket(this HttpContext http) - { - if (http == null) throw new ArgumentNullException(nameof(http)); - return new HttpContextWrapper(http).GetUmbracoAuthTicket(); - } - - public static AuthenticationTicket GetUmbracoAuthTicket(this IOwinContext ctx) - { - if (ctx == null) throw new ArgumentNullException(nameof(ctx)); - return GetAuthTicket(ctx, /*Current.Configs.Security() TODO introduce injection instead of default value*/new SecuritySettings().AuthCookieName); - } - - private static AuthenticationTicket GetAuthTicket(this IOwinContext owinCtx, string cookieName) - { - var asDictionary = new Dictionary(); - foreach (var requestCookie in owinCtx.Request.Cookies) - { - var key = requestCookie.Key; - asDictionary[key] = requestCookie.Value; - } - - var secureFormat = owinCtx.GetUmbracoAuthTicketDataProtector(); - - // get the ticket - try - { - - return GetAuthTicket(secureFormat, asDictionary, cookieName); - } - catch (Exception) - { - owinCtx.Authentication.SignOut( - Constants.Security.BackOfficeAuthenticationType, - Constants.Security.BackOfficeExternalAuthenticationType); - return null; - } - } - - private static AuthenticationTicket GetAuthTicket(this HttpContextBase http, string cookieName) - { - var asDictionary = new Dictionary(); - for (var i = 0; i < http.Request.Cookies.Keys.Count; i++) - { - var key = http.Request.Cookies.Keys.Get(i); - asDictionary[key] = http.Request.Cookies[key].Value; - } - - var owinCtx = http.GetOwinContext(); - var secureFormat = owinCtx.GetUmbracoAuthTicketDataProtector(); - - // will only happen in tests - if (secureFormat == null) return null; - - // get the ticket - try - { - return GetAuthTicket(secureFormat, asDictionary, cookieName); - } - catch (Exception) - { - // occurs when decryption fails - - return null; - } - } - - private static AuthenticationTicket GetAuthTicket(ISecureDataFormat secureDataFormat, IDictionary cookies, string cookieName) - { - if (cookies == null) throw new ArgumentNullException(nameof(cookies)); - - if (cookies.ContainsKey(cookieName) == false) return null; - - var formsCookie = cookies[cookieName]; - if (formsCookie == null) - { - return null; - } - // get the ticket - - return secureDataFormat.Unprotect(formsCookie); - } - - - } -} diff --git a/src/Umbraco.Web/Security/FixWindowsAuthMiddlware.cs b/src/Umbraco.Web/Security/FixWindowsAuthMiddlware.cs deleted file mode 100644 index 3338344e73..0000000000 --- a/src/Umbraco.Web/Security/FixWindowsAuthMiddlware.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Linq; -using System.Security.Claims; -using System.Security.Principal; -using System.Threading.Tasks; -using Microsoft.Owin; -using Umbraco.Core; -using Umbraco.Core.Security; - -namespace Umbraco.Web.Security -{ - - /// - /// This is used to inspect the request to see if 2 x identities are assigned: A windows one and a back office one. - /// When this is the case, it means that auth has executed for Windows & auth has executed for our back office cookie - /// handler and now two identities have been assigned. Unfortunately, at some stage in the pipeline - I'm pretty sure - /// it's the Role Provider Module - it again changes the user's Principal to a RolePrincipal and discards the second - /// Identity which is the Back office identity thus preventing a user from accessing the back office... it's very annoying. - /// - /// To fix this, we re-set the user Principal to only have a single identity: the back office one, since we know this is - /// for a back office request. - /// - internal class FixWindowsAuthMiddlware : OwinMiddleware - { - public FixWindowsAuthMiddlware(OwinMiddleware next) : base(next) - { - } - - public override async Task Invoke(IOwinContext context) - { - if (context.Request.Uri.IsClientSideRequest() == false) - { - var claimsPrincipal = context.Request.User as ClaimsPrincipal; - if (claimsPrincipal != null - && claimsPrincipal.Identities.Count() > 1 - && claimsPrincipal.Identities.Any(x => x is WindowsIdentity) - && claimsPrincipal.Identities.Any(x => x is UmbracoBackOfficeIdentity)) - { - var backOfficeIdentity = claimsPrincipal.Identities.First(x => x is UmbracoBackOfficeIdentity); - if (backOfficeIdentity.IsAuthenticated) - { - context.Request.User = new ClaimsPrincipal(backOfficeIdentity); - } - } - } - - if (Next != null) - { - await Next.Invoke(context); - } - } - } -} diff --git a/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationHandler.cs b/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationHandler.cs deleted file mode 100644 index deb8e9fd63..0000000000 --- a/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationHandler.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.Cookies; -using Microsoft.Owin.Security.Infrastructure; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Security; - -namespace Umbraco.Web.Security -{ - /// - /// If a flag is set on the context to force renew the ticket, this will do it - /// - internal class ForceRenewalCookieAuthenticationHandler : AuthenticationHandler - { - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly IRequestCache _requestCache; - - public ForceRenewalCookieAuthenticationHandler(IUmbracoContextAccessor umbracoContextAccessor, IRequestCache requestCache) - { - _umbracoContextAccessor = umbracoContextAccessor; - _requestCache = requestCache; - } - - /// - /// This handler doesn't actually do any auth so we return null; - /// - /// - protected override Task AuthenticateCoreAsync() - { - return Task.FromResult((AuthenticationTicket)null); - } - - /// - /// Gets the ticket from the request - /// - /// - private AuthenticationTicket GetTicket() - { - var cookie = Options.CookieManager.GetRequestCookie(Context, Options.CookieName); - if (string.IsNullOrWhiteSpace(cookie)) - { - return null; - } - var ticket = Options.TicketDataFormat.Unprotect(cookie); - if (ticket == null) - { - return null; - } - return ticket; - } - - /// - /// This will check if the token exists in the request to force renewal - /// - /// - protected override Task ApplyResponseGrantAsync() - { - if (_umbracoContextAccessor.UmbracoContext == null || Context.Request.Uri.IsClientSideRequest()) - { - return Task.FromResult(0); - } - - //Now we need to check if we should force renew this based on a flag in the context and whether this is a request that is not normally renewed by OWIN... - // which means that it is not a normal URL that is authenticated. - - //var normalAuthUrl = ((BackOfficeCookieManager) Options.CookieManager) - // .ShouldAuthenticateRequest(Context, _umbracoContextAccessor.UmbracoContext.OriginalRequestUrl, - // //Pass in false, we want to know if this is a normal auth'd page - // checkForceAuthTokens: false); - ////This is auth'd normally, so OWIN will naturally take care of the cookie renewal - //if (normalAuthUrl) return Task.FromResult(0); - - //check for the special flag in either the owin or http context - var shouldRenew = Context.Get(Constants.Security.ForceReAuthFlag) != null || (_requestCache.IsAvailable && _requestCache.Get(Constants.Security.ForceReAuthFlag) != null); - - if (shouldRenew) - { - var signin = Helper.LookupSignIn(Options.AuthenticationType); - var shouldSignin = signin != null; - var signout = Helper.LookupSignOut(Options.AuthenticationType, Options.AuthenticationMode); - var shouldSignout = signout != null; - - //we don't care about the sign in/sign out scenario, only renewal - if (shouldSignin == false && shouldSignout == false) - { - //get the ticket - var ticket = GetTicket(); - if (ticket != null) - { - var currentUtc = Options.SystemClock.UtcNow; - var issuedUtc = ticket.Properties.IssuedUtc; - var expiresUtc = ticket.Properties.ExpiresUtc; - - if (expiresUtc.HasValue && issuedUtc.HasValue) - { - var timeElapsed = currentUtc.Subtract(issuedUtc.Value); - var timeRemaining = expiresUtc.Value.Subtract(currentUtc); - - //if it's time to renew, then do it - if (timeRemaining < timeElapsed) - { - //renew the date/times - ticket.Properties.IssuedUtc = currentUtc; - var timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value); - ticket.Properties.ExpiresUtc = currentUtc.Add(timeSpan); - - //now save back all the required cookie details - var cookieValue = Options.TicketDataFormat.Protect(ticket); - - var cookieOptions = ((UmbracoBackOfficeCookieAuthOptions)Options).CreateRequestCookieOptions(Context, ticket); - - Options.CookieManager.AppendResponseCookie( - Context, - Options.CookieName, - cookieValue, - cookieOptions); - } - - - } - } - } - } - - return Task.FromResult(0); - } - } -} diff --git a/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationMiddleware.cs b/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationMiddleware.cs deleted file mode 100644 index b8f8127544..0000000000 --- a/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationMiddleware.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.Owin; -using Microsoft.Owin.Security.Cookies; -using Microsoft.Owin.Security.Infrastructure; -using Owin; -using Umbraco.Core.Cache; - -namespace Umbraco.Web.Security -{ - /// - /// This middleware is used simply to force renew the auth ticket if a flag to do so is found in the request - /// - internal class ForceRenewalCookieAuthenticationMiddleware : CookieAuthenticationMiddleware - { - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly IRequestCache _requestCache; - - public ForceRenewalCookieAuthenticationMiddleware( - OwinMiddleware next, - IAppBuilder app, - UmbracoBackOfficeCookieAuthOptions options, - IUmbracoContextAccessor umbracoContextAccessor, - IRequestCache requestCache) : base(next, app, options) - { - _umbracoContextAccessor = umbracoContextAccessor; - _requestCache = requestCache; - } - - protected override AuthenticationHandler CreateHandler() - { - return new ForceRenewalCookieAuthenticationHandler(_umbracoContextAccessor, _requestCache); - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 50d379102d..65cba08ec9 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -190,7 +190,6 @@ - @@ -203,9 +202,6 @@ - - - From 36342438da94d3f0362915f67036e5af3a7bd799 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 7 Dec 2020 18:47:25 +1100 Subject: [PATCH 59/63] Deleting old code --- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - .../AngularAntiForgeryTests.cs | 54 ------ .../UmbracoAuthorizedApiController.cs | 4 +- ...alidateAngularAntiForgeryTokenAttribute.cs | 8 +- src/Umbraco.Web/AppBuilderExtensions.cs | 55 ------ src/Umbraco.Web/Logging/OwinLogger.cs | 64 ------- src/Umbraco.Web/Logging/OwinLoggerFactory.cs | 19 -- ...dateMvcAngularAntiForgeryTokenAttribute.cs | 61 ------- src/Umbraco.Web/OwinExtensions.cs | 75 -------- .../OwinMiddlewareConfiguredEventArgs.cs | 15 -- .../Security/AppBuilderExtensions.cs | 88 --------- .../Security/GetUserSecondsMiddleWare.cs | 145 --------------- .../Security/IdentityAuditEventArgs.cs | 134 -------------- .../Security/IdentityFactoryMiddleware.cs | 106 ----------- .../UmbracoAuthTicketDataProtector.cs | 19 -- .../UmbracoBackOfficeCookieAuthOptions.cs | 53 ------ .../Security/UmbracoSecureDataFormat.cs | 67 ------- src/Umbraco.Web/Security/WebAuthExtensions.cs | 60 ------- src/Umbraco.Web/Umbraco.Web.csproj | 23 --- src/Umbraco.Web/UmbracoApplicationBase.cs | 7 +- src/Umbraco.Web/UmbracoDefaultOwinStartup.cs | 104 ----------- .../Filters/AngularAntiForgeryHelper.cs | 105 ----------- ...alidateAngularAntiForgeryTokenAttribute.cs | 43 ----- .../CDF/ClientDependencyComponent.cs | 77 -------- .../WebAssets/CDF/ClientDependencyComposer.cs | 16 -- .../CDF/ClientDependencyConfiguration.cs | 151 ---------------- .../CDF/ClientDependencyRuntimeMinifier.cs | 169 ------------------ .../WebAssets/CDF/DependencyPathRenderer.cs | 49 ----- .../CDF/UmbracoClientDependencyLoader.cs | 45 ----- 29 files changed, 9 insertions(+), 1808 deletions(-) delete mode 100644 src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs delete mode 100644 src/Umbraco.Web/AppBuilderExtensions.cs delete mode 100644 src/Umbraco.Web/Logging/OwinLogger.cs delete mode 100644 src/Umbraco.Web/Logging/OwinLoggerFactory.cs delete mode 100644 src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs delete mode 100644 src/Umbraco.Web/OwinExtensions.cs delete mode 100644 src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs delete mode 100644 src/Umbraco.Web/Security/AppBuilderExtensions.cs delete mode 100644 src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs delete mode 100644 src/Umbraco.Web/Security/IdentityAuditEventArgs.cs delete mode 100644 src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs delete mode 100644 src/Umbraco.Web/Security/UmbracoAuthTicketDataProtector.cs delete mode 100644 src/Umbraco.Web/Security/UmbracoBackOfficeCookieAuthOptions.cs delete mode 100644 src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs delete mode 100644 src/Umbraco.Web/Security/WebAuthExtensions.cs delete mode 100644 src/Umbraco.Web/UmbracoDefaultOwinStartup.cs delete mode 100644 src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs delete mode 100644 src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs delete mode 100644 src/Umbraco.Web/WebAssets/CDF/ClientDependencyComponent.cs delete mode 100644 src/Umbraco.Web/WebAssets/CDF/ClientDependencyComposer.cs delete mode 100644 src/Umbraco.Web/WebAssets/CDF/ClientDependencyConfiguration.cs delete mode 100644 src/Umbraco.Web/WebAssets/CDF/ClientDependencyRuntimeMinifier.cs delete mode 100644 src/Umbraco.Web/WebAssets/CDF/DependencyPathRenderer.cs delete mode 100644 src/Umbraco.Web/WebAssets/CDF/UmbracoClientDependencyLoader.cs diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 008fbc47d7..44e81d604a 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -195,7 +195,6 @@ - diff --git a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs b/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs deleted file mode 100644 index fd30f1b5ec..0000000000 --- a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.IO; -using System.Net; -using System.Security.Principal; -using System.Web; -using NUnit.Framework; -using Umbraco.Web.WebApi.Filters; - -namespace Umbraco.Tests.Web.AngularIntegration -{ - [TestFixture] - public class AngularAntiForgeryTests - { - - [TearDown] - public void TearDown() - { - HttpContext.Current = null; - } - - [Test] - public void Can_Validate_Generated_Tokens() - { - using (var writer = new StringWriter()) - { - HttpContext.Current = new HttpContext(new HttpRequest("test.html", "http://test/", ""), new HttpResponse(writer)); - - string cookieToken, headerToken; - AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - - Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); - } - - } - - [Test] - public void Can_Validate_Generated_Tokens_With_User() - { - using (var writer = new StringWriter()) - { - HttpContext.Current = new HttpContext(new HttpRequest("test.html", "http://test/", ""), new HttpResponse(writer)) - { - User = new GenericPrincipal(new HttpListenerBasicIdentity("test", "test"), new string[] {}) - }; - - string cookieToken, headerToken; - AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - - Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); - } - - } - - } -} diff --git a/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs index a807c663d0..9c415fe180 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UmbracoAuthorizedApiController.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; @@ -16,7 +16,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// is logged in using forms authentication which indicates the seconds remaining /// before their timeout expires. /// - [IsBackOffice] + [IsBackOffice] [UmbracoUserTimeoutFilter] [Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] [DisableBrowserCache] diff --git a/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs index f7fd174e03..ef8e22c9d8 100644 --- a/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/ValidateAngularAntiForgeryTokenAttribute.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Net; using System.Security.Claims; using System.Threading.Tasks; @@ -23,7 +23,8 @@ namespace Umbraco.Web.BackOffice.Filters /// public sealed class ValidateAngularAntiForgeryTokenAttribute : TypeFilterAttribute { - public ValidateAngularAntiForgeryTokenAttribute() : base(typeof(ValidateAngularAntiForgeryTokenFilter)) + public ValidateAngularAntiForgeryTokenAttribute() + : base(typeof(ValidateAngularAntiForgeryTokenFilter)) { } @@ -44,12 +45,13 @@ namespace Umbraco.Web.BackOffice.Filters { if (context.Controller is ControllerBase controller && controller.User.Identity is ClaimsIdentity userIdentity) { - //if there is not CookiePath claim, then exit + // if there is not CookiePath claim, then exit if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false) { await next(); } } + var cookieToken = _cookieManager.GetCookieValue(Constants.Web.CsrfValidationCookieName); var httpContext = context.HttpContext; diff --git a/src/Umbraco.Web/AppBuilderExtensions.cs b/src/Umbraco.Web/AppBuilderExtensions.cs deleted file mode 100644 index f4766bc414..0000000000 --- a/src/Umbraco.Web/AppBuilderExtensions.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Web; -using Microsoft.AspNet.SignalR; -using Microsoft.Owin.Logging; -using Owin; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; -using Umbraco.Web.Logging; - -namespace Umbraco.Web -{ - /// - /// Provides general extension methods to IAppBuilder. - /// - public static class AppBuilderExtensions - { - /// - /// Called at the end of configuring middleware - /// - /// The app builder. - /// - /// This could be used for something else in the future - maybe to inform Umbraco that middleware is done/ready, but for - /// now this is used to raise the custom event - /// - /// This is an extension method in case developer entirely replace the UmbracoDefaultOwinStartup class, in which case they will - /// need to ensure they call this extension method in their startup class. - /// - public static void FinalizeMiddlewareConfiguration(this IAppBuilder app) - { - UmbracoDefaultOwinStartup.OnMiddlewareConfigured(new OwinMiddlewareConfiguredEventArgs(app)); - } - - /// - /// Sets the OWIN logger to use Umbraco's logging system. - /// - /// The app builder. - public static void SetUmbracoLoggerFactory(this IAppBuilder app) - { - app.SetLoggerFactory(new OwinLoggerFactory()); - } - - /// - /// Configures SignalR. - /// - /// The app builder. - /// - /// - public static IAppBuilder UseSignalR(this IAppBuilder app, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - { - var umbracoPath = globalSettings.GetUmbracoMvcArea(hostingEnvironment); - var signalrPath = HttpRuntime.AppDomainAppVirtualPath + umbracoPath + "/BackOffice/signalr"; - return app.MapSignalR(signalrPath, new HubConfiguration { EnableDetailedErrors = true }); - } - } -} diff --git a/src/Umbraco.Web/Logging/OwinLogger.cs b/src/Umbraco.Web/Logging/OwinLogger.cs deleted file mode 100644 index 7983ee36c7..0000000000 --- a/src/Umbraco.Web/Logging/OwinLogger.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Diagnostics; -using Microsoft.Extensions.Logging; - -namespace Umbraco.Web.Logging -{ - internal class OwinLogger : Microsoft.Owin.Logging.ILogger - { - private readonly ILogger _logger; - private readonly Lazy _type; - - public OwinLogger(ILogger logger, Lazy type) - { - _logger = logger; - _type = type; - } - - /// - /// Aggregates most logging patterns to a single method. This must be compatible with the Func representation in the OWIN environment. - /// To check IsEnabled call WriteCore with only TraceEventType and check the return value, no event will be written. - /// - /// - /// - public bool WriteCore(TraceEventType eventType, int eventId, object state, Exception exception, Func formatter) - { - if (state == null) state = ""; - switch (eventType) - { - case TraceEventType.Critical: - _logger.LogCritical(exception, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Error: - _logger.LogError(exception, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Warning: - _logger.LogWarning("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Information: - _logger.LogInformation("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Verbose: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Start: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Stop: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Suspend: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Resume: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - case TraceEventType.Transfer: - _logger.LogDebug("[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state); - return true; - default: - throw new ArgumentOutOfRangeException("eventType"); - } - } - } -} diff --git a/src/Umbraco.Web/Logging/OwinLoggerFactory.cs b/src/Umbraco.Web/Logging/OwinLoggerFactory.cs deleted file mode 100644 index d8b76145c6..0000000000 --- a/src/Umbraco.Web/Logging/OwinLoggerFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using Microsoft.Owin.Logging; -using Umbraco.Core; - -namespace Umbraco.Web.Logging -{ - public class OwinLoggerFactory : ILoggerFactory - { - /// - /// Creates a new ILogger instance of the given name. - /// - /// - /// - public Microsoft.Owin.Logging.ILogger Create(string name) - { - return new OwinLogger(StaticApplicationLogging.Logger, new Lazy(() => Type.GetType(name) ?? typeof (OwinLogger))); - } - } -} diff --git a/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs deleted file mode 100644 index 484ee70ff7..0000000000 --- a/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Security.Claims; -using System.Web.Mvc; -using Umbraco.Web.WebApi.Filters; - -namespace Umbraco.Web.Mvc -{ - /// - /// A filter to check for the csrf token based on Angular's standard approach - /// - /// - /// Code derived from http://ericpanorel.net/2013/07/28/spa-authentication-and-csrf-mvc4-antiforgery-implementation/ - /// - /// If the authentication type is cookie based, then this filter will execute, otherwise it will be disabled - /// - public sealed class ValidateMvcAngularAntiForgeryTokenAttribute : ActionFilterAttribute - { - public override void OnActionExecuting(ActionExecutingContext filterContext) - { - var userIdentity = filterContext.HttpContext.User.Identity as ClaimsIdentity; - if (userIdentity != null) - { - //if there is not CookiePath claim, then exit - if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false) - { - base.OnActionExecuting(filterContext); - return; - } - } - - string failedReason; - var headers = new List>>(); - foreach (var key in filterContext.HttpContext.Request.Headers.AllKeys) - { - if (headers.Any(x => x.Key == key)) - { - var found = headers.First(x => x.Key == key); - found.Value.Add(filterContext.HttpContext.Request.Headers[key]); - } - else - { - headers.Add(new KeyValuePair>(key, new List { filterContext.HttpContext.Request.Headers[key] })); - } - } - var cookie = filterContext.HttpContext.Request.Cookies[Core.Constants.Web.CsrfValidationCookieName]; - if (AngularAntiForgeryHelper.ValidateHeaders( - headers.Select(x => new KeyValuePair>(x.Key, x.Value)).ToArray(), - cookie == null ? "" : cookie.Value, - out failedReason) == false) - { - var result = new HttpStatusCodeResult(HttpStatusCode.ExpectationFailed); - filterContext.Result = result; - return; - } - - base.OnActionExecuting(filterContext); - } - } -} diff --git a/src/Umbraco.Web/OwinExtensions.cs b/src/Umbraco.Web/OwinExtensions.cs deleted file mode 100644 index 801ceae191..0000000000 --- a/src/Umbraco.Web/OwinExtensions.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Web; -using Microsoft.Owin; -using Microsoft.Owin.Security; -using Umbraco.Core; -using Umbraco.Web.Security; - -namespace Umbraco.Web -{ - public static class OwinExtensions - { - - /// - /// Gets the for the Umbraco back office cookie - /// - /// - /// - internal static ISecureDataFormat GetUmbracoAuthTicketDataProtector(this IOwinContext owinContext) - { - var found = owinContext.Get(); - return found?.Protector; - } - - public static string GetCurrentRequestIpAddress(this IOwinContext owinContext) - { - if (owinContext == null) - { - return "Unknown, owinContext is null"; - } - if (owinContext.Request == null) - { - return "Unknown, owinContext.Request is null"; - } - - var httpContext = owinContext.TryGetHttpContext(); - if (httpContext == false) - { - return "Unknown, cannot resolve HttpContext from owinContext"; - } - - return httpContext.Result.GetCurrentRequestIpAddress(); - } - - /// - /// Nasty little hack to get HttpContextBase from an owin context - /// - /// - /// - internal static Attempt TryGetHttpContext(this IOwinContext owinContext) - { - var ctx = owinContext.Get(typeof(HttpContextBase).FullName); - return ctx == null ? Attempt.Fail() : Attempt.Succeed(ctx); - } - - /// - /// Adapted from Microsoft.AspNet.Identity.Owin.OwinContextExtensions - /// - public static T Get(this IOwinContext context) - { - if (context == null) throw new ArgumentNullException(nameof(context)); - return context.Get(GetKey(typeof(T))); - } - - public static IOwinContext Set(this IOwinContext context, T value) - { - if (context == null) throw new ArgumentNullException(nameof(context)); - return context.Set(GetKey(typeof(T)), value); - } - - private static string GetKey(Type t) - { - return "AspNet.Identity.Owin:" + t.AssemblyQualifiedName; - } - } -} diff --git a/src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs b/src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs deleted file mode 100644 index 8b4d46a373..0000000000 --- a/src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Owin; - -namespace Umbraco.Web -{ - public class OwinMiddlewareConfiguredEventArgs : EventArgs - { - public OwinMiddlewareConfiguredEventArgs(IAppBuilder appBuilder) - { - AppBuilder = appBuilder; - } - - public IAppBuilder AppBuilder { get; private set; } - } -} diff --git a/src/Umbraco.Web/Security/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/AppBuilderExtensions.cs deleted file mode 100644 index aa5ff7a4e1..0000000000 --- a/src/Umbraco.Web/Security/AppBuilderExtensions.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Threading; -using Microsoft.Owin; -using Microsoft.Owin.Extensions; -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.Cookies; -using Microsoft.Owin.Security.DataProtection; -using Owin; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; -using Umbraco.Web.Composing; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Security -{ - /// - /// Provides security/identity extension methods to IAppBuilder. - /// - public static class AppBuilderExtensions - { - - // TODO: Migrate this! - - /// - /// Ensures that the cookie middleware for validating external logins is assigned to the pipeline with the correct - /// Umbraco back office configuration - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// By default this will be configured to execute on PipelineStage.Authenticate - /// - public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState,GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IRequestCache requestCache) - { - return app.UseUmbracoBackOfficeExternalCookieAuthentication(umbracoContextAccessor, runtimeState, globalSettings, hostingEnvironment, requestCache, PipelineStage.Authenticate); - } - - /// - /// Ensures that the cookie middleware for validating external logins is assigned to the pipeline with the correct - /// Umbraco back office configuration - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app, - IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, - GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IRequestCache requestCache, PipelineStage stage) - { - if (app == null) throw new ArgumentNullException(nameof(app)); - if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); - if (hostingEnvironment == null) throw new ArgumentNullException(nameof(hostingEnvironment)); - - app.UseCookieAuthentication(new CookieAuthenticationOptions - { - AuthenticationType = Constants.Security.BackOfficeExternalAuthenticationType, - AuthenticationMode = AuthenticationMode.Passive, - CookieName = Constants.Security.BackOfficeExternalCookieName, - ExpireTimeSpan = TimeSpan.FromMinutes(5), - CookiePath = "/", - CookieSecure = globalSettings.UseHttps ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest, - CookieHttpOnly = true, - CookieDomain = new SecuritySettings().AuthCookieDomain // TODO inject settings - }, stage); - - app.UseStageMarker(stage); - return app; - } - - public static void SanitizeThreadCulture(this IAppBuilder app) - { - Thread.CurrentThread.SanitizeThreadCulture(); - } - - } -} diff --git a/src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs b/src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs deleted file mode 100644 index 62724a4846..0000000000 --- a/src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.Diagnostics; -using System.Globalization; -using System.Threading.Tasks; -using System.Web; -using Microsoft.Extensions.Options; -using Microsoft.Owin; -using Microsoft.Owin.Logging; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; - -namespace Umbraco.Web.Security -{ - /// - /// Custom middleware to return the remaining seconds the user has before they are logged out - /// - /// - /// This is quite a custom request because in most situations we just want to return the seconds and don't want - /// to renew the auth ticket, however if KeepUserLoggedIn is true, then we do want to renew the auth ticket for - /// this request! - /// - internal class GetUserSecondsMiddleWare : OwinMiddleware - { - private readonly UmbracoBackOfficeCookieAuthOptions _authOptions; - private readonly GlobalSettings _globalSettings; - private readonly SecuritySettings _security; - private readonly ILogger _logger; - private readonly IHostingEnvironment _hostingEnvironment; - - public GetUserSecondsMiddleWare( - OwinMiddleware next, - UmbracoBackOfficeCookieAuthOptions authOptions, - GlobalSettings globalSettings, - IOptions security, - ILogger logger, - IHostingEnvironment hostingEnvironment) - : base(next) - { - _authOptions = authOptions ?? throw new ArgumentNullException(nameof(authOptions)); - _globalSettings = globalSettings; - _security = security.Value; - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _hostingEnvironment = hostingEnvironment; - } - - public override async Task Invoke(IOwinContext context) - { - var request = context.Request; - var response = context.Response; - - if (request.Uri.Scheme.InvariantStartsWith("http") - && request.Uri.AbsolutePath.InvariantEquals( - $"{_globalSettings.GetBackOfficePath(_hostingEnvironment)}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds")) - { - var cookie = _authOptions.CookieManager.GetRequestCookie(context, _security.AuthCookieName); - if (cookie.IsNullOrWhiteSpace() == false) - { - var ticket = _authOptions.TicketDataFormat.Unprotect(cookie); - if (ticket != null) - { - var remainingSeconds = ticket.Properties.ExpiresUtc.HasValue - ? (ticket.Properties.ExpiresUtc.Value - _authOptions.SystemClock.UtcNow).TotalSeconds - : 0; - - response.ContentType = "application/json; charset=utf-8"; - response.StatusCode = 200; - response.Headers.Add("Cache-Control", new[] { "no-cache" }); - response.Headers.Add("Pragma", new[] { "no-cache" }); - response.Headers.Add("Expires", new[] { "-1" }); - response.Headers.Add("Date", new[] { _authOptions.SystemClock.UtcNow.ToString("R") }); - - //Ok, so here we need to check if we want to process/renew the auth ticket for each - // of these requests. If that is the case, the user will really never be logged out until they - // close their browser (there will be edge cases of that, especially when debugging) - if (_security.KeepUserLoggedIn) - { - var currentUtc = _authOptions.SystemClock.UtcNow; - var issuedUtc = ticket.Properties.IssuedUtc; - var expiresUtc = ticket.Properties.ExpiresUtc; - - if (expiresUtc.HasValue && issuedUtc.HasValue) - { - var timeElapsed = currentUtc.Subtract(issuedUtc.Value); - var timeRemaining = expiresUtc.Value.Subtract(currentUtc); - - //if it's time to renew, then do it - if (timeRemaining < timeElapsed) - { - // TODO: This would probably be simpler just to do: context.OwinContext.Authentication.SignIn(context.Properties, identity); - // this will invoke the default Cookie middleware to basically perform this logic for us. - - ticket.Properties.IssuedUtc = currentUtc; - var timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value); - ticket.Properties.ExpiresUtc = currentUtc.Add(timeSpan); - - var cookieValue = _authOptions.TicketDataFormat.Protect(ticket); - - var cookieOptions = _authOptions.CreateRequestCookieOptions(context, ticket); - - _authOptions.CookieManager.AppendResponseCookie( - context, - _authOptions.CookieName, - cookieValue, - cookieOptions); - - remainingSeconds = (ticket.Properties.ExpiresUtc.Value - currentUtc).TotalSeconds; - } - } - - // NOTE: SessionIdValidator has been moved to netcore - ////We also need to re-validate the user's session if we are relying on this ping to keep their session alive - //await SessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context, _authOptions.CookieManager, _authOptions.SystemClock, issuedUtc, ticket.Identity, _globalSettings); - } - else if (remainingSeconds <= 30) - { - //NOTE: We are using 30 seconds because that is what is coded into angular to force logout to give some headway in - // the timeout process. - - _logger.WriteCore(TraceEventType.Information, 0, - string.Format("User logged will be logged out due to timeout: {0}, IP Address: {1}", ticket.Identity.Name, request.RemoteIpAddress), - null, null); - } - - await response.WriteAsync(remainingSeconds.ToString(CultureInfo.InvariantCulture)); - return; - } - } - - // HACK: we need to suppress the stupid forms authentication module but we can only do that by using non owin stuff - if (HttpContext.Current != null && HttpContext.Current.Response != null) - { - HttpContext.Current.Response.SuppressFormsAuthenticationRedirect = true; - } - - response.StatusCode = 401; - } - else if (Next != null) - { - await Next.Invoke(context); - } - } - } -} diff --git a/src/Umbraco.Web/Security/IdentityAuditEventArgs.cs b/src/Umbraco.Web/Security/IdentityAuditEventArgs.cs deleted file mode 100644 index b3b193a78f..0000000000 --- a/src/Umbraco.Web/Security/IdentityAuditEventArgs.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Threading; -using Umbraco.Extensions; - - -namespace Umbraco.Web.Security -{ - - /// - /// This class is used by events raised from the BackofficeUserManager - /// - public class IdentityAuditEventArgs : EventArgs - { - /// - /// The action that got triggered from the audit event - /// - public AuditEvent Action { get; private set; } - - /// - /// Current date/time in UTC format - /// - public DateTime DateTimeUtc { get; private set; } - - /// - /// The source IP address of the user performing the action - /// - public string IpAddress { get; private set; } - - /// - /// The user affected by the event raised - /// - public int AffectedUser { get; private set; } - - /// - /// If a user is performing an action on a different user, then this will be set. Otherwise it will be -1 - /// - public int PerformingUser { get; private set; } - - /// - /// An optional comment about the action being logged - /// - public string Comment { get; private set; } - - /// - /// This property is always empty except in the LoginFailed event for an unknown user trying to login - /// - public string Username { get; private set; } - - - /// - /// Default constructor - /// - /// - /// - /// - /// - /// - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string comment = null, int performingUser = -1, int affectedUser = -1) - { - DateTimeUtc = DateTime.UtcNow; - Action = action; - - IpAddress = ipAddress; - Comment = comment; - AffectedUser = affectedUser; - - PerformingUser = performingUser == -1 - ? GetCurrentRequestBackOfficeUserId() - : performingUser; - } - - /// - /// Creates an instance without a performing or affected user (the id will be set to -1) - /// - /// - /// - /// - /// - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string username, string comment) - { - DateTimeUtc = DateTime.UtcNow; - Action = action; - - IpAddress = ipAddress; - Username = username; - Comment = comment; - - PerformingUser = -1; - } - - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string username, string comment, int performingUser) - { - DateTimeUtc = DateTime.UtcNow; - Action = action; - - IpAddress = ipAddress; - Username = username; - Comment = comment; - - PerformingUser = performingUser == -1 - ? GetCurrentRequestBackOfficeUserId() - : performingUser; - } - - /// - /// Returns the current logged in backoffice user's Id logging if there is one - /// - /// - protected int GetCurrentRequestBackOfficeUserId() - { - var userId = -1; - var backOfficeIdentity = Thread.CurrentPrincipal.GetUmbracoIdentity(); - if (backOfficeIdentity != null) - int.TryParse(backOfficeIdentity.Id.ToString(), out userId); - return userId; - } - } - - public enum AuditEvent - { - AccountLocked, - AccountUnlocked, - ForgotPasswordRequested, - ForgotPasswordChangedSuccess, - LoginFailed, - LoginRequiresVerification, - LoginSucces, - LogoutSuccess, - PasswordChanged, - PasswordReset, - ResetAccessFailedCount, - SendingUserInvite - } -} diff --git a/src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs b/src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs deleted file mode 100644 index 2975149107..0000000000 --- a/src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.Owin; -using Microsoft.Owin.Security.DataProtection; - -namespace Umbraco.Web.Security -{ - /// - /// Adapted from Microsoft.AspNet.Identity.Owin.IdentityFactoryMiddleware - /// - public class IdentityFactoryMiddleware : OwinMiddleware - where TResult : class, IDisposable - where TOptions : IdentityFactoryOptions - { - /// The next middleware in the OWIN pipeline to invoke - /// Configuration options for the middleware - public IdentityFactoryMiddleware(OwinMiddleware next, TOptions options) - : base(next) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - if (options.Provider == null) throw new ArgumentException("options.Provider"); - - Options = options; - } - - /// - /// Configuration options - /// - public TOptions Options { get; private set; } - - /// - /// Create an object using the Options.Provider, storing it in the OwinContext and then disposes the object when finished - /// - /// - /// - public override async Task Invoke(IOwinContext context) - { - var instance = Options.Provider.Create(Options, context); - try - { - context.Set(instance); - if (Next != null) - { - await Next.Invoke(context); - } - } - finally - { - Options.Provider.Dispose(Options, instance); - } - } - } - - public class IdentityFactoryOptions where T : class, IDisposable - { - /// - /// Used to configure the data protection provider - /// - public IDataProtectionProvider DataProtectionProvider { get; set; } - - /// - /// Provider used to Create and Dispose objects - /// - public IdentityFactoryProvider Provider { get; set; } - } - - public class IdentityFactoryProvider where T : class, IDisposable - { - public IdentityFactoryProvider() - { - OnDispose = (options, instance) => { }; - OnCreate = (options, context) => null; - } - - /// - /// A delegate assigned to this property will be invoked when the related method is called - /// - public Func, IOwinContext, T> OnCreate { get; set; } - - /// - /// A delegate assigned to this property will be invoked when the related method is called - /// - public Action, T> OnDispose { get; set; } - - /// - /// Calls the OnCreate Delegate - /// - /// - /// - /// - public virtual T Create(IdentityFactoryOptions options, IOwinContext context) - { - return OnCreate(options, context); - } - - /// - /// Calls the OnDispose delegate - /// - /// - /// - public virtual void Dispose(IdentityFactoryOptions options, T instance) - { - OnDispose(options, instance); - } - } -} diff --git a/src/Umbraco.Web/Security/UmbracoAuthTicketDataProtector.cs b/src/Umbraco.Web/Security/UmbracoAuthTicketDataProtector.cs deleted file mode 100644 index 3824935559..0000000000 --- a/src/Umbraco.Web/Security/UmbracoAuthTicketDataProtector.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using Microsoft.Owin.Security; -using Umbraco.Core; - -namespace Umbraco.Web.Security -{ - /// - /// This is used so that we can retrieve the auth ticket protector from an IOwinContext - /// - internal class UmbracoAuthTicketDataProtector : DisposableObjectSlim - { - public UmbracoAuthTicketDataProtector(ISecureDataFormat protector) - { - Protector = protector ?? throw new ArgumentNullException(nameof(protector)); - } - - public ISecureDataFormat Protector { get; } - } -} diff --git a/src/Umbraco.Web/Security/UmbracoBackOfficeCookieAuthOptions.cs b/src/Umbraco.Web/Security/UmbracoBackOfficeCookieAuthOptions.cs deleted file mode 100644 index 34669bc5ae..0000000000 --- a/src/Umbraco.Web/Security/UmbracoBackOfficeCookieAuthOptions.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using Microsoft.Owin; -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.Cookies; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Hosting; -using Umbraco.Core.IO; - -namespace Umbraco.Web.Security -{ - /// - /// Umbraco auth cookie options - /// - public sealed class UmbracoBackOfficeCookieAuthOptions : CookieAuthenticationOptions - { - public int LoginTimeoutMinutes { get; } - - /// - /// Creates the cookie options for saving the auth cookie - /// - /// - /// - /// - public CookieOptions CreateRequestCookieOptions(IOwinContext ctx, AuthenticationTicket ticket) - { - if (ctx == null) throw new ArgumentNullException(nameof(ctx)); - if (ticket == null) throw new ArgumentNullException(nameof(ticket)); - - var issuedUtc = ticket.Properties.IssuedUtc ?? SystemClock.UtcNow; - var expiresUtc = ticket.Properties.ExpiresUtc ?? issuedUtc.Add(ExpireTimeSpan); - - var cookieOptions = new CookieOptions - { - Path = "/", - Domain = this.CookieDomain ?? null, - HttpOnly = true, - Secure = this.CookieSecure == CookieSecureOption.Always - || (this.CookieSecure == CookieSecureOption.SameAsRequest && ctx.Request.IsSecure), - }; - - if (ticket.Properties.IsPersistent) - { - cookieOptions.Expires = expiresUtc.UtcDateTime; - } - - return cookieOptions; - } - - } -} diff --git a/src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs b/src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs deleted file mode 100644 index d1b0c54279..0000000000 --- a/src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using Microsoft.Owin.Security; -using Umbraco.Core.Security; - -namespace Umbraco.Web.Security -{ - - /// - /// Custom secure format that ensures the Identity in the ticket is and not just a ClaimsIdentity - /// - internal class UmbracoSecureDataFormat : ISecureDataFormat - { - private readonly int _loginTimeoutMinutes; - private readonly ISecureDataFormat _ticketDataFormat; - - public UmbracoSecureDataFormat(int loginTimeoutMinutes, ISecureDataFormat ticketDataFormat) - { - _loginTimeoutMinutes = loginTimeoutMinutes; - _ticketDataFormat = ticketDataFormat ?? throw new ArgumentNullException(nameof(ticketDataFormat)); - } - - public string Protect(AuthenticationTicket data) - { - var backofficeIdentity = (UmbracoBackOfficeIdentity)data.Identity; - - //create a new ticket based on the passed in tickets details, however, we'll adjust the expires utc based on the specified timeout mins - var ticket = new AuthenticationTicket(backofficeIdentity, - new AuthenticationProperties(data.Properties.Dictionary) - { - IssuedUtc = data.Properties.IssuedUtc, - ExpiresUtc = data.Properties.ExpiresUtc ?? DateTimeOffset.UtcNow.AddMinutes(_loginTimeoutMinutes), - AllowRefresh = data.Properties.AllowRefresh, - IsPersistent = data.Properties.IsPersistent, - RedirectUri = data.Properties.RedirectUri - }); - - return _ticketDataFormat.Protect(ticket); - } - - /// - /// Un-protects the cookie - /// - /// - /// - public AuthenticationTicket Unprotect(string protectedText) - { - AuthenticationTicket decrypt; - try - { - decrypt = _ticketDataFormat.Unprotect(protectedText); - if (decrypt == null) return null; - } - catch (Exception) - { - return null; - } - - if (!UmbracoBackOfficeIdentity.FromClaimsIdentity(decrypt.Identity, out var identity)) - return null; - - //return the ticket with a UmbracoBackOfficeIdentity - var ticket = new AuthenticationTicket(identity, decrypt.Properties); - - return ticket; - } - } -} diff --git a/src/Umbraco.Web/Security/WebAuthExtensions.cs b/src/Umbraco.Web/Security/WebAuthExtensions.cs deleted file mode 100644 index f008dc8ba7..0000000000 --- a/src/Umbraco.Web/Security/WebAuthExtensions.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Net.Http; -using System.Security.Principal; -using System.ServiceModel.Channels; -using System.Threading; -using System.Web; -using Umbraco.Web.WebApi; - -namespace Umbraco.Web.Security -{ - internal static class WebAuthExtensions - { - /// - /// This will set a an authenticated IPrincipal to the current request for webforms & webapi - /// - /// - /// - /// - internal static IPrincipal SetPrincipalForRequest(this HttpRequestMessage request, IPrincipal principal) - { - //It is actually not good enough to set this on the current app Context and the thread, it also needs - // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually - // an underlying fault of asp.net not propagating the User correctly. - if (HttpContext.Current != null) - { - HttpContext.Current.User = principal; - } - var http = request.TryGetHttpContext(); - if (http) - { - http.Result.User = principal; - } - Thread.CurrentPrincipal = principal; - - //For WebAPI - request.SetUserPrincipal(principal); - - return principal; - } - - /// - /// This will set a an authenticated IPrincipal to the current request given the IUser object - /// - /// - /// - /// - internal static IPrincipal SetPrincipalForRequest(this HttpContextBase httpContext, IPrincipal principal) - { - //It is actually not good enough to set this on the current app Context and the thread, it also needs - // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually - // an underlying fault of asp.net not propagating the User correctly. - if (HttpContext.Current != null) - { - HttpContext.Current.User = principal; - } - httpContext.User = principal; - Thread.CurrentPrincipal = principal; - return principal; - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 65cba08ec9..9e7a9fe7af 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -126,7 +126,6 @@ Properties\SolutionInfo.cs - @@ -141,14 +140,10 @@ - - - - @@ -159,8 +154,6 @@ - - @@ -175,12 +168,9 @@ - - - @@ -190,8 +180,6 @@ - - @@ -200,26 +188,15 @@ - - - - - - - - - - - diff --git a/src/Umbraco.Web/UmbracoApplicationBase.cs b/src/Umbraco.Web/UmbracoApplicationBase.cs index c7066c664f..f10f4491d9 100644 --- a/src/Umbraco.Web/UmbracoApplicationBase.cs +++ b/src/Umbraco.Web/UmbracoApplicationBase.cs @@ -1,16 +1,15 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Threading; using System.Web; using System.Web.Hosting; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -using Serilog; using Serilog.Context; using Umbraco.Core; using Umbraco.Core.Cache; @@ -22,10 +21,8 @@ using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Logging.Serilog.Enrichers; -using Umbraco.Core.Runtime; using Umbraco.Net; using Umbraco.Web.Hosting; -using Umbraco.Web.Logging; using ConnectionStrings = Umbraco.Core.Configuration.Models.ConnectionStrings; using Current = Umbraco.Web.Composing.Current; using GlobalSettings = Umbraco.Core.Configuration.Models.GlobalSettings; diff --git a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs deleted file mode 100644 index 58fb36e13b..0000000000 --- a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Microsoft.Owin; -using Owin; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Hosting; -using Umbraco.Core.Mapping; -using Umbraco.Net; -using Umbraco.Core.Services; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web.Security; - - -[assembly: OwinStartup("UmbracoDefaultOwinStartup", typeof(UmbracoDefaultOwinStartup))] - -namespace Umbraco.Web -{ - /// - /// The default way to configure OWIN for Umbraco - /// - /// - /// The startup type is specified in appSettings under owin:appStartup - /// - public class UmbracoDefaultOwinStartup - { - protected IUmbracoContextAccessor UmbracoContextAccessor => Current.UmbracoContextAccessor; - protected GlobalSettings GlobalSettings => Current.Factory.GetRequiredService(); - protected SecuritySettings SecuritySettings => Current.Factory.GetRequiredService>().Value; - protected IUserPasswordConfiguration UserPasswordConfig => Current.Factory.GetRequiredService(); - protected IRuntimeState RuntimeState => Current.RuntimeState; - protected ServiceContext Services => Current.Services; - protected UmbracoMapper Mapper => Current.Mapper; - protected IIpResolver IpResolver => Current.IpResolver; - protected IHostingEnvironment HostingEnvironment => Current.HostingEnvironment; - protected IRequestCache RequestCache => Current.AppCaches.RequestCache; - - /// - /// Main startup method - /// - /// - public virtual void Configuration(IAppBuilder app) - { - app.SanitizeThreadCulture(); - - // there's nothing we can do really - if (RuntimeState.Level == RuntimeLevel.BootFailed) - return; - - ConfigureServices(app, Services); - ConfigureMiddleware(app); - } - - /// - /// Configures services to be created in the OWIN context (CreatePerOwinContext) - /// - /// - /// - protected virtual void ConfigureServices(IAppBuilder app, ServiceContext services) - { - app.SetUmbracoLoggerFactory(); - } - - /// - /// Configures middleware to be used (i.e. app.Use...) - /// - /// - protected virtual void ConfigureMiddleware(IAppBuilder app) - { - - // Configure OWIN for authentication. - ConfigureUmbracoAuthentication(app); - - app - .UseSignalR(GlobalSettings, HostingEnvironment) - .FinalizeMiddlewareConfiguration(); - } - - /// - /// Configure external/OAuth login providers - /// - /// - protected virtual void ConfigureUmbracoAuthentication(IAppBuilder app) - { - // Ensure owin is configured for Umbraco back office authentication. - // Front-end OWIN cookie configuration must be declared after this code. - app - .UseUmbracoBackOfficeExternalCookieAuthentication(UmbracoContextAccessor, RuntimeState, GlobalSettings, HostingEnvironment, RequestCache, PipelineStage.Authenticate); - // TODO: this would be considered a breaking change but this must come after all authentication so should be moved within ConfigureMiddleware - } - - public static event EventHandler MiddlewareConfigured; - - internal static void OnMiddlewareConfigured(OwinMiddlewareConfiguredEventArgs args) - { - MiddlewareConfigured?.Invoke(null, args); - } - } -} diff --git a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs deleted file mode 100644 index 4b16e650da..0000000000 --- a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http.Headers; -using System.Web.Helpers; -using Microsoft.Extensions.Logging; -using Umbraco.Core; -using Umbraco.Web.Composing; - -namespace Umbraco.Web.WebApi.Filters -{ - /// - /// A helper class to deal with csrf prevention with angularjs and webapi - /// - public static class AngularAntiForgeryHelper - { - /// - /// Returns 2 tokens - one for the cookie value and one that angular should set as the header value - /// - /// - /// - /// - /// .Net provides us a way to validate one token with another for added security. With the way angular works, this - /// means that we need to set 2 cookies since angular uses one cookie value to create the header value, then we want to validate - /// this header value against our original cookie value. - /// - public static void GetTokens(out string cookieToken, out string headerToken) - { - AntiForgery.GetTokens(null, out cookieToken, out headerToken); - } - - /// - /// Validates the header token against the validation cookie value - /// - /// - /// - /// - public static bool ValidateTokens(string cookieToken, string headerToken) - { - // ensure that the cookie matches the header and then ensure it matches the correct value! - try - { - AntiForgery.Validate(cookieToken, headerToken); - } - catch (Exception ex) - { - Current.Logger.LogError(ex, "Could not validate XSRF token"); - return false; - } - return true; - } - - internal static bool ValidateHeaders( - KeyValuePair>[] requestHeaders, - string cookieToken, - out string failedReason) - { - failedReason = ""; - - if (requestHeaders.Any(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername)) == false) - { - failedReason = "Missing token"; - return false; - } - - var headerToken = requestHeaders - .Where(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername)) - .Select(z => z.Value) - .SelectMany(z => z) - .FirstOrDefault(); - - // both header and cookie must be there - if (cookieToken == null || headerToken == null) - { - failedReason = "Missing token null"; - return false; - } - - if (ValidateTokens(cookieToken, headerToken) == false) - { - failedReason = "Invalid token"; - return false; - } - - return true; - } - - /// - /// Validates the headers/cookies passed in for the request - /// - /// - /// - /// - public static bool ValidateHeaders(HttpRequestHeaders requestHeaders, out string failedReason) - { - var cookieToken = requestHeaders.GetCookieValue(Constants.Web.CsrfValidationCookieName); - - return ValidateHeaders( - requestHeaders.ToDictionary(x => x.Key, x => x.Value).ToArray(), - cookieToken == null ? null : cookieToken, - out failedReason); - } - - } -} diff --git a/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs deleted file mode 100644 index f147a2a4cb..0000000000 --- a/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Security.Claims; -using System.Web.Http; -using System.Web.Http.Filters; - -namespace Umbraco.Web.WebApi.Filters -{ - /// - /// A filter to check for the csrf token based on Angular's standard approach - /// - /// - /// Code derived from http://ericpanorel.net/2013/07/28/spa-authentication-and-csrf-mvc4-antiforgery-implementation/ - /// - /// If the authentication type is cookie based, then this filter will execute, otherwise it will be disabled - /// - public sealed class ValidateAngularAntiForgeryTokenAttribute : ActionFilterAttribute - { - public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext) - { - var userIdentity = ((ApiController) actionContext.ControllerContext.Controller).User.Identity as ClaimsIdentity; - if (userIdentity != null) - { - //if there is not CookiePath claim, then exit - if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false) - { - base.OnActionExecuting(actionContext); - return; - } - } - - string failedReason; - if (AngularAntiForgeryHelper.ValidateHeaders(actionContext.Request.Headers, out failedReason) == false) - { - actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.ExpectationFailed); - actionContext.Response.ReasonPhrase = failedReason; - return; - } - - base.OnActionExecuting(actionContext); - } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComponent.cs b/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComponent.cs deleted file mode 100644 index 47dd0908dd..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComponent.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Specialized; -using System.IO; -using ClientDependency.Core.CompositeFiles.Providers; -using ClientDependency.Core.Config; -using Microsoft.Extensions.Options; -using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; -using Umbraco.Web.Runtime; - -namespace Umbraco.Web.WebAssets.CDF -{ - [ComposeAfter(typeof(WebInitialComponent))] - public sealed class ClientDependencyComponent : IComponent - { - private readonly HostingSettings _hostingSettings; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly RuntimeSettings _settings; - - public ClientDependencyComponent( - IOptions hostingSettings, - IHostingEnvironment hostingEnvironment, - IOptions settings) - { - _hostingSettings = hostingSettings.Value; - _hostingEnvironment = hostingEnvironment; - _settings = settings.Value; - } - - public void Initialize() - { - if (_hostingEnvironment.IsHosted) - { - ConfigureClientDependency(); - } - } - - private void ConfigureClientDependency() - { - // Backwards compatibility - set the path and URL type for ClientDependency 1.5.1 [LK] - XmlFileMapper.FileMapDefaultFolder = Core.Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ClientDependency"; - BaseCompositeFileProcessingProvider.UrlTypeDefault = CompositeUrlType.Base64QueryStrings; - - // Now we need to detect if we are running 'Umbraco.Core.LocalTempStorage' as EnvironmentTemp and in that case we want to change the CDF file - // location to be there - if (_hostingSettings.LocalTempStorageLocation == LocalTempStorage.EnvironmentTemp) - { - var cachePath = _hostingEnvironment.LocalTempPath; - - //set the file map and composite file default location to the %temp% location - BaseCompositeFileProcessingProvider.CompositeFilePathDefaultFolder - = XmlFileMapper.FileMapDefaultFolder - = Path.Combine(cachePath, "ClientDependency"); - } - - if (_settings.MaxQueryStringLength.HasValue || _settings.MaxRequestLength.HasValue) - { - //set the max url length for CDF to be the smallest of the max query length, max request length - ClientDependency.Core.CompositeFiles.CompositeDependencyHandler.MaxHandlerUrlLength = Math.Min(_settings.MaxQueryStringLength.GetValueOrDefault(), _settings.MaxRequestLength.GetValueOrDefault()); - } - - //Register a custom renderer - used to process property editor dependencies - var renderer = new DependencyPathRenderer(); - renderer.Initialize("Umbraco.DependencyPathRenderer", new NameValueCollection - { - { "compositeFileHandlerPath", ClientDependencySettings.Instance.CompositeFileHandlerPath } - }); - - ClientDependencySettings.Instance.MvcRendererCollection.Add(renderer); - } - public void Terminate() - { } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComposer.cs b/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComposer.cs deleted file mode 100644 index 75e0d6123f..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyComposer.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Composing; -using Umbraco.Core.WebAssets; - -namespace Umbraco.Web.WebAssets.CDF -{ - public sealed class ClientDependencyComposer : ComponentComposer - { - public override void Compose(IUmbracoBuilder builder) - { - base.Compose(builder); - builder.Services.AddUnique(); - } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyConfiguration.cs b/src/Umbraco.Web/WebAssets/CDF/ClientDependencyConfiguration.cs deleted file mode 100644 index 81dd05c25a..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyConfiguration.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Web; -using System.Xml.Linq; -using Microsoft.Extensions.Logging; -using ClientDependency.Core.CompositeFiles.Providers; -using ClientDependency.Core.Config; -using Semver; -using Umbraco.Core.Hosting; - -namespace Umbraco.Web.WebAssets.CDF -{ - /// - /// A utility class for working with CDF config and cache files - use sparingly! - /// - public class ClientDependencyConfiguration - { - private readonly ILogger _logger; - private readonly string _fileName; - - private string FileMapDefaultFolder - { - get => XmlFileMapper.FileMapDefaultFolder; - set => XmlFileMapper.FileMapDefaultFolder = value; - } - - public ClientDependencyConfiguration(ILogger logger, IHostingEnvironment hostingEnvironment) - { - if (logger == null) throw new ArgumentNullException("logger"); - _logger = logger; - _fileName = hostingEnvironment.MapPathContentRoot(string.Format("{0}/ClientDependency.config", Core.Constants.SystemDirectories.Config)); - } - - /// - /// Changes the version number in ClientDependency.config to a hashed value for the version and the DateTime.Day - /// - /// The version of Umbraco we're upgrading to - /// A date value to use in the hash to prevent this method from updating the version on each startup - /// Allows the developer to specify the date precision for the hash (i.e. "yyyyMMdd" would be a precision for the day) - /// Boolean to indicate successful update of the ClientDependency.config file - public bool UpdateVersionNumber(SemVersion version, DateTime date, string dateFormat) - { - var byteContents = Encoding.Unicode.GetBytes(version + date.ToString(dateFormat)); - - //This is a way to convert a string to a long - //see https://www.codeproject.com/Articles/34309/Convert-String-to-bit-Integer - //We could much more easily use MD5 which would create us an INT but since that is not compliant with - //hashing standards we have to use SHA - int intHash; - using (var hash = SHA256.Create()) - { - var bytes = hash.ComputeHash(byteContents); - - var longResult = new[] { 0, 8, 16, 24 } - .Select(i => BitConverter.ToInt64(bytes, i)) - .Aggregate((x, y) => x ^ y); - - //CDF requires an INT, and although this isn't fail safe, it will work for our purposes. We are not hashing for crypto purposes - //so there could be some collisions with this conversion but it's not a problem for our purposes - //It's also important to note that the long.GetHashCode() implementation in .NET is this: return (int) this ^ (int) (this >> 32); - //which means that this value will not change per AppDomain like some GetHashCode implementations. - intHash = longResult.GetHashCode(); - } - - try - { - var clientDependencyConfigXml = XDocument.Load(_fileName, LoadOptions.PreserveWhitespace); - if (clientDependencyConfigXml.Root != null) - { - - var versionAttribute = clientDependencyConfigXml.Root.Attribute("version"); - - //Set the new version to the hashcode of now - var oldVersion = versionAttribute.Value; - var newVersion = Math.Abs(intHash).ToString(); - - //don't update if it's the same version - if (oldVersion == newVersion) - return false; - - versionAttribute.SetValue(newVersion); - clientDependencyConfigXml.Save(_fileName, SaveOptions.DisableFormatting); - - _logger.LogInformation("Updated version number from {OldVersion} to {NewVersion}", oldVersion, newVersion); - return true; - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Couldn't update ClientDependency version number"); - } - - return false; - } - - /// - /// Clears the temporary files stored for the ClientDependency folder - /// - /// - public bool ClearTempFiles(HttpContextBase currentHttpContext) - { - var cdfTempDirectories = new HashSet(); - foreach (BaseCompositeFileProcessingProvider provider in ClientDependencySettings.Instance - .CompositeFileProcessingProviderCollection) - { - var path = provider.CompositeFilePath.FullName; - cdfTempDirectories.Add(path); - } - - try - { - var fullPath = FileMapDefaultFolder.StartsWith("~/") - ? currentHttpContext.Server.MapPath(FileMapDefaultFolder) - : FileMapDefaultFolder; - if (fullPath != null) - { - cdfTempDirectories.Add(fullPath); - } - } - catch (Exception ex) - { - //invalid path format or something... try/catch to be safe - _logger.LogError(ex, "Could not get path from ClientDependency.config"); - } - - var success = true; - foreach (var directory in cdfTempDirectories) - { - try - { - if (!Directory.Exists(directory)) - continue; - - Directory.Delete(directory, true); - } - catch (Exception ex) - { - // Something could be locking the directory or the was another error, making sure we don't break the upgrade installer - _logger.LogError(ex, "Could not clear temp files"); - success = false; - } - } - - return success; - } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyRuntimeMinifier.cs b/src/Umbraco.Web/WebAssets/CDF/ClientDependencyRuntimeMinifier.cs deleted file mode 100644 index c72887b4d2..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/ClientDependencyRuntimeMinifier.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using ClientDependency.Core; -using ClientDependency.Core.CompositeFiles; -using ClientDependency.Core.Config; -using Umbraco.Core.Configuration; -using Umbraco.Core.WebAssets; -using CssFile = ClientDependency.Core.CssFile; -using JavascriptFile = ClientDependency.Core.JavascriptFile; -using Umbraco.Core.Hosting; - -namespace Umbraco.Web.WebAssets.CDF -{ - public class ClientDependencyRuntimeMinifier : IRuntimeMinifier - { - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly ILoggerFactory _loggerFactory; - private readonly ILogger _logger; - private readonly IUmbracoVersion _umbracoVersion; - - public string CacheBuster => ClientDependencySettings.Instance.Version.ToString(); - - public ClientDependencyRuntimeMinifier( - IHttpContextAccessor httpContextAccessor, - IHostingEnvironment hostingEnvironment, - ILoggerFactory loggerFactory, - IUmbracoVersion umbracoVersion) - { - _httpContextAccessor = httpContextAccessor; - _hostingEnvironment = hostingEnvironment; - _loggerFactory = loggerFactory; - _logger = _loggerFactory.CreateLogger(); - _umbracoVersion = umbracoVersion; - } - - public void CreateCssBundle(string bundleName, params string[] filePaths) - { - if (filePaths.Any(f => !f.StartsWith("/") && !f.StartsWith("~/"))) - throw new InvalidOperationException("All file paths must be absolute"); - - BundleManager.CreateCssBundle( - bundleName, - filePaths.Select(x => new CssFile(x)).ToArray()); - } - - public Task RenderCssHereAsync(string bundleName) - { - var bundleFiles = GetCssBundleFiles(bundleName); - if (bundleFiles == null) return Task.FromResult(string.Empty); - return Task.FromResult(RenderOutput(bundleFiles, AssetType.Css)); - } - - public void CreateJsBundle(string bundleName, params string[] filePaths) - { - if (filePaths.Any(f => !f.StartsWith("/") && !f.StartsWith("~/"))) - throw new InvalidOperationException("All file paths must be absolute"); - - BundleManager.CreateJsBundle( - bundleName, - filePaths.Select(x => new JavascriptFile(x)).ToArray()); - } - - public Task RenderJsHereAsync(string bundleName) - { - var bundleFiles = GetJsBundleFiles(bundleName); - if (bundleFiles == null) return Task.FromResult(string.Empty); - return Task.FromResult(RenderOutput(bundleFiles, AssetType.Javascript)); - } - - public Task> GetAssetPathsAsync(string bundleName) - { - var bundleFiles = GetJsBundleFiles(bundleName)?.ToList() ?? GetCssBundleFiles(bundleName)?.ToList(); - if (bundleFiles == null || bundleFiles.Count == 0) return Task.FromResult(Enumerable.Empty()); - - var assetType = bundleFiles[0].DependencyType == ClientDependencyType.Css ? AssetType.Css : AssetType.Javascript; - - // This is a hack on CDF so that we can resolve CDF urls directly since that isn't directly supported by the lib - var renderer = ClientDependencySettings.Instance.MvcRendererCollection["Umbraco.DependencyPathRenderer"]; - renderer.RegisterDependencies(bundleFiles, new HashSet(), out var scripts, out var stylesheets, _httpContextAccessor.HttpContext); - - var toParse = assetType == AssetType.Javascript ? scripts : stylesheets; - return Task.FromResult>(toParse.Split(new[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries)); - } - - public Task MinifyAsync(string fileContent, AssetType assetType) - { - TextReader reader = new StringReader(fileContent); - - if (assetType == AssetType.Javascript) - { - var jsMinifier = new JSMin(); - return Task.FromResult(jsMinifier.Minify(reader)); - } - - // asset type is Css - var cssMinifier = new CssMinifier(); - return Task.FromResult(cssMinifier.Minify(reader)); - } - - public void Reset() - { - // Update ClientDependency version - var clientDependencyConfig = new ClientDependencyConfiguration(_loggerFactory.CreateLogger(), _hostingEnvironment); - var clientDependencyUpdated = clientDependencyConfig.UpdateVersionNumber( - _umbracoVersion.SemanticVersion, DateTime.UtcNow, "yyyyMMdd"); - // Delete ClientDependency temp directories to make sure we get fresh caches - var clientDependencyTempFilesDeleted = clientDependencyConfig.ClearTempFiles(_httpContextAccessor.HttpContext); - } - - private string RenderOutput(IEnumerable bundleFiles, AssetType assetType) - { - var renderer = ClientDependencySettings.Instance.DefaultMvcRenderer; - - renderer.RegisterDependencies( - bundleFiles.ToList(), - new HashSet(), - out var jsOutput, out var cssOutput, _httpContextAccessor.GetRequiredHttpContext()); - - return assetType == AssetType.Css ? cssOutput : jsOutput; - } - - private IEnumerable GetCssBundleFiles(string bundleName) - { - // internal methods needs reflection - var bundle = typeof(BundleManager) - .GetMethod("GetCssBundle", BindingFlags.NonPublic | BindingFlags.Static) - .Invoke(null, new object[] { bundleName }); - var bundleFiles = (IEnumerable) bundle?.GetType().GetProperty("Files").GetValue(bundle, null); - return bundleFiles; - } - - private IEnumerable GetJsBundleFiles(string bundleName) - { - // internal methods needs reflection - var bundle = typeof(BundleManager) - .GetMethod("GetJsBundle", BindingFlags.NonPublic | BindingFlags.Static) - .Invoke(null, new object[] { bundleName }); - var bundleFiles = (IEnumerable)bundle?.GetType().GetProperty("Files").GetValue(bundle, null); - return bundleFiles; - } - - private ClientDependencyType MapDependencyTypeValue(AssetType type) - { - return type switch - { - AssetType.Javascript => ClientDependencyType.Javascript, - AssetType.Css => ClientDependencyType.Css, - _ => (ClientDependencyType) Enum.Parse(typeof(ClientDependencyType), type.ToString(), true) - }; - } - - private IClientDependencyFile MapAssetFile(IAssetFile assetFile) - { - var assetFileType = (AssetFile)assetFile; - var basicFile = new BasicFile(MapDependencyTypeValue(assetFileType.DependencyType)) - { - FilePath = assetFile.FilePath - }; - - return basicFile; - } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/DependencyPathRenderer.cs b/src/Umbraco.Web/WebAssets/CDF/DependencyPathRenderer.cs deleted file mode 100644 index ef73dbecb1..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/DependencyPathRenderer.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Collections.Generic; -using System.Web; -using ClientDependency.Core; -using ClientDependency.Core.FileRegistration.Providers; - -namespace Umbraco.Web.WebAssets.CDF -{ - /// - /// A custom renderer that only outputs a dependency path instead of script tags - for use with the js loader with yepnope - /// - public class DependencyPathRenderer : StandardRenderer - { - public override string Name - { - get { return "Umbraco.DependencyPathRenderer"; } - } - - /// - /// Used to delimit each dependency so we can split later - /// - public const string Delimiter = "||||"; - - /// - /// Override because the StandardRenderer replaces & with & but we don't want that so we'll reverse it - /// - /// - /// - /// - /// - /// - public override void RegisterDependencies(List allDependencies, HashSet paths, out string jsOutput, out string cssOutput, HttpContextBase http) - { - base.RegisterDependencies(allDependencies, paths, out jsOutput, out cssOutput, http); - - jsOutput = jsOutput.Replace("&", "&"); - cssOutput = cssOutput.Replace("&", "&"); - } - - protected override string RenderSingleJsFile(string js, IDictionary htmlAttributes) - { - return js + Delimiter; - } - - protected override string RenderSingleCssFile(string css, IDictionary htmlAttributes) - { - return css + Delimiter; - } - } -} diff --git a/src/Umbraco.Web/WebAssets/CDF/UmbracoClientDependencyLoader.cs b/src/Umbraco.Web/WebAssets/CDF/UmbracoClientDependencyLoader.cs deleted file mode 100644 index a0704140f1..0000000000 --- a/src/Umbraco.Web/WebAssets/CDF/UmbracoClientDependencyLoader.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Web.UI; -using ClientDependency.Core.Controls; -using ClientDependency.Core.FileRegistration.Providers; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.IO; - -namespace Umbraco.Web.WebAssets.CDF -{ - /// - /// Used to load in all client dependencies for Umbraco. - /// Ensures that both UmbracoClient and UmbracoRoot paths are added to the loader. - /// - public class UmbracoClientDependencyLoader : ClientDependencyLoader - { - /// - /// Set the defaults - /// - public UmbracoClientDependencyLoader(GlobalSettings globalSettings, IIOHelper ioHelper) - : base() - { - this.AddPath("UmbracoRoot", ioHelper.ResolveUrl(globalSettings.UmbracoPath)); - this.ProviderName = LoaderControlProvider.DefaultName; - - } - - public static ClientDependencyLoader TryCreate(Control parent, out bool isNew, GlobalSettings globalSettings, IIOHelper ioHelper) - { - if (ClientDependencyLoader.Instance == null) - { - var loader = new UmbracoClientDependencyLoader(globalSettings, ioHelper); - parent.Controls.Add(loader); - isNew = true; - return loader; - } - else - { - isNew = false; - return ClientDependencyLoader.Instance; - } - - } - - } -} From 0d3aa828c1dc8375f6331169d5f683d241521054 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 7 Dec 2020 18:55:01 +1100 Subject: [PATCH 60/63] remove more old code --- .../OwinDataProtectorTokenProviderTests.cs | 247 ------------------ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - .../OwinDataProtectorTokenProvider.cs | 112 -------- src/Umbraco.Web/Umbraco.Web.csproj | 1 - .../WebApi/HttpRequestMessageExtensions.cs | 107 +------- .../WebApi/UmbracoApiControllerBase.cs | 12 +- 6 files changed, 2 insertions(+), 478 deletions(-) delete mode 100644 src/Umbraco.Tests/Security/OwinDataProtectorTokenProviderTests.cs delete mode 100644 src/Umbraco.Web/Security/OwinDataProtectorTokenProvider.cs diff --git a/src/Umbraco.Tests/Security/OwinDataProtectorTokenProviderTests.cs b/src/Umbraco.Tests/Security/OwinDataProtectorTokenProviderTests.cs deleted file mode 100644 index c44844fd66..0000000000 --- a/src/Umbraco.Tests/Security/OwinDataProtectorTokenProviderTests.cs +++ /dev/null @@ -1,247 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Microsoft.Owin.Security.DataProtection; -using Moq; -using NUnit.Framework; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Security; -using Umbraco.Tests.Common.Builders; -using Umbraco.Web.Security; - -namespace Umbraco.Tests.Security -{ - public class OwinDataProtectorTokenProviderTests - { - private Mock _mockDataProtector; - private Mock> _mockUserManager; - private BackOfficeIdentityUser _testUser; - private const string _testPurpose = "test"; - - [Test] - public void Ctor_When_Protector_Is_Null_Expect_ArgumentNullException() - { - Assert.Throws(() => new OwinDataProtectorTokenProvider(null)); - } - - [Test] - public async Task CanGenerateTwoFactorTokenAsync_Expect_False() - { - var sut = CreateSut(); - - var canGenerate = await sut.CanGenerateTwoFactorTokenAsync(_mockUserManager.Object, _testUser); - - Assert.False(canGenerate); - } - - [Test] - public void GenerateAsync_When_UserManager_Is_Null_Expect_ArgumentNullException() - { - var sut = CreateSut(); - Assert.ThrowsAsync(async () => await sut.GenerateAsync(null, null, _testUser)); - } - - [Test] - public void GenerateAsync_When_User_Is_Null_Expect_ArgumentNullException() - { - var sut = CreateSut(); - Assert.ThrowsAsync(async () => await sut.GenerateAsync(null, _mockUserManager.Object, null)); - } - - [Test] - public async Task GenerateAsync_When_Token_Generated_Expect_Ticks_And_User_ID() - { - var sut = CreateSut(); - - var token = await sut.GenerateAsync(null, _mockUserManager.Object, _testUser); - - using (var reader = new BinaryReader(new MemoryStream(Convert.FromBase64String(token)))) - { - var creationTime = new DateTimeOffset(reader.ReadInt64(), TimeSpan.Zero); - var foundUserId = reader.ReadString(); - - Assert.That(creationTime.DateTime, Is.EqualTo(DateTime.UtcNow).Within(1).Minutes); - Assert.AreEqual(_testUser.Id.ToString(), foundUserId); - } - } - - [Test] - public async Task GenerateAsync_When_Token_Generated_With_Purpose_Expect_Purpose_In_Token() - { - var expectedPurpose = Guid.NewGuid().ToString(); - - var sut = CreateSut(); - - var token = await sut.GenerateAsync(expectedPurpose, _mockUserManager.Object, _testUser); - - using (var reader = new BinaryReader(new MemoryStream(Convert.FromBase64String(token)))) - { - reader.ReadInt64(); // creation time - reader.ReadString(); // user ID - var purpose = reader.ReadString(); - - Assert.AreEqual(expectedPurpose, purpose); - } - } - - [Test] - public async Task GenerateAsync_When_Token_Generated_And_SecurityStamp_Supported_Expect_SecurityStamp_In_Token() - { - var expectedSecurityStamp = Guid.NewGuid().ToString(); - - var sut = CreateSut(); - _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true); - _mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(expectedSecurityStamp); - - var token = await sut.GenerateAsync(null, _mockUserManager.Object, _testUser); - - using (var reader = new BinaryReader(new MemoryStream(Convert.FromBase64String(token)))) - { - reader.ReadInt64(); // creation time - reader.ReadString(); // user ID - reader.ReadString(); // purpose - var securityStamp = reader.ReadString(); - - Assert.AreEqual(expectedSecurityStamp, securityStamp); - } - } - - [Test] - [TestCase(null)] - [TestCase("")] - [TestCase(" ")] - public void ValidateAsync_When_Token_Is_Null_Or_Whitespace_Expect_ArgumentNullException(string token) - { - var sut = CreateSut(); - - Assert.ThrowsAsync(() => sut.ValidateAsync(null, token, _mockUserManager.Object, _testUser)); - } - - [Test] - public void ValidateAsync_When_UserManager_Is_Null_Expect_ArgumentNullException() - { - var sut = CreateSut(); - - Assert.ThrowsAsync(() => sut.ValidateAsync(null, Guid.NewGuid().ToString(), null, _testUser)); - } - - [Test] - public void ValidateAsync_When_User_Is_Null_Expect_ArgumentNullException() - { - var sut = CreateSut(); - - Assert.ThrowsAsync(() => sut.ValidateAsync(null, Guid.NewGuid().ToString(), _mockUserManager.Object, null)); - } - - [Test] - public async Task ValidateAsync_When_Token_Has_Expired_Expect_False() - { - var sut = CreateSut(); - var testToken = CreateTestToken(creationDate: DateTime.UtcNow.AddYears(-10)); - - var isValid = await sut.ValidateAsync(null, testToken, _mockUserManager.Object, _testUser); - - Assert.False(isValid); - } - - [Test] - public async Task ValidateAsync_When_Token_Was_Issued_To_Wrong_User_Expect_False() - { - var sut = CreateSut(); - var testToken = CreateTestToken(userId: Guid.NewGuid().ToString()); - - var isValid = await sut.ValidateAsync(_testPurpose, testToken, _mockUserManager.Object, _testUser); - - Assert.False(isValid); - } - - [Test] - public async Task ValidateAsync_When_Token_Was_Has_Wrong_Purpose_Expect_False() - { - var sut = CreateSut(); - var testToken = CreateTestToken(purpose: "invalid"); - - var isValid = await sut.ValidateAsync("valid", testToken, _mockUserManager.Object, _testUser); - - Assert.False(isValid); - } - - [Test] - public async Task ValidateAsync_When_Token_Was_Has_Wrong_SecurityStamp_Expect_False() - { - var sut = CreateSut(); - _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true); - _mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(Guid.NewGuid().ToString); - - var testToken = CreateTestToken(securityStamp: "invalid"); - - var isValid = await sut.ValidateAsync(_testPurpose, testToken, _mockUserManager.Object, _testUser); - - Assert.False(isValid); - } - - [Test] - public async Task ValidateAsync_When_Valid_Token_Expect_True() - { - const string validPurpose = "test"; - var validSecurityStamp = Guid.NewGuid().ToString(); - - var sut = CreateSut(); - _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true); - _mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(validSecurityStamp); - - var testToken = CreateTestToken( - creationDate: DateTime.UtcNow, - userId: _testUser.Id.ToString(), - purpose: validPurpose, - securityStamp: validSecurityStamp); - - var isValid = await sut.ValidateAsync(validPurpose, testToken, _mockUserManager.Object, _testUser); - - Assert.True(isValid); - } - - private OwinDataProtectorTokenProvider CreateSut() - => new OwinDataProtectorTokenProvider(_mockDataProtector.Object); - - private string CreateTestToken(DateTime? creationDate = null, string userId = null, string purpose = null, string securityStamp = null) - { - var ms = new MemoryStream(); - using (var writer = new BinaryWriter(ms)) - { - writer.Write(creationDate?.Ticks ?? DateTimeOffset.UtcNow.UtcTicks); - writer.Write(userId ?? _testUser.Id.ToString()); - writer.Write(purpose ?? _testPurpose); - writer.Write(securityStamp ?? ""); - } - - return Convert.ToBase64String(ms.ToArray()); - } - - [SetUp] - public void Setup() - { - _mockDataProtector = new Mock(); - _mockDataProtector.Setup(x => x.Protect(It.IsAny())).Returns((byte[] originalBytes) => originalBytes); - _mockDataProtector.Setup(x => x.Unprotect(It.IsAny())).Returns((byte[] originalBytes) => originalBytes); - - var globalSettings = new GlobalSettings(); - - _mockUserManager = new Mock>(new Mock>().Object, - null, null, null, null, null, null, null, null); - _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(false); - - _testUser = new BackOfficeIdentityUser(globalSettings, 2, new List()) - { - UserName = "alice", - Name = "Alice", - Email = "alice@umbraco.test", - SecurityStamp = Guid.NewGuid().ToString() - }; - } - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 44e81d604a..74eed99143 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -143,7 +143,6 @@ - diff --git a/src/Umbraco.Web/Security/OwinDataProtectorTokenProvider.cs b/src/Umbraco.Web/Security/OwinDataProtectorTokenProvider.cs deleted file mode 100644 index 429014dea8..0000000000 --- a/src/Umbraco.Web/Security/OwinDataProtectorTokenProvider.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Microsoft.Owin.Security.DataProtection; -using Umbraco.Core.Security; - -namespace Umbraco.Web.Security -{ - /// - /// Adapted from Microsoft.AspNet.Identity.Owin.DataProtectorTokenProvider - /// - public class OwinDataProtectorTokenProvider : IUserTwoFactorTokenProvider where TUser : BackOfficeIdentityUser - { - public TimeSpan TokenLifespan { get; set; } - private static readonly Encoding _defaultEncoding = new UTF8Encoding(false, true); - private readonly IDataProtector _protector; - - public OwinDataProtectorTokenProvider(IDataProtector protector) - { - _protector = protector ?? throw new ArgumentNullException(nameof(protector)); - TokenLifespan = TimeSpan.FromDays(1); - } - - public async Task GenerateAsync(string purpose, UserManager manager, TUser user) - { - if (manager == null) throw new ArgumentNullException(nameof(manager)); - if (user == null) throw new ArgumentNullException(nameof(user)); - - var ms = new MemoryStream(); - using (var writer = new BinaryWriter(ms, _defaultEncoding, true)) - { - writer.Write(DateTimeOffset.UtcNow.UtcTicks); - writer.Write(Convert.ToString(user.Id, CultureInfo.InvariantCulture)); - writer.Write(purpose ?? ""); - - string stamp = null; - if (manager.SupportsUserSecurityStamp) - { - stamp = await manager.GetSecurityStampAsync(user); - } - writer.Write(stamp ?? ""); - } - - var protectedBytes = _protector.Protect(ms.ToArray()); - return Convert.ToBase64String(protectedBytes); - } - - public async Task ValidateAsync(string purpose, string token, UserManager manager, TUser user) - { - if (string.IsNullOrWhiteSpace(token)) throw new ArgumentNullException(nameof(token)); - if (manager == null) throw new ArgumentNullException(nameof(manager)); - if (user == null) throw new ArgumentNullException(nameof(user)); - - try - { - var unprotectedData = _protector.Unprotect(Convert.FromBase64String(token)); - var ms = new MemoryStream(unprotectedData); - using (var reader = new BinaryReader(ms, _defaultEncoding, true)) - { - var creationTime = new DateTimeOffset(reader.ReadInt64(), TimeSpan.Zero); - var expirationTime = creationTime + TokenLifespan; - if (expirationTime < DateTimeOffset.UtcNow) - { - return false; - } - - var userId = reader.ReadString(); - if (!string.Equals(userId, Convert.ToString(user.Id, CultureInfo.InvariantCulture))) - { - return false; - } - - var purp = reader.ReadString(); - if (!string.Equals(purp, purpose)) - { - return false; - } - - var stamp = reader.ReadString(); - if (reader.PeekChar() != -1) - { - return false; - } - - if (manager.SupportsUserSecurityStamp) - { - var expectedStamp = await manager.GetSecurityStampAsync(user); - return stamp == expectedStamp; - } - - return stamp == ""; - } - } - // ReSharper disable once EmptyGeneralCatchClause - catch - { - // Do not leak exception - } - - return false; - } - - public Task CanGenerateTwoFactorTokenAsync(UserManager manager, TUser user) - { - // This token provider is designed for flows such as password reset and account confirmation - return Task.FromResult(false); - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 9e7a9fe7af..bda42e9db9 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -156,7 +156,6 @@ - diff --git a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs index 7bda897a5e..8fa387ec27 100644 --- a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs +++ b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs @@ -1,13 +1,9 @@ -using System; -using System.Globalization; -using System.Linq; +using System; using System.Net; using System.Net.Http; using System.Web; -using System.Web.Http.ModelBinding; using Microsoft.Owin; using Umbraco.Core; -using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.WebApi { @@ -63,108 +59,7 @@ namespace Umbraco.Web.WebApi return Attempt.Fail(); } - /// - /// Create a 403 (Forbidden) response indicating that the current user doesn't have access to the resource - /// requested or the action it needs to take. - /// - /// - /// - /// - /// This is different from a 401 which indicates that the user is not logged in. - /// - public static HttpResponseMessage CreateUserNoAccessResponse(this HttpRequestMessage request) - { - return request.CreateResponse(HttpStatusCode.Forbidden); - } - /// - /// Create a 400 response message indicating that a validation error occurred - /// - /// - /// - /// - /// - public static HttpResponseMessage CreateValidationErrorResponse(this HttpRequestMessage request, T value) - { - var msg = request.CreateResponse(HttpStatusCode.BadRequest, value); - msg.Headers.Add("X-Status-Reason", "Validation failed"); - return msg; - } - - /// - /// Create a 400 response message indicating that a validation error occurred - /// - /// - /// - public static HttpResponseMessage CreateValidationErrorResponse(this HttpRequestMessage request) - { - var msg = request.CreateResponse(HttpStatusCode.BadRequest); - msg.Headers.Add("X-Status-Reason", "Validation failed"); - return msg; - } - - /// - /// Create a 400 response message indicating that a validation error occurred - /// - /// - /// - /// - public static HttpResponseMessage CreateValidationErrorResponse(this HttpRequestMessage request, string errorMessage) - { - var msg = request.CreateErrorResponse(HttpStatusCode.BadRequest, errorMessage); - msg.Headers.Add("X-Status-Reason", "Validation failed"); - return msg; - } - - /// - /// Creates an error response with notifications in the result to be displayed in the UI - /// - /// - /// - /// - public static HttpResponseMessage CreateNotificationValidationErrorResponse(this HttpRequestMessage request, string errorMessage) - { - var notificationModel = new SimpleNotificationModel - { - Message = errorMessage - }; - notificationModel.AddErrorNotification(errorMessage, string.Empty); - return request.CreateValidationErrorResponse(notificationModel); - } - - /// - /// Creates a successful response with notifications in the result to be displayed in the UI - /// - /// - /// - /// - public static HttpResponseMessage CreateNotificationSuccessResponse(this HttpRequestMessage request, string successMessage) - { - var notificationModel = new SimpleNotificationModel - { - Message = successMessage - }; - notificationModel.AddSuccessNotification(successMessage, string.Empty); - return request.CreateResponse(HttpStatusCode.OK, notificationModel); - } - - /// - /// Create a 400 response message indicating that a validation error occurred - /// - /// - /// - /// - public static HttpResponseMessage CreateValidationErrorResponse(this HttpRequestMessage request, ModelStateDictionary modelState) - { - var msg = request.CreateErrorResponse(HttpStatusCode.BadRequest, modelState); - msg.Headers.Add("X-Status-Reason", "Validation failed"); - return msg; - } - - internal static string ClientCulture(this HttpRequestMessage request) - { - return request.Headers.TryGetValues("X-UMB-CULTURE", out var values) ? values.FirstOrDefault() : null; - } } } diff --git a/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs b/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs index e5e123db88..035cee052e 100644 --- a/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs +++ b/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web; using System.Web.Http; using Microsoft.Extensions.DependencyInjection; @@ -124,16 +124,6 @@ namespace Umbraco.Web.WebApi /// public IBackOfficeSecurity Security => BackOfficeSecurityAccessor.BackOfficeSecurity; - /// - /// Tries to get the current HttpContext. - /// - protected Attempt TryGetHttpContext() - => Request.TryGetHttpContext(); - /// - /// Tries to get the current OWIN context. - /// - protected Attempt TryGetOwinContext() - => Request.TryGetOwinContext(); } } From dcb00b729d5556f1d991b51b1455cf82443d6bf4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 7 Dec 2020 18:58:07 +1100 Subject: [PATCH 61/63] remove more old code --- src/Umbraco.Web/HttpUrlHelperExtensions.cs | 154 --------------------- src/Umbraco.Web/ImageProcessorLogger.cs | 48 ------- src/Umbraco.Web/Umbraco.Web.csproj | 2 - 3 files changed, 204 deletions(-) delete mode 100644 src/Umbraco.Web/HttpUrlHelperExtensions.cs delete mode 100644 src/Umbraco.Web/ImageProcessorLogger.cs diff --git a/src/Umbraco.Web/HttpUrlHelperExtensions.cs b/src/Umbraco.Web/HttpUrlHelperExtensions.cs deleted file mode 100644 index 4e5533d327..0000000000 --- a/src/Umbraco.Web/HttpUrlHelperExtensions.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Web.Http.Routing; -using Umbraco.Core; -using Umbraco.Web.Composing; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; - -namespace Umbraco.Web -{ - public static class HttpUrlHelperExtensions - { - /// - /// Return the Url for a Web Api service - /// - /// - /// - /// - /// - /// - public static string GetUmbracoApiService(this UrlHelper url, string actionName, object id = null) - where T : UmbracoApiController - { - return url.GetUmbracoApiService(actionName, typeof(T), id); - } - - public static string GetUmbracoApiService(this UrlHelper url, Expression> methodSelector) - where T : UmbracoApiController - { - var method = ExpressionHelper.GetMethodInfo(methodSelector); - var methodParams = ExpressionHelper.GetMethodParams(methodSelector); - if (method == null) - { - throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) + " or the result "); - } - - if (methodParams.Any() == false) - { - return url.GetUmbracoApiService(method.Name); - } - return url.GetUmbracoApiService(method.Name, methodParams.Values.First()); - } - - /// - /// Return the Url for a Web Api service - /// - /// - /// - /// - /// - /// - public static string GetUmbracoApiService(this UrlHelper url, string actionName, Type apiControllerType, object id = null) - { - if (actionName == null) throw new ArgumentNullException(nameof(actionName)); - if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName)); - if (apiControllerType == null) throw new ArgumentNullException(nameof(apiControllerType)); - - var area = ""; - - var apiController = Current.UmbracoApiControllerTypes - .SingleOrDefault(x => x == apiControllerType); - if (apiController == null) - throw new InvalidOperationException("Could not find the umbraco api controller of type " + apiControllerType.FullName); - var metaData = PluginController.GetMetadata(apiController); - if (metaData.AreaName.IsNullOrWhiteSpace() == false) - { - //set the area to the plugin area - area = metaData.AreaName; - } - return url.GetUmbracoApiService(actionName, ControllerExtensions.GetControllerName(apiControllerType), area, id); - } - - /// - /// Return the Url for a Web Api service - /// - /// - /// - /// - /// - /// - public static string GetUmbracoApiService(this UrlHelper url, string actionName, string controllerName, object id = null) - { - return url.GetUmbracoApiService(actionName, controllerName, "", id); - } - - /// - /// Return the Url for a Web Api service - /// - /// - /// - /// - /// - /// - /// - public static string GetUmbracoApiService(this UrlHelper url, string actionName, string controllerName, string area, object id = null) - { - if (actionName == null) throw new ArgumentNullException(nameof(actionName)); - if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName)); - if (controllerName == null) throw new ArgumentNullException(nameof(controllerName)); - if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(controllerName)); - - string routeName; - if (area.IsNullOrWhiteSpace()) - { - routeName = string.Format("umbraco-{0}-{1}", "api", controllerName); - if (id == null) - { - return url.Route(routeName, new { controller = controllerName, action = actionName, httproute = "" }); - } - else - { - return url.Route(routeName, new { controller = controllerName, action = actionName, id = id, httproute = "" }); - } - } - else - { - routeName = string.Format("umbraco-{0}-{1}-{2}", "api", area, controllerName); - if (id == null) - { - return url.Route(routeName, new { controller = controllerName, action = actionName, httproute = "" }); - } - else - { - return url.Route(routeName, new { controller = controllerName, action = actionName, id = id, httproute = "" }); - } - } - } - - /// - /// Return the Base Url (not including the action) for a Web Api service - /// - /// - /// - /// - /// - public static string GetUmbracoApiServiceBaseUrl(this UrlHelper url, string actionName) - where T : UmbracoApiController - { - return url.GetUmbracoApiService(actionName).TrimEnd(actionName); - } - - public static string GetUmbracoApiServiceBaseUrl(this UrlHelper url, Expression> methodSelector) - where T : UmbracoApiController - { - var method = ExpressionHelper.GetMethodInfo(methodSelector); - if (method == null) - { - throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) + " or the result "); - } - return url.GetUmbracoApiService(method.Name).TrimEnd(method.Name); - } - } -} diff --git a/src/Umbraco.Web/ImageProcessorLogger.cs b/src/Umbraco.Web/ImageProcessorLogger.cs deleted file mode 100644 index ad7a6ceaaf..0000000000 --- a/src/Umbraco.Web/ImageProcessorLogger.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using Microsoft.Extensions.Logging; -using ImageProcessor.Common.Exceptions; -using Umbraco.Web.Composing; - -namespace Umbraco.Web -{ - - /// - /// A logger for explicitly logging ImageProcessor exceptions. - /// - /// Creating this logger is enough for ImageProcessor to find and replace its in-built debug logger - /// without any additional configuration required. This class currently has to be public in order - /// to do so. - /// - /// - public sealed class ImageProcessorLogger : ImageProcessor.Common.Exceptions.ILogger - { - /// - /// Logs the specified message as an error. - /// - /// The type calling the logger. - /// The message to log. - /// The property or method name calling the log. - /// The line number where the method is called. - public void Log(string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0) - { - // Using LogHelper since the ImageProcessor logger expects a parameterless constructor. - var message = $"{callerName} {lineNumber} : {text}"; - Current.Logger.LogError(new ImageProcessingException(message).Message); - } - - /// - /// Logs the specified message as an error. - /// - /// The type calling the logger. - /// The message to log. - /// The property or method name calling the log. - /// The line number where the method is called. - public void Log(Type type, string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0) - { - // Using LogHelper since the ImageProcessor logger expects a parameterless constructor. - var message = $"{callerName} {lineNumber} : {text}"; - Current.Logger.LogError(new ImageProcessingException(message).Message); - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index bda42e9db9..ad7dd86730 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -169,7 +169,6 @@ - @@ -205,7 +204,6 @@ - From 6f7a0f358a94a58f1ac8a730197902950afed994 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 7 Dec 2020 19:14:42 +1100 Subject: [PATCH 62/63] c# 9 --- .../Filters/CheckIfUserTicketDataIsStaleAttribute.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs index ff0c55746a..6e14468a2a 100644 --- a/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs @@ -106,7 +106,8 @@ namespace Umbraco.Web.BackOffice.Filters return; } - if (actionContext.HttpContext.User.Identity is not UmbracoBackOfficeIdentity identity) + var identity = actionContext.HttpContext.User.Identity as UmbracoBackOfficeIdentity; + if (identity == null) { return; } From da8bfd4615ac0afdcbcd7f4d2ee25c726fb58337 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 7 Dec 2020 10:04:52 +0100 Subject: [PATCH 63/63] Adhered two further tests to code linting rules (omitted from previous commit/PR) --- .../HealthCheckSettingsExtensionsTests.cs | 15 ++++--- .../ContentSettingsValidatorTests.cs | 42 ++++++++++--------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Extensions/HealthCheckSettingsExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Extensions/HealthCheckSettingsExtensionsTests.cs index 833e5c865b..5bc14b3792 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Extensions/HealthCheckSettingsExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Extensions/HealthCheckSettingsExtensionsTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; @@ -12,10 +15,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Extensions private ICronTabParser CronTabParser => new NCronTabParser(); [TestCase("30 12 * * *", 30)] - [TestCase("15 18 * * *", 60 * 6 + 15)] + [TestCase("15 18 * * *", (60 * 6) + 15)] [TestCase("0 3 * * *", 60 * 15)] - [TestCase("0 3 2 * *", 24 * 60 * 1 + 60 * 15)] - [TestCase("0 6 * * 3", 24 * 60 * 3 + 60 * 18)] + [TestCase("0 3 2 * *", (24 * 60 * 1) + (60 * 15))] + [TestCase("0 6 * * 3", (24 * 60 * 3) + (60 * 18))] public void Returns_Notification_Delay_From_Provided_Time(string firstRunTimeCronExpression, int expectedDelayInMinutes) { var settings = new HealthChecksSettings @@ -26,7 +29,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Extensions } }; var now = new DateTime(2020, 10, 31, 12, 0, 0); - var result = settings.GetNotificationDelay(CronTabParser, now, TimeSpan.Zero); + TimeSpan result = settings.GetNotificationDelay(CronTabParser, now, TimeSpan.Zero); Assert.AreEqual(expectedDelayInMinutes, result.TotalMinutes); } @@ -41,7 +44,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Extensions } }; var now = new DateTime(2020, 10, 31, 12, 25, 0); - var result = settings.GetNotificationDelay(CronTabParser, now, TimeSpan.FromMinutes(10)); + TimeSpan result = settings.GetNotificationDelay(CronTabParser, now, TimeSpan.FromMinutes(10)); Assert.AreEqual(10, result.TotalMinutes); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs index 31f6f5e31c..ded3f1c725 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs @@ -1,18 +1,22 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Options; +using NUnit.Framework; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models.Validation; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation { [TestFixture] - public class ContentSettingsValidationTests + public class ContentSettingsValidatorTests { [Test] public void Returns_Success_ForValid_Configuration() { var validator = new ContentSettingsValidator(); - var options = BuildContentSettings(); - var result = validator.Validate("settings", options); + ContentSettings options = BuildContentSettings(); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.True(result.Succeeded); } @@ -20,8 +24,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation public void Returns_Fail_For_Configuration_With_Invalid_Error404Collection_Due_To_Duplicate_Id() { var validator = new ContentSettingsValidator(); - var options = BuildContentSettings(contentXPath: "/aaa/bbb"); - var result = validator.Validate("settings", options); + ContentSettings options = BuildContentSettings(contentXPath: "/aaa/bbb"); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.False(result.Succeeded); } @@ -29,8 +33,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation public void Returns_Fail_For_Configuration_With_Invalid_Error404Collection_Due_To_Empty_Culture() { var validator = new ContentSettingsValidator(); - var options = BuildContentSettings(culture: string.Empty); - var result = validator.Validate("settings", options); + ContentSettings options = BuildContentSettings(culture: string.Empty); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.False(result.Succeeded); } @@ -38,27 +42,25 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation public void Returns_Fail_For_Configuration_With_Invalid_AutoFillImageProperties_Collection() { var validator = new ContentSettingsValidator(); - var options = BuildContentSettings(culture: string.Empty); - var result = validator.Validate("settings", options); + ContentSettings options = BuildContentSettings(culture: string.Empty); + ValidateOptionsResult result = validator.Validate("settings", options); Assert.False(result.Succeeded); } - private static ContentSettings BuildContentSettings(string culture = "en-US", string contentXPath = "", string autoFillImagePropertyAlias = "testAlias") - { - return new ContentSettings + private static ContentSettings BuildContentSettings(string culture = "en-US", string contentXPath = "", string autoFillImagePropertyAlias = "testAlias") => + new ContentSettings { Error404Collection = new ContentErrorPage[] - { - new ContentErrorPage { Culture = culture, ContentId = 1, ContentXPath = contentXPath }, - }, + { + new ContentErrorPage { Culture = culture, ContentId = 1, ContentXPath = contentXPath }, + }, Imaging = new ContentImagingSettings { AutoFillImageProperties = new ImagingAutoFillUploadField[] - { - new ImagingAutoFillUploadField { Alias = autoFillImagePropertyAlias, WidthFieldAlias = "w", HeightFieldAlias = "h", LengthFieldAlias = "l", ExtensionFieldAlias = "e" } - } + { + new ImagingAutoFillUploadField { Alias = autoFillImagePropertyAlias, WidthFieldAlias = "w", HeightFieldAlias = "h", LengthFieldAlias = "l", ExtensionFieldAlias = "e" } + } } }; - } } }