From 5321b761a5d0a8887bac375ddfc4a964462b118e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 3 Nov 2020 17:31:05 +0000 Subject: [PATCH 01/37] Add stylecop linting and example fix. --- linting/.editorconfig | 435 ++++++++++++++++++ linting/stylecop.json | 16 + .../Configuration/ICronTabParser.cs | 20 +- src/Umbraco.Core/Umbraco.Core.csproj | 15 +- 4 files changed, 484 insertions(+), 2 deletions(-) create mode 100644 linting/.editorconfig create mode 100644 linting/stylecop.json diff --git a/linting/.editorconfig b/linting/.editorconfig new file mode 100644 index 0000000000..1df50c82dd --- /dev/null +++ b/linting/.editorconfig @@ -0,0 +1,435 @@ +# Version: 1.6.2 (Using https://semver.org/) +# Updated: 2020-11-02 +# See https://github.com/RehanSaeed/EditorConfig/releases for release notes. +# See https://github.com/RehanSaeed/EditorConfig for updates to this file. +# See http://EditorConfig.org for more information about .editorconfig files. + +########################################## +# Common Settings +########################################## + +# This file is the top-most EditorConfig file +root = true + +# All Files +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +########################################## +# File Extension Settings +########################################## + +# Visual Studio Solution Files +[*.sln] +indent_style = tab + +# Visual Studio XML Project Files +[*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML Configuration Files +[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}] +indent_size = 2 + +# JSON Files +[*.{json,json5,webmanifest}] +indent_size = 2 + +# YAML Files +[*.{yml,yaml}] +indent_size = 2 + +# Markdown Files +[*.md] +trim_trailing_whitespace = false + +# Web Files +[*.{htm,html,js,jsm,ts,tsx,css,sass,scss,less,svg,vue}] +indent_size = 2 + +# Batch Files +[*.{cmd,bat}] +end_of_line = crlf + +# Bash Files +[*.sh] +end_of_line = lf + +# Makefiles +[Makefile] +indent_style = tab + +########################################## +# File Header (Uncomment to support file headers) +# https://docs.microsoft.com/visualstudio/ide/reference/add-file-header +########################################## + +# [*.{cs,csx,cake,vb,vbx}] +# file_header_template = \n© PROJECT-AUTHOR\n + +# SA1636: File header copyright text should match +# Justification: .editorconfig supports file headers. If this is changed to a value other than "none", a stylecop.json file will need to added to the project. +# dotnet_diagnostic.SA1636.severity = none + +########################################## +# .NET Language Conventions +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions +########################################## + +# .NET Code Style Settings +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#net-code-style-settings +[*.{cs,csx,cake,vb,vbx}] +# "this." and "Me." qualifiers +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#this-and-me +dotnet_style_qualification_for_field = true:warning +dotnet_style_qualification_for_property = true:warning +dotnet_style_qualification_for_method = true:warning +dotnet_style_qualification_for_event = true:warning +# Language keywords instead of framework type names for type references +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#language-keywords +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning +# Modifier preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#normalize-modifiers +dotnet_style_require_accessibility_modifiers = always:warning +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:warning +dotnet_style_readonly_field = true:warning +# Parentheses preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parentheses-preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion +# Expression-level preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_prefer_inferred_tuple_names = true:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion +dotnet_style_prefer_conditional_expression_over_return = false:suggestion +dotnet_style_prefer_compound_assignment = true:warning +# Null-checking preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#null-checking-preferences +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +# Parameter preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parameter-preferences +dotnet_code_quality_unused_parameters = all:warning +# More style options (Undocumented) +# https://github.com/MicrosoftDocs/visualstudio-docs/issues/3641 +dotnet_style_operator_placement_when_wrapping = end_of_line +# https://github.com/dotnet/roslyn/pull/40070 +dotnet_style_prefer_simplified_interpolation = true:warning + +# C# Code Style Settings +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-code-style-settings +[*.{cs,csx,cake}] +# Implicit and explicit types +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#implicit-and-explicit-types +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:warning +# Expression-bodied members +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-bodied-members +csharp_style_expression_bodied_methods = true:warning +csharp_style_expression_bodied_constructors = true:warning +csharp_style_expression_bodied_operators = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_lambdas = true:warning +csharp_style_expression_bodied_local_functions = true:warning +# Pattern matching +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#pattern-matching +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +# Inlined variable declarations +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#inlined-variable-declarations +csharp_style_inlined_variable_declaration = true:warning +# Expression-level preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences +csharp_prefer_simple_default_expression = true:warning +# "Null" checking preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-null-checking-preferences +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:warning +# Code block preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#code-block-preferences +csharp_prefer_braces = true:warning +# Unused value preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#unused-value-preferences +csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +# Index and range preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#index-and-range-preferences +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:warning +# Miscellaneous preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#miscellaneous-preferences +csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +csharp_using_directive_placement = inside_namespace:warning +csharp_prefer_static_local_function = true:warning +csharp_prefer_simple_using_statement = true:suggestion + +########################################## +# .NET Formatting Conventions +# https://docs.microsoft.com/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions +########################################## + +# Organize usings +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#organize-using-directives +dotnet_sort_system_directives_first = true +# Newline options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#new-line-options +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true +# Indentation options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#indentation-options +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = no_change +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents_when_block = false +# Spacing options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#spacing-options +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_after_comma = true +csharp_space_before_comma = false +csharp_space_after_dot = false +csharp_space_before_dot = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_before_semicolon_in_for_statement = false +csharp_space_around_declaration_statements = false +csharp_space_before_open_square_brackets = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_square_brackets = false +# Wrapping options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#wrap-options +csharp_preserve_single_line_statements = false +csharp_preserve_single_line_blocks = true + +########################################## +# .NET Naming Conventions +# https://docs.microsoft.com/visualstudio/ide/editorconfig-naming-conventions +########################################## + +[*.{cs,csx,cake,vb,vbx}] + +########################################## +# Styles +########################################## + +# camel_case_style - Define the camelCase style +dotnet_naming_style.camel_case_style.capitalization = camel_case +# pascal_case_style - Define the PascalCase style +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +# first_upper_style - The first character must start with an upper-case character +dotnet_naming_style.first_upper_style.capitalization = first_word_upper +# prefix_interface_with_i_style - Interfaces must be PascalCase and the first character of an interface must be an 'I' +dotnet_naming_style.prefix_interface_with_i_style.capitalization = pascal_case +dotnet_naming_style.prefix_interface_with_i_style.required_prefix = I +# prefix_type_parameters_with_t_style - Generic Type Parameters must be PascalCase and the first character must be a 'T' +dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_case +dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T +# disallowed_style - Anything that has this style applied is marked as disallowed +dotnet_naming_style.disallowed_style.capitalization = pascal_case +dotnet_naming_style.disallowed_style.required_prefix = ____RULE_VIOLATION____ +dotnet_naming_style.disallowed_style.required_suffix = ____RULE_VIOLATION____ +# internal_error_style - This style should never occur... if it does, it indicates a bug in file or in the parser using the file +dotnet_naming_style.internal_error_style.capitalization = pascal_case +dotnet_naming_style.internal_error_style.required_prefix = ____INTERNAL_ERROR____ +dotnet_naming_style.internal_error_style.required_suffix = ____INTERNAL_ERROR____ + +########################################## +# .NET Design Guideline Field Naming Rules +# Naming rules for fields follow the .NET Framework design guidelines +# https://docs.microsoft.com/dotnet/standard/design-guidelines/index +########################################## + +# All public/protected/protected_internal constant fields must be PascalCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/field +dotnet_naming_symbols.public_protected_constant_fields_group.applicable_accessibilities = public, protected, protected_internal +dotnet_naming_symbols.public_protected_constant_fields_group.required_modifiers = const +dotnet_naming_symbols.public_protected_constant_fields_group.applicable_kinds = field +dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.symbols = public_protected_constant_fields_group +dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.severity = warning + +# All public/protected/protected_internal static readonly fields must be PascalCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/field +dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_accessibilities = public, protected, protected_internal +dotnet_naming_symbols.public_protected_static_readonly_fields_group.required_modifiers = static, readonly +dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_kinds = field +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.symbols = public_protected_static_readonly_fields_group +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.severity = warning + +# No other public/protected/protected_internal fields are allowed +# https://docs.microsoft.com/dotnet/standard/design-guidelines/field +dotnet_naming_symbols.other_public_protected_fields_group.applicable_accessibilities = public, protected, protected_internal +dotnet_naming_symbols.other_public_protected_fields_group.applicable_kinds = field +dotnet_naming_rule.other_public_protected_fields_disallowed_rule.symbols = other_public_protected_fields_group +dotnet_naming_rule.other_public_protected_fields_disallowed_rule.style = disallowed_style +dotnet_naming_rule.other_public_protected_fields_disallowed_rule.severity = error + +########################################## +# StyleCop Field Naming Rules +# Naming rules for fields follow the StyleCop analyzers +# This does not override any rules using disallowed_style above +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers +########################################## + +# All constant fields must be PascalCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md +dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private +dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers = const +dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds = field +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols = stylecop_constant_fields_group +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.severity = warning + +# All static readonly fields must be PascalCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md +dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private +dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly +dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.severity = warning + +# No non-private instance fields are allowed +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md +dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected +dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = error + +# Private fields must be camelCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md +dotnet_naming_symbols.stylecop_private_fields_group.applicable_accessibilities = private +dotnet_naming_symbols.stylecop_private_fields_group.applicable_kinds = field +dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.symbols = stylecop_private_fields_group +dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.style = camel_case_style +dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.severity = warning + +# Local variables must be camelCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md +dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities = local +dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds = local +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols = stylecop_local_fields_group +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style = camel_case_style +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.severity = silent + +# This rule should never fire. However, it's included for at least two purposes: +# First, it helps to understand, reason about, and root-case certain types of issues, such as bugs in .editorconfig parsers. +# Second, it helps to raise immediate awareness if a new field type is added (as occurred recently in C#). +dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_accessibilities = * +dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_kinds = field +dotnet_naming_rule.sanity_check_uncovered_field_case_rule.symbols = sanity_check_uncovered_field_case_group +dotnet_naming_rule.sanity_check_uncovered_field_case_rule.style = internal_error_style +dotnet_naming_rule.sanity_check_uncovered_field_case_rule.severity = error + + +########################################## +# Other Naming Rules +########################################## + +# All of the following must be PascalCase: +# - Namespaces +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-namespaces +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md +# - Classes and Enumerations +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md +# - Delegates +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces#names-of-common-types +# - Constructors, Properties, Events, Methods +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-type-members +dotnet_naming_symbols.element_group.applicable_kinds = namespace, class, enum, struct, delegate, event, method, property +dotnet_naming_rule.element_rule.symbols = element_group +dotnet_naming_rule.element_rule.style = pascal_case_style +dotnet_naming_rule.element_rule.severity = warning + +# Interfaces use PascalCase and are prefixed with uppercase 'I' +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +dotnet_naming_symbols.interface_group.applicable_kinds = interface +dotnet_naming_rule.interface_rule.symbols = interface_group +dotnet_naming_rule.interface_rule.style = prefix_interface_with_i_style +dotnet_naming_rule.interface_rule.severity = warning + +# Generics Type Parameters use PascalCase and are prefixed with uppercase 'T' +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +dotnet_naming_symbols.type_parameter_group.applicable_kinds = type_parameter +dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_group +dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style +dotnet_naming_rule.type_parameter_rule.severity = warning + +# Function parameters use camelCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters +dotnet_naming_symbols.parameters_group.applicable_kinds = parameter +dotnet_naming_rule.parameters_rule.symbols = parameters_group +dotnet_naming_rule.parameters_rule.style = camel_case_style +dotnet_naming_rule.parameters_rule.severity = warning + +########################################## +# License +########################################## +# The following applies as to the .editorconfig file ONLY, and is +# included below for reference, per the requirements of the license +# corresponding to this .editorconfig file. +# See: https://github.com/RehanSaeed/EditorConfig +# +# MIT License +# +# Copyright (c) 2017-2019 Muhammad Rehan Saeed +# Copyright (c) 2019 Henry Gabryjelski +# +# Permission is hereby granted, free of charge, to any +# person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the +# Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, +# sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject +# to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +########################################## \ No newline at end of file diff --git a/linting/stylecop.json b/linting/stylecop.json new file mode 100644 index 0000000000..56526838bd --- /dev/null +++ b/linting/stylecop.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "orderingRules": { + "usingDirectivesPlacement": "outsideNamespace", + "elementOrder": [ + "kind" + ] + }, + "documentationRules": { + "xmlHeader": false, + "documentInternalElements": false, + "copyrightText": "Copyright (c) Umbraco and contributors.\nSee LICENSE for more details." + } + } +} diff --git a/src/Umbraco.Core/Configuration/ICronTabParser.cs b/src/Umbraco.Core/Configuration/ICronTabParser.cs index 2238be4a4c..0bc18361a9 100644 --- a/src/Umbraco.Core/Configuration/ICronTabParser.cs +++ b/src/Umbraco.Core/Configuration/ICronTabParser.cs @@ -1,10 +1,28 @@ -using System; +// Copyright (c) Umbraco and contributors. +// See LICENSE for more details. + +using System; namespace Umbraco.Core.Configuration { + /// + /// Defines the contract for that allows the parsing of chrontab expressions. + /// public interface ICronTabParser { + /// + /// Returns a value indicating whether a given chrontab expression is valid. + /// + /// The chrontab expression to parse. + /// The result. bool IsValidCronTab(string cronTab); + + /// + /// Returns the next occurence for the given chrontab expression from the given time. + /// + /// The chrontab expression to parse. + /// The date and time to start from. + /// The representing the next occurence. DateTime GetNextOccurrence(string cronTab, DateTime time); } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2b3efc9349..5a7aa3ba9b 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -7,7 +7,7 @@ 0.5.0 0.5.0 0.5.0 - Umbraco CMS + Umbraco CMS @@ -18,12 +18,25 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + <_Parameter1>Umbraco.Tests From 4aa85073d0b049f0dee9a5f77cd86ecb22841942 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 3 Nov 2020 17:47:16 +0000 Subject: [PATCH 02/37] Update copyright notice based on root license --- linting/stylecop.json | 2 +- src/Umbraco.Core/Configuration/ICronTabParser.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/linting/stylecop.json b/linting/stylecop.json index 56526838bd..b2f7771470 100644 --- a/linting/stylecop.json +++ b/linting/stylecop.json @@ -10,7 +10,7 @@ "documentationRules": { "xmlHeader": false, "documentInternalElements": false, - "copyrightText": "Copyright (c) Umbraco and contributors.\nSee LICENSE for more details." + "copyrightText": "Copyright (c) Umbraco.\nSee LICENSE for more details." } } } diff --git a/src/Umbraco.Core/Configuration/ICronTabParser.cs b/src/Umbraco.Core/Configuration/ICronTabParser.cs index 0bc18361a9..7124a098b3 100644 --- a/src/Umbraco.Core/Configuration/ICronTabParser.cs +++ b/src/Umbraco.Core/Configuration/ICronTabParser.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco and contributors. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; From b8ca26b940f7d4faa612a50a7f6d080c3394116b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 11 Nov 2020 17:07:18 +0000 Subject: [PATCH 03/37] Update .editorconfig --- linting/.editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linting/.editorconfig b/linting/.editorconfig index 1df50c82dd..cbd8f4859f 100644 --- a/linting/.editorconfig +++ b/linting/.editorconfig @@ -177,7 +177,7 @@ csharp_style_prefer_range_operator = true:warning # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#miscellaneous-preferences csharp_style_deconstructed_variable_declaration = true:warning csharp_style_pattern_local_over_anonymous_function = true:warning -csharp_using_directive_placement = inside_namespace:warning +csharp_using_directive_placement = outside_namespace:warning csharp_prefer_static_local_function = true:warning csharp_prefer_simple_using_statement = true:suggestion From 02122683a3e7df0a48b3fa0f20185c98625f7723 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 12 Nov 2020 14:39:30 +0000 Subject: [PATCH 04/37] Add files to solution --- src/umbraco.sln | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/umbraco.sln b/src/umbraco.sln index 6216b3128b..40772b74f1 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -7,10 +7,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web.UI", "Umbraco.W EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{2849E9D4-3B4E-40A3-A309-F3CB4F0E125F}" ProjectSection(SolutionItems) = preProject + ..\linting\.editorconfig = ..\linting\.editorconfig ..\build\build-bootstrap.ps1 = ..\build\build-bootstrap.ps1 ..\build\build.ps1 = ..\build\build.ps1 ..\NuGet.Config = ..\NuGet.Config SolutionInfo.cs = SolutionInfo.cs + ..\linting\stylecop.json = ..\linting\stylecop.json EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{FD962632-184C-4005-A5F3-E705D92FC645}" @@ -33,8 +35,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuSpecs", "NuSpecs", "{227C ProjectSection(SolutionItems) = preProject ..\build\NuSpecs\UmbracoCms.Core.nuspec = ..\build\NuSpecs\UmbracoCms.Core.nuspec ..\build\NuSpecs\UmbracoCms.nuspec = ..\build\NuSpecs\UmbracoCms.nuspec - ..\build\NuSpecs\UmbracoCms.Web.nuspec = ..\build\NuSpecs\UmbracoCms.Web.nuspec ..\build\NuSpecs\UmbracoCms.SqlCe.nuspec = ..\build\NuSpecs\UmbracoCms.SqlCe.nuspec + ..\build\NuSpecs\UmbracoCms.Web.nuspec = ..\build\NuSpecs\UmbracoCms.Web.nuspec EndProjectSection EndProject Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Web.UI.Client", "http://localhost:3961", "{3819A550-DCEC-4153-91B4-8BA9F7F0B9B4}" @@ -62,11 +64,8 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Web.UI.Client", "ht EndProject Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Tests.AcceptanceTest", "http://localhost:58896", "{9E4C8A12-FBE0-4673-8CE2-DF99D5D57817}" ProjectSection(WebsiteProperties) = preProject - UseIISExpress = "true" - TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5" - Debug.AspNetCompiler.VirtualPath = "/localhost_58896" - Debug.AspNetCompiler.PhysicalPath = "Umbraco.Tests.AcceptanceTest\" - Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_58896\" + UseIISExpress = "true" + TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5" Debug.AspNetCompiler.VirtualPath = "/localhost_62926" Debug.AspNetCompiler.PhysicalPath = "Umbraco.Tests.AcceptanceTest\" Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_62926\" @@ -74,9 +73,6 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Tests.AcceptanceTes Debug.AspNetCompiler.ForceOverwrite = "true" Debug.AspNetCompiler.FixedNames = "false" Debug.AspNetCompiler.Debug = "True" - Release.AspNetCompiler.VirtualPath = "/localhost_58896" - Release.AspNetCompiler.PhysicalPath = "Umbraco.Tests.AcceptanceTest\" - Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_58896\" Release.AspNetCompiler.VirtualPath = "/localhost_62926" Release.AspNetCompiler.PhysicalPath = "Umbraco.Tests.AcceptanceTest\" Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_62926\" @@ -84,11 +80,9 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Tests.AcceptanceTes Release.AspNetCompiler.ForceOverwrite = "true" Release.AspNetCompiler.FixedNames = "false" Release.AspNetCompiler.Debug = "False" - DefaultWebSiteLanguage = "Visual C#" - StartServerOnDebug = "false" - VWDPort = "58896" - VWDPort = "62926" SlnRelativePath = "Umbraco.Tests.AcceptanceTest\" + DefaultWebSiteLanguage = "Visual C#" + StartServerOnDebug = "false" EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web", "Umbraco.Web\Umbraco.Web.csproj", "{651E1350-91B6-44B7-BD60-7207006D7003}" @@ -107,10 +101,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{E3F9F378 EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{5B03EF4E-E0AC-4905-861B-8C3EC1A0D458}" -ProjectSection(SolutionItems) = preProject - ..\build\NuSpecs\build\Umbraco.Cms.props = ..\build\NuSpecs\build\Umbraco.Cms.props - ..\build\NuSpecs\build\Umbraco.Cms.targets = ..\build\NuSpecs\build\Umbraco.Cms.targets -EndProjectSection + ProjectSection(SolutionItems) = preProject + ..\build\NuSpecs\build\Umbraco.Cms.props = ..\build\NuSpecs\build\Umbraco.Cms.props + ..\build\NuSpecs\build\Umbraco.Cms.targets = ..\build\NuSpecs\build\Umbraco.Cms.targets + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DocTools", "DocTools", "{53594E5B-64A2-4545-8367-E3627D266AE8}" ProjectSection(SolutionItems) = preProject @@ -133,7 +127,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IssueTemplates", "IssueTemp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Core", "Umbraco.Core\Umbraco.Core.csproj", "{29AA69D9-B597-4395-8D42-43B1263C240A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.ModelsBuilder.Embedded", "Umbraco.ModelsBuilder.Embedded\Umbraco.ModelsBuilder.Embedded.csproj", "{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.ModelsBuilder.Embedded", "Umbraco.ModelsBuilder.Embedded\Umbraco.ModelsBuilder.Embedded.csproj", "{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Infrastructure", "Umbraco.Infrastructure\Umbraco.Infrastructure.csproj", "{3AE7BF57-966B-45A5-910A-954D7C554441}" EndProject From 0e1f6fc69b3be70208b5099d3634c943760c0f97 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 20 Nov 2020 00:02:05 +0000 Subject: [PATCH 05/37] Remove rulset noise and allow further customization --- linting/codeanalysis.ruleset | 26 +++++++++++++ linting/codeanalysis.tests.ruleset | 57 ++++++++++++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 27 +++++++------ src/umbraco-netcore-only.sln | 4 ++ src/umbraco.sln | 2 + 5 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 linting/codeanalysis.ruleset create mode 100644 linting/codeanalysis.tests.ruleset diff --git a/linting/codeanalysis.ruleset b/linting/codeanalysis.ruleset new file mode 100644 index 0000000000..e4a1e8fe83 --- /dev/null +++ b/linting/codeanalysis.ruleset @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/linting/codeanalysis.tests.ruleset b/linting/codeanalysis.tests.ruleset new file mode 100644 index 0000000000..7074c8c25b --- /dev/null +++ b/linting/codeanalysis.tests.ruleset @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5a7aa3ba9b..8f9282be55 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -7,7 +7,7 @@ 0.5.0 0.5.0 0.5.0 - Umbraco CMS + Umbraco CMS @@ -15,17 +15,17 @@ - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + @@ -36,6 +36,9 @@ + + ..\..\linting\codeanalysis.ruleset + diff --git a/src/umbraco-netcore-only.sln b/src/umbraco-netcore-only.sln index f9e749c59d..08c1ec42d0 100644 --- a/src/umbraco-netcore-only.sln +++ b/src/umbraco-netcore-only.sln @@ -5,10 +5,14 @@ VisualStudioVersion = 16.0.29209.152 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{2849E9D4-3B4E-40A3-A309-F3CB4F0E125F}" ProjectSection(SolutionItems) = preProject + ..\linting\.editorconfig = ..\linting\.editorconfig ..\build\build-bootstrap.ps1 = ..\build\build-bootstrap.ps1 ..\build\build.ps1 = ..\build\build.ps1 ..\NuGet.Config = ..\NuGet.Config SolutionInfo.cs = SolutionInfo.cs + ..\linting\stylecop.json = ..\linting\stylecop.json + ..\linting\codeanalysis.ruleset = ..\linting\codeanalysis.ruleset + ..\linting\codeanalysis.tests.ruleset = ..\linting\codeanalysis.tests.ruleset EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{FD962632-184C-4005-A5F3-E705D92FC645}" diff --git a/src/umbraco.sln b/src/umbraco.sln index 40772b74f1..ab7bfae3ec 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{2849E9D4 ..\NuGet.Config = ..\NuGet.Config SolutionInfo.cs = SolutionInfo.cs ..\linting\stylecop.json = ..\linting\stylecop.json + ..\linting\codeanalysis.ruleset = ..\linting\codeanalysis.ruleset + ..\linting\codeanalysis.tests.ruleset = ..\linting\codeanalysis.tests.ruleset EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{FD962632-184C-4005-A5F3-E705D92FC645}" From b16c2e47a1745169cc9251367b37813eb65d6bfd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 20 Nov 2020 00:10:49 +0000 Subject: [PATCH 06/37] Align umbraco.sln --- src/umbraco.sln | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/umbraco.sln b/src/umbraco.sln index ab7bfae3ec..1c4226f9db 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -37,8 +37,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuSpecs", "NuSpecs", "{227C ProjectSection(SolutionItems) = preProject ..\build\NuSpecs\UmbracoCms.Core.nuspec = ..\build\NuSpecs\UmbracoCms.Core.nuspec ..\build\NuSpecs\UmbracoCms.nuspec = ..\build\NuSpecs\UmbracoCms.nuspec - ..\build\NuSpecs\UmbracoCms.SqlCe.nuspec = ..\build\NuSpecs\UmbracoCms.SqlCe.nuspec ..\build\NuSpecs\UmbracoCms.Web.nuspec = ..\build\NuSpecs\UmbracoCms.Web.nuspec + ..\build\NuSpecs\UmbracoCms.SqlCe.nuspec = ..\build\NuSpecs\UmbracoCms.SqlCe.nuspec EndProjectSection EndProject Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Web.UI.Client", "http://localhost:3961", "{3819A550-DCEC-4153-91B4-8BA9F7F0B9B4}" @@ -66,8 +66,11 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Web.UI.Client", "ht EndProject Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Tests.AcceptanceTest", "http://localhost:58896", "{9E4C8A12-FBE0-4673-8CE2-DF99D5D57817}" ProjectSection(WebsiteProperties) = preProject - UseIISExpress = "true" - TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5" + UseIISExpress = "true" + TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5" + Debug.AspNetCompiler.VirtualPath = "/localhost_58896" + Debug.AspNetCompiler.PhysicalPath = "Umbraco.Tests.AcceptanceTest\" + Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_58896\" Debug.AspNetCompiler.VirtualPath = "/localhost_62926" Debug.AspNetCompiler.PhysicalPath = "Umbraco.Tests.AcceptanceTest\" Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_62926\" @@ -75,6 +78,9 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Tests.AcceptanceTes Debug.AspNetCompiler.ForceOverwrite = "true" Debug.AspNetCompiler.FixedNames = "false" Debug.AspNetCompiler.Debug = "True" + Release.AspNetCompiler.VirtualPath = "/localhost_58896" + Release.AspNetCompiler.PhysicalPath = "Umbraco.Tests.AcceptanceTest\" + Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_58896\" Release.AspNetCompiler.VirtualPath = "/localhost_62926" Release.AspNetCompiler.PhysicalPath = "Umbraco.Tests.AcceptanceTest\" Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_62926\" @@ -82,9 +88,11 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Tests.AcceptanceTes Release.AspNetCompiler.ForceOverwrite = "true" Release.AspNetCompiler.FixedNames = "false" Release.AspNetCompiler.Debug = "False" + DefaultWebSiteLanguage = "Visual C#" + StartServerOnDebug = "false" + VWDPort = "58896" + VWDPort = "62926" SlnRelativePath = "Umbraco.Tests.AcceptanceTest\" - DefaultWebSiteLanguage = "Visual C#" - StartServerOnDebug = "false" EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web", "Umbraco.Web\Umbraco.Web.csproj", "{651E1350-91B6-44B7-BD60-7207006D7003}" @@ -103,10 +111,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{E3F9F378 EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{5B03EF4E-E0AC-4905-861B-8C3EC1A0D458}" - ProjectSection(SolutionItems) = preProject - ..\build\NuSpecs\build\Umbraco.Cms.props = ..\build\NuSpecs\build\Umbraco.Cms.props - ..\build\NuSpecs\build\Umbraco.Cms.targets = ..\build\NuSpecs\build\Umbraco.Cms.targets - EndProjectSection +ProjectSection(SolutionItems) = preProject + ..\build\NuSpecs\build\Umbraco.Cms.props = ..\build\NuSpecs\build\Umbraco.Cms.props + ..\build\NuSpecs\build\Umbraco.Cms.targets = ..\build\NuSpecs\build\Umbraco.Cms.targets +EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DocTools", "DocTools", "{53594E5B-64A2-4545-8367-E3627D266AE8}" ProjectSection(SolutionItems) = preProject @@ -129,7 +137,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IssueTemplates", "IssueTemp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Core", "Umbraco.Core\Umbraco.Core.csproj", "{29AA69D9-B597-4395-8D42-43B1263C240A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.ModelsBuilder.Embedded", "Umbraco.ModelsBuilder.Embedded\Umbraco.ModelsBuilder.Embedded.csproj", "{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.ModelsBuilder.Embedded", "Umbraco.ModelsBuilder.Embedded\Umbraco.ModelsBuilder.Embedded.csproj", "{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Infrastructure", "Umbraco.Infrastructure\Umbraco.Infrastructure.csproj", "{3AE7BF57-966B-45A5-910A-954D7C554441}" EndProject From 71f7952e3d9df9704df3506aafc87a9ed4428c99 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 23 Nov 2020 11:14:07 +0000 Subject: [PATCH 07/37] Update rules based upon feedback --- linting/.editorconfig | 21 +++++++++++++++------ linting/codeanalysis.ruleset | 6 ++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/linting/.editorconfig b/linting/.editorconfig index cbd8f4859f..5a101735b1 100644 --- a/linting/.editorconfig +++ b/linting/.editorconfig @@ -69,7 +69,7 @@ indent_style = tab ########################################## # [*.{cs,csx,cake,vb,vbx}] -# file_header_template = \n© PROJECT-AUTHOR\n +file_header_template = Copyright (c) Umbraco.\nSee LICENSE for more details. # SA1636: File header copyright text should match # Justification: .editorconfig supports file headers. If this is changed to a value other than "none", a stylecop.json file will need to added to the project. @@ -85,10 +85,10 @@ indent_style = tab [*.{cs,csx,cake,vb,vbx}] # "this." and "Me." qualifiers # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#this-and-me -dotnet_style_qualification_for_field = true:warning -dotnet_style_qualification_for_property = true:warning -dotnet_style_qualification_for_method = true:warning -dotnet_style_qualification_for_event = true:warning +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion # Language keywords instead of framework type names for type references # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#language-keywords dotnet_style_predefined_type_for_locals_parameters_members = true:warning @@ -392,6 +392,15 @@ dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_gro dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style dotnet_naming_rule.type_parameter_rule.severity = warning +# Internal and private fields use camelCase and are prefixed with '_' +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + # Function parameters use camelCase # https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters dotnet_naming_symbols.parameters_group.applicable_kinds = parameter @@ -432,4 +441,4 @@ dotnet_naming_rule.parameters_rule.severity = warning # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. -########################################## \ No newline at end of file +########################################## diff --git a/linting/codeanalysis.ruleset b/linting/codeanalysis.ruleset index e4a1e8fe83..3049c3c3c4 100644 --- a/linting/codeanalysis.ruleset +++ b/linting/codeanalysis.ruleset @@ -8,9 +8,15 @@ + + + + + + From 2d2748a7d31fe4fa9e9fda5e8ff0b8fde3fdf958 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 23 Nov 2020 12:37:05 +0000 Subject: [PATCH 08/37] Add underscore warnings --- linting/.editorconfig | 24 ++++++++++++++++-------- linting/codeanalysis.ruleset | 3 +++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/linting/.editorconfig b/linting/.editorconfig index 5a101735b1..dd71932330 100644 --- a/linting/.editorconfig +++ b/linting/.editorconfig @@ -392,14 +392,22 @@ dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_gro dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style dotnet_naming_rule.type_parameter_rule.severity = warning -# Internal and private fields use camelCase and are prefixed with '_' -dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion -dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields -dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style -dotnet_naming_symbols.private_internal_fields.applicable_kinds = field -dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal -dotnet_naming_style.camel_case_underscore_style.required_prefix = _ -dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case +# Static fields are camelCase and start with s_ +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_style.static_field_style.capitalization = camel_case +dotnet_naming_style.static_field_style.required_prefix = s_ +dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style +dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields +dotnet_naming_rule.static_fields_should_be_camel_case.severity = warning + +# Instance fields use camelCase and are prefixed with '_' +dotnet_naming_symbols.instance_fields.applicable_kinds = field +dotnet_naming_style.instance_field_style.capitalization = camel_case +dotnet_naming_style.instance_field_style.required_prefix = _ +dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style +dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields +dotnet_naming_rule.instance_fields_should_be_camel_case.severity = warning # Function parameters use camelCase # https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters diff --git a/linting/codeanalysis.ruleset b/linting/codeanalysis.ruleset index 3049c3c3c4..81a3786e95 100644 --- a/linting/codeanalysis.ruleset +++ b/linting/codeanalysis.ruleset @@ -17,6 +17,9 @@ + + + From 4a1040d1afe865f81afaa95615ba01ed1bdb9be6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 24 Nov 2020 12:20:43 +0000 Subject: [PATCH 09/37] Naming rules now work --- linting/.editorconfig | 33 +++++++++++---------- linting/codeanalysis.ruleset | 3 ++ src/Umbraco.Core/Properties/AssemblyInfo.cs | 6 +--- src/Umbraco.Web/Properties/AssemblyInfo.cs | 6 +--- 4 files changed, 23 insertions(+), 25 deletions(-) diff --git a/linting/.editorconfig b/linting/.editorconfig index dd71932330..da757142c2 100644 --- a/linting/.editorconfig +++ b/linting/.editorconfig @@ -316,8 +316,9 @@ dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.severity = warning # All static readonly fields must be PascalCase +# Ajusted to ignore private fields. # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md -dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private +dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group @@ -392,22 +393,24 @@ dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_gro dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style dotnet_naming_rule.type_parameter_rule.severity = warning -# Static fields are camelCase and start with s_ -dotnet_naming_symbols.static_fields.applicable_kinds = field -dotnet_naming_symbols.static_fields.required_modifiers = static -dotnet_naming_style.static_field_style.capitalization = camel_case -dotnet_naming_style.static_field_style.required_prefix = s_ -dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style -dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields -dotnet_naming_rule.static_fields_should_be_camel_case.severity = warning +# Private static fields use camelCase and start with s_ +dotnet_naming_symbols.private_static_field_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_field_symbols.required_modifiers = static,shared +dotnet_naming_symbols.private_static_field_symbols.applicable_kinds = field +dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.symbols = private_static_field_symbols +dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.style = camel_case_and_prefix_with_s_underscore_style +dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.severity = warning +dotnet_naming_style.camel_case_and_prefix_with_s_underscore_style.required_prefix = s_ +dotnet_naming_style.camel_case_and_prefix_with_s_underscore_style.capitalization = camel_case # Instance fields use camelCase and are prefixed with '_' -dotnet_naming_symbols.instance_fields.applicable_kinds = field -dotnet_naming_style.instance_field_style.capitalization = camel_case -dotnet_naming_style.instance_field_style.required_prefix = _ -dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style -dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields -dotnet_naming_rule.instance_fields_should_be_camel_case.severity = warning +dotnet_naming_symbols.private_field_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_field_symbols.applicable_kinds = field +dotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.symbols = private_field_symbols +dotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.style = camel_case_and_prefix_with_underscore_style +dotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.severity = warning +dotnet_naming_style.camel_case_and_prefix_with_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_and_prefix_with_underscore_style.capitalization = camel_case # Function parameters use camelCase # https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters diff --git a/linting/codeanalysis.ruleset b/linting/codeanalysis.ruleset index 81a3786e95..b94fa22670 100644 --- a/linting/codeanalysis.ruleset +++ b/linting/codeanalysis.ruleset @@ -14,6 +14,9 @@ + + + diff --git a/src/Umbraco.Core/Properties/AssemblyInfo.cs b/src/Umbraco.Core/Properties/AssemblyInfo.cs index f129ca7731..ede9e49a7d 100644 --- a/src/Umbraco.Core/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Core/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -28,7 +28,3 @@ using System.Runtime.InteropServices; // Umbraco Headless [assembly: InternalsVisibleTo("Umbraco.Cloud.Headless")] - -// code analysis -// IDE1006 is broken, wants _value syntax for consts, etc - and it's even confusing ppl at MS, kill it -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "~_~")] diff --git a/src/Umbraco.Web/Properties/AssemblyInfo.cs b/src/Umbraco.Web/Properties/AssemblyInfo.cs index ce2cbf0282..e348aceaee 100644 --- a/src/Umbraco.Web/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Web/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -31,7 +31,3 @@ using System.Runtime.InteropServices; // Umbraco Headless [assembly: InternalsVisibleTo("Umbraco.Cloud.Headless")] - -// code analysis -// IDE1006 is broken, wants _value syntax for consts, etc - and it's even confusing ppl at MS, kill it -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "~_~")] From ed6706a14583fb52465a89ec2af676eea0813582 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 27 Nov 2020 01:08:18 +0000 Subject: [PATCH 10/37] Global rules with split rulesets. --- Directory.Build.props | 23 ++ Directory.Build.targets | 26 ++ linting/.editorconfig | 4 +- src/.editorconfig | 455 +++++++++++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 16 - src/umbraco-netcore-only.sln | 2 + src/umbraco.sln | 2 + 7 files changed, 510 insertions(+), 18 deletions(-) create mode 100644 Directory.Build.props create mode 100644 Directory.Build.targets create mode 100644 src/.editorconfig diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000000..e600602a27 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,23 @@ + + + + + + + + + + + + + $(MSBuildThisFileDirectory)linting\codeanalysis.tests.ruleset + + + + + $(MSBuildThisFileDirectory)linting\codeanalysis.ruleset + + + + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000000..e9ffa15255 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,26 @@ + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + diff --git a/linting/.editorconfig b/linting/.editorconfig index da757142c2..abeb2801d2 100644 --- a/linting/.editorconfig +++ b/linting/.editorconfig @@ -135,9 +135,9 @@ dotnet_style_prefer_simplified_interpolation = true:warning [*.{cs,csx,cake}] # Implicit and explicit types # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#implicit-and-explicit-types -csharp_style_var_for_built_in_types = true:warning +csharp_style_var_for_built_in_types = never csharp_style_var_when_type_is_apparent = true:warning -csharp_style_var_elsewhere = true:warning +csharp_style_var_elsewhere = false:warning # Expression-bodied members # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-bodied-members csharp_style_expression_bodied_methods = true:warning diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 0000000000..abeb2801d2 --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,455 @@ +# Version: 1.6.2 (Using https://semver.org/) +# Updated: 2020-11-02 +# See https://github.com/RehanSaeed/EditorConfig/releases for release notes. +# See https://github.com/RehanSaeed/EditorConfig for updates to this file. +# See http://EditorConfig.org for more information about .editorconfig files. + +########################################## +# Common Settings +########################################## + +# This file is the top-most EditorConfig file +root = true + +# All Files +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +########################################## +# File Extension Settings +########################################## + +# Visual Studio Solution Files +[*.sln] +indent_style = tab + +# Visual Studio XML Project Files +[*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML Configuration Files +[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}] +indent_size = 2 + +# JSON Files +[*.{json,json5,webmanifest}] +indent_size = 2 + +# YAML Files +[*.{yml,yaml}] +indent_size = 2 + +# Markdown Files +[*.md] +trim_trailing_whitespace = false + +# Web Files +[*.{htm,html,js,jsm,ts,tsx,css,sass,scss,less,svg,vue}] +indent_size = 2 + +# Batch Files +[*.{cmd,bat}] +end_of_line = crlf + +# Bash Files +[*.sh] +end_of_line = lf + +# Makefiles +[Makefile] +indent_style = tab + +########################################## +# File Header (Uncomment to support file headers) +# https://docs.microsoft.com/visualstudio/ide/reference/add-file-header +########################################## + +# [*.{cs,csx,cake,vb,vbx}] +file_header_template = Copyright (c) Umbraco.\nSee LICENSE for more details. + +# SA1636: File header copyright text should match +# Justification: .editorconfig supports file headers. If this is changed to a value other than "none", a stylecop.json file will need to added to the project. +# dotnet_diagnostic.SA1636.severity = none + +########################################## +# .NET Language Conventions +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions +########################################## + +# .NET Code Style Settings +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#net-code-style-settings +[*.{cs,csx,cake,vb,vbx}] +# "this." and "Me." qualifiers +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#this-and-me +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion +# Language keywords instead of framework type names for type references +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#language-keywords +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning +# Modifier preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#normalize-modifiers +dotnet_style_require_accessibility_modifiers = always:warning +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:warning +dotnet_style_readonly_field = true:warning +# Parentheses preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parentheses-preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion +# Expression-level preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_prefer_inferred_tuple_names = true:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion +dotnet_style_prefer_conditional_expression_over_return = false:suggestion +dotnet_style_prefer_compound_assignment = true:warning +# Null-checking preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#null-checking-preferences +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +# Parameter preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parameter-preferences +dotnet_code_quality_unused_parameters = all:warning +# More style options (Undocumented) +# https://github.com/MicrosoftDocs/visualstudio-docs/issues/3641 +dotnet_style_operator_placement_when_wrapping = end_of_line +# https://github.com/dotnet/roslyn/pull/40070 +dotnet_style_prefer_simplified_interpolation = true:warning + +# C# Code Style Settings +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-code-style-settings +[*.{cs,csx,cake}] +# Implicit and explicit types +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#implicit-and-explicit-types +csharp_style_var_for_built_in_types = never +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = false:warning +# Expression-bodied members +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-bodied-members +csharp_style_expression_bodied_methods = true:warning +csharp_style_expression_bodied_constructors = true:warning +csharp_style_expression_bodied_operators = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_lambdas = true:warning +csharp_style_expression_bodied_local_functions = true:warning +# Pattern matching +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#pattern-matching +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +# Inlined variable declarations +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#inlined-variable-declarations +csharp_style_inlined_variable_declaration = true:warning +# Expression-level preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences +csharp_prefer_simple_default_expression = true:warning +# "Null" checking preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-null-checking-preferences +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:warning +# Code block preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#code-block-preferences +csharp_prefer_braces = true:warning +# Unused value preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#unused-value-preferences +csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +# Index and range preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#index-and-range-preferences +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:warning +# Miscellaneous preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#miscellaneous-preferences +csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +csharp_using_directive_placement = outside_namespace:warning +csharp_prefer_static_local_function = true:warning +csharp_prefer_simple_using_statement = true:suggestion + +########################################## +# .NET Formatting Conventions +# https://docs.microsoft.com/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions +########################################## + +# Organize usings +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#organize-using-directives +dotnet_sort_system_directives_first = true +# Newline options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#new-line-options +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true +# Indentation options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#indentation-options +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = no_change +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents_when_block = false +# Spacing options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#spacing-options +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_after_comma = true +csharp_space_before_comma = false +csharp_space_after_dot = false +csharp_space_before_dot = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_before_semicolon_in_for_statement = false +csharp_space_around_declaration_statements = false +csharp_space_before_open_square_brackets = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_square_brackets = false +# Wrapping options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#wrap-options +csharp_preserve_single_line_statements = false +csharp_preserve_single_line_blocks = true + +########################################## +# .NET Naming Conventions +# https://docs.microsoft.com/visualstudio/ide/editorconfig-naming-conventions +########################################## + +[*.{cs,csx,cake,vb,vbx}] + +########################################## +# Styles +########################################## + +# camel_case_style - Define the camelCase style +dotnet_naming_style.camel_case_style.capitalization = camel_case +# pascal_case_style - Define the PascalCase style +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +# first_upper_style - The first character must start with an upper-case character +dotnet_naming_style.first_upper_style.capitalization = first_word_upper +# prefix_interface_with_i_style - Interfaces must be PascalCase and the first character of an interface must be an 'I' +dotnet_naming_style.prefix_interface_with_i_style.capitalization = pascal_case +dotnet_naming_style.prefix_interface_with_i_style.required_prefix = I +# prefix_type_parameters_with_t_style - Generic Type Parameters must be PascalCase and the first character must be a 'T' +dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_case +dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T +# disallowed_style - Anything that has this style applied is marked as disallowed +dotnet_naming_style.disallowed_style.capitalization = pascal_case +dotnet_naming_style.disallowed_style.required_prefix = ____RULE_VIOLATION____ +dotnet_naming_style.disallowed_style.required_suffix = ____RULE_VIOLATION____ +# internal_error_style - This style should never occur... if it does, it indicates a bug in file or in the parser using the file +dotnet_naming_style.internal_error_style.capitalization = pascal_case +dotnet_naming_style.internal_error_style.required_prefix = ____INTERNAL_ERROR____ +dotnet_naming_style.internal_error_style.required_suffix = ____INTERNAL_ERROR____ + +########################################## +# .NET Design Guideline Field Naming Rules +# Naming rules for fields follow the .NET Framework design guidelines +# https://docs.microsoft.com/dotnet/standard/design-guidelines/index +########################################## + +# All public/protected/protected_internal constant fields must be PascalCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/field +dotnet_naming_symbols.public_protected_constant_fields_group.applicable_accessibilities = public, protected, protected_internal +dotnet_naming_symbols.public_protected_constant_fields_group.required_modifiers = const +dotnet_naming_symbols.public_protected_constant_fields_group.applicable_kinds = field +dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.symbols = public_protected_constant_fields_group +dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.severity = warning + +# All public/protected/protected_internal static readonly fields must be PascalCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/field +dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_accessibilities = public, protected, protected_internal +dotnet_naming_symbols.public_protected_static_readonly_fields_group.required_modifiers = static, readonly +dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_kinds = field +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.symbols = public_protected_static_readonly_fields_group +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.severity = warning + +# No other public/protected/protected_internal fields are allowed +# https://docs.microsoft.com/dotnet/standard/design-guidelines/field +dotnet_naming_symbols.other_public_protected_fields_group.applicable_accessibilities = public, protected, protected_internal +dotnet_naming_symbols.other_public_protected_fields_group.applicable_kinds = field +dotnet_naming_rule.other_public_protected_fields_disallowed_rule.symbols = other_public_protected_fields_group +dotnet_naming_rule.other_public_protected_fields_disallowed_rule.style = disallowed_style +dotnet_naming_rule.other_public_protected_fields_disallowed_rule.severity = error + +########################################## +# StyleCop Field Naming Rules +# Naming rules for fields follow the StyleCop analyzers +# This does not override any rules using disallowed_style above +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers +########################################## + +# All constant fields must be PascalCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md +dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private +dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers = const +dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds = field +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols = stylecop_constant_fields_group +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.severity = warning + +# All static readonly fields must be PascalCase +# Ajusted to ignore private fields. +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md +dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected +dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly +dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.severity = warning + +# No non-private instance fields are allowed +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md +dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected +dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = error + +# Private fields must be camelCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md +dotnet_naming_symbols.stylecop_private_fields_group.applicable_accessibilities = private +dotnet_naming_symbols.stylecop_private_fields_group.applicable_kinds = field +dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.symbols = stylecop_private_fields_group +dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.style = camel_case_style +dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.severity = warning + +# Local variables must be camelCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md +dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities = local +dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds = local +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols = stylecop_local_fields_group +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style = camel_case_style +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.severity = silent + +# This rule should never fire. However, it's included for at least two purposes: +# First, it helps to understand, reason about, and root-case certain types of issues, such as bugs in .editorconfig parsers. +# Second, it helps to raise immediate awareness if a new field type is added (as occurred recently in C#). +dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_accessibilities = * +dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_kinds = field +dotnet_naming_rule.sanity_check_uncovered_field_case_rule.symbols = sanity_check_uncovered_field_case_group +dotnet_naming_rule.sanity_check_uncovered_field_case_rule.style = internal_error_style +dotnet_naming_rule.sanity_check_uncovered_field_case_rule.severity = error + + +########################################## +# Other Naming Rules +########################################## + +# All of the following must be PascalCase: +# - Namespaces +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-namespaces +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md +# - Classes and Enumerations +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md +# - Delegates +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces#names-of-common-types +# - Constructors, Properties, Events, Methods +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-type-members +dotnet_naming_symbols.element_group.applicable_kinds = namespace, class, enum, struct, delegate, event, method, property +dotnet_naming_rule.element_rule.symbols = element_group +dotnet_naming_rule.element_rule.style = pascal_case_style +dotnet_naming_rule.element_rule.severity = warning + +# Interfaces use PascalCase and are prefixed with uppercase 'I' +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +dotnet_naming_symbols.interface_group.applicable_kinds = interface +dotnet_naming_rule.interface_rule.symbols = interface_group +dotnet_naming_rule.interface_rule.style = prefix_interface_with_i_style +dotnet_naming_rule.interface_rule.severity = warning + +# Generics Type Parameters use PascalCase and are prefixed with uppercase 'T' +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +dotnet_naming_symbols.type_parameter_group.applicable_kinds = type_parameter +dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_group +dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style +dotnet_naming_rule.type_parameter_rule.severity = warning + +# Private static fields use camelCase and start with s_ +dotnet_naming_symbols.private_static_field_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_field_symbols.required_modifiers = static,shared +dotnet_naming_symbols.private_static_field_symbols.applicable_kinds = field +dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.symbols = private_static_field_symbols +dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.style = camel_case_and_prefix_with_s_underscore_style +dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.severity = warning +dotnet_naming_style.camel_case_and_prefix_with_s_underscore_style.required_prefix = s_ +dotnet_naming_style.camel_case_and_prefix_with_s_underscore_style.capitalization = camel_case + +# Instance fields use camelCase and are prefixed with '_' +dotnet_naming_symbols.private_field_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_field_symbols.applicable_kinds = field +dotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.symbols = private_field_symbols +dotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.style = camel_case_and_prefix_with_underscore_style +dotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.severity = warning +dotnet_naming_style.camel_case_and_prefix_with_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_and_prefix_with_underscore_style.capitalization = camel_case + +# Function parameters use camelCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters +dotnet_naming_symbols.parameters_group.applicable_kinds = parameter +dotnet_naming_rule.parameters_rule.symbols = parameters_group +dotnet_naming_rule.parameters_rule.style = camel_case_style +dotnet_naming_rule.parameters_rule.severity = warning + +########################################## +# License +########################################## +# The following applies as to the .editorconfig file ONLY, and is +# included below for reference, per the requirements of the license +# corresponding to this .editorconfig file. +# See: https://github.com/RehanSaeed/EditorConfig +# +# MIT License +# +# Copyright (c) 2017-2019 Muhammad Rehan Saeed +# Copyright (c) 2019 Henry Gabryjelski +# +# Permission is hereby granted, free of charge, to any +# person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the +# Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, +# sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject +# to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +########################################## diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8f9282be55..300dedc1c6 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -18,28 +18,12 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - ..\..\linting\codeanalysis.ruleset - - <_Parameter1>Umbraco.Tests diff --git a/src/umbraco-netcore-only.sln b/src/umbraco-netcore-only.sln index 08c1ec42d0..d7d3385dc8 100644 --- a/src/umbraco-netcore-only.sln +++ b/src/umbraco-netcore-only.sln @@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{2849E9D4 ..\linting\stylecop.json = ..\linting\stylecop.json ..\linting\codeanalysis.ruleset = ..\linting\codeanalysis.ruleset ..\linting\codeanalysis.tests.ruleset = ..\linting\codeanalysis.tests.ruleset + ..\Directory.Build.props = ..\Directory.Build.props + ..\Directory.Build.targets = ..\Directory.Build.targets EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{FD962632-184C-4005-A5F3-E705D92FC645}" diff --git a/src/umbraco.sln b/src/umbraco.sln index 1c4226f9db..34a186aca1 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -15,6 +15,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{2849E9D4 ..\linting\stylecop.json = ..\linting\stylecop.json ..\linting\codeanalysis.ruleset = ..\linting\codeanalysis.ruleset ..\linting\codeanalysis.tests.ruleset = ..\linting\codeanalysis.tests.ruleset + ..\Directory.Build.props = ..\Directory.Build.props + ..\Directory.Build.targets = ..\Directory.Build.targets EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{FD962632-184C-4005-A5F3-E705D92FC645}" From 88c1259d845eca2c1eaad150ec0a5cc93c35d16d Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 27 Nov 2020 13:33:01 +0100 Subject: [PATCH 11/37] Revert "Revert "Moves some files, adds notes, starts poc for back office login providers"" Signed-off-by: Bjarke Berg --- .../Controllers/AuthenticationController.cs | 19 +++- .../Controllers/BackOfficeController.cs | 11 +- .../Controllers/BackOfficeServerVariables.cs | 4 +- .../Controllers/UsersController.cs | 1 - .../BackOfficeServiceCollectionExtensions.cs | 2 +- .../HtmlHelperBackOfficeExtensions.cs | 4 +- .../Extensions/UmbracoBuilderExtensions.cs | 8 ++ .../DenyLocalLoginAuthorizationAttribute.cs | 2 +- .../BackOfficeExternalLoginProviderOptions.cs | 33 +++++- .../Security/BackOfficeSignInManager.cs | 4 +- .../Security/ExternalSignInAutoLinkOptions.cs | 9 +- .../IBackOfficeExternalLoginProviders.cs | 106 ++++++++++++++++++ .../Security/BackofficeSecurity.cs | 1 + .../Security/BackofficeSecurityFactory.cs | 2 + .../IBackOfficeExternalLoginProviders.cs | 61 ---------- .../UmbracoBackOffice/AuthorizeUpgrade.cshtml | 2 +- .../umbraco/UmbracoBackOffice/Default.cshtml | 2 +- 17 files changed, 180 insertions(+), 91 deletions(-) rename src/{Umbraco.Web.Common => Umbraco.Web.BackOffice}/Security/BackOfficeExternalLoginProviderOptions.cs (57%) rename src/{Umbraco.Web.Common => Umbraco.Web.BackOffice}/Security/BackOfficeSignInManager.cs (99%) rename src/{Umbraco.Web.Common => Umbraco.Web.BackOffice}/Security/ExternalSignInAutoLinkOptions.cs (89%) create mode 100644 src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs delete mode 100644 src/Umbraco.Web.Common/Security/IBackOfficeExternalLoginProviders.cs diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 1b414279a4..8189641c3a 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -21,6 +21,7 @@ using Umbraco.Core.Services; using Umbraco.Extensions; using Umbraco.Net; using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.BackOffice.Security; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Controllers; @@ -163,7 +164,6 @@ namespace Umbraco.Web.BackOffice.Controllers var user = await _userManager.FindByIdAsync(User.Identity.GetUserId()); if (user == null) throw new InvalidOperationException("Could not find user"); - ExternalSignInAutoLinkOptions autoLinkOptions = null; var authType = (await _signInManager.GetExternalAuthenticationSchemesAsync()) .FirstOrDefault(x => x.Name == unlinkLoginModel.LoginProvider); @@ -173,11 +173,18 @@ namespace Umbraco.Web.BackOffice.Controllers } else { - autoLinkOptions = _externalAuthenticationOptions.Get(authType.Name); - if (!autoLinkOptions.AllowManualLinking) + var opt = _externalAuthenticationOptions.Get(authType.Name); + if (opt == null) { - // If AllowManualLinking is disabled for this provider we cannot unlink - return BadRequest(); + return BadRequest($"Could not find external authentication options registered for provider {unlinkLoginModel.LoginProvider}"); + } + else + { + if (!opt.AutoLinkOptions.AllowManualLinking) + { + // If AllowManualLinking is disabled for this provider we cannot unlink + return BadRequest(); + } } } @@ -243,7 +250,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [UmbracoBackOfficeAuthorize] [SetAngularAntiForgeryTokens] - //[CheckIfUserTicketDataIsStale] // TODO: Migrate this, though it will need to be done differently at the cookie auth level + [CheckIfUserTicketDataIsStale] public UserDetail GetCurrentUser() { var user = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser; diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index d2e7f0d36c..2a0bba8c1f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -34,10 +34,11 @@ using Microsoft.AspNetCore.Identity; using System.Security.Claims; using Microsoft.AspNetCore.Http; using Umbraco.Web.Security; +using Umbraco.Web.BackOffice.Security; namespace Umbraco.Web.BackOffice.Controllers { - [DisableBrowserCache] //TODO Reintroduce + [DisableBrowserCache] //[UmbracoRequireHttps] //TODO Reintroduce [PluginController(Constants.Web.Mvc.BackOfficeArea)] public class BackOfficeController : UmbracoController @@ -413,7 +414,7 @@ namespace Umbraco.Web.BackOffice.Controllers } else { - autoLinkOptions = _externalLogins.Get(authType.Name); + autoLinkOptions = _externalLogins.Get(authType.Name)?.AutoLinkOptions; } // Sign in the user with this external login provider if the user already has a login @@ -460,7 +461,9 @@ namespace Umbraco.Web.BackOffice.Controllers return false; if (autoLinkOptions.AutoLinkExternalAccount == false) + { return true; // TODO: This seems weird to return true, but it was like that before so must be a reason? + } var email = loginInfo.Principal.FindFirstValue(ClaimTypes.Email); @@ -578,10 +581,6 @@ namespace Umbraco.Web.BackOffice.Controllers } } - // Used for XSRF protection when adding external logins - // TODO: This is duplicated in BackOfficeSignInManager - private const string XsrfKey = "XsrfId"; - private IActionResult RedirectToLocal(string returnUrl) { if (Url.IsLocalUrl(returnUrl)) diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index fd42a2b9b8..2fa3f62cb8 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -17,9 +17,9 @@ using Umbraco.Web.BackOffice.HealthCheck; using Umbraco.Web.BackOffice.Profiling; using Umbraco.Web.BackOffice.PropertyEditors; using Umbraco.Web.BackOffice.Routing; +using Umbraco.Web.BackOffice.Security; using Umbraco.Web.BackOffice.Trees; using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Common.Security; using Umbraco.Web.Features; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Trees; @@ -422,7 +422,7 @@ namespace Umbraco.Web.BackOffice.Controllers .Select(p => new { authType = p.AuthenticationType, caption = p.Name, - properties = p.Properties + properties = p.Options }) .ToArray() } diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index f187ec22ce..8c5003467b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -41,7 +41,6 @@ using IUser = Umbraco.Core.Models.Membership.IUser; using Task = System.Threading.Tasks.Task; using Umbraco.Net; using Umbraco.Web.Common.ActionsResults; -using Umbraco.Web.Common.Security; namespace Umbraco.Web.BackOffice.Controllers { diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs index 5b78179f4c..8f9cfa975e 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs @@ -62,7 +62,7 @@ namespace Umbraco.Extensions services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); - services.TryAddSingleton(); + services.TryAddSingleton(); /* * IdentityBuilderExtensions.AddUserManager adds UserManager to service collection diff --git a/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs index 9828931198..555ed5bb90 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs @@ -13,13 +13,13 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Hosting; using Umbraco.Core.WebAssets; using Umbraco.Web.BackOffice.Controllers; -using Umbraco.Web.Common.Security; using Umbraco.Web.Features; using Umbraco.Web.Models; using Umbraco.Web.WebApi; using Umbraco.Web.WebAssets; using Umbraco.Core; using Umbraco.Core.Security; +using Umbraco.Web.BackOffice.Security; namespace Umbraco.Extensions { @@ -75,7 +75,7 @@ namespace Umbraco.Extensions { authType = p.AuthenticationType, caption = p.Name, - properties = p.Properties + properties = p.Options }) .ToArray(); diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs index 76fd4a46f7..75d53c91de 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs @@ -31,6 +31,14 @@ namespace Umbraco.Extensions { builder.Services.AddAntiforgery(); builder.Services.AddSingleton(); + + // TODO: We need to see if we are 'allowed' to do this, the docs say: + // "The call to AddIdentity configures the default scheme settings. The AddAuthentication(String) overload sets the DefaultScheme property. The AddAuthentication(Action) overload allows configuring authentication options, which can be used to set up default authentication schemes for different purposes. Subsequent calls to AddAuthentication override previously configured AuthenticationOptions properties." + // So if someone calls services.AddAuthentication() ... in Startup does that overwrite all of this? + // It also says "When the app requires multiple providers, chain the provider extension methods behind AddAuthentication" + // Which leads me to believe it all gets overwritten? :/ + // UPDATE: I have tested this breifly in Startup doing Services.AddAuthentication().AddGoogle() ... and the back office auth + // still seems to work. We'll see how it goes i guess. builder.Services .AddAuthentication(Core.Constants.Security.BackOfficeAuthenticationType) .AddCookie(Core.Constants.Security.BackOfficeAuthenticationType) diff --git a/src/Umbraco.Web.BackOffice/Filters/DenyLocalLoginAuthorizationAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/DenyLocalLoginAuthorizationAttribute.cs index a5d22d702d..36247dd91e 100644 --- a/src/Umbraco.Web.BackOffice/Filters/DenyLocalLoginAuthorizationAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/DenyLocalLoginAuthorizationAttribute.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; -using Umbraco.Web.Common.Security; +using Umbraco.Web.BackOffice.Security; namespace Umbraco.Web.Editors.Filters { diff --git a/src/Umbraco.Web.Common/Security/BackOfficeExternalLoginProviderOptions.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviderOptions.cs similarity index 57% rename from src/Umbraco.Web.Common/Security/BackOfficeExternalLoginProviderOptions.cs rename to src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviderOptions.cs index de16d0ec14..58ec79e51b 100644 --- a/src/Umbraco.Web.Common/Security/BackOfficeExternalLoginProviderOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviderOptions.cs @@ -1,23 +1,46 @@ using System; using System.Runtime.Serialization; -namespace Umbraco.Web.Common.Security +namespace Umbraco.Web.BackOffice.Security { + // TODO: This is only for the back office, does it need to be in common? + /// /// Options used to configure back office external login providers /// public class BackOfficeExternalLoginProviderOptions { + public BackOfficeExternalLoginProviderOptions( + string style, string icon, string callbackPath, + ExternalSignInAutoLinkOptions autoLinkOptions = null, + bool denyLocalLogin = false, + bool autoRedirectLoginToExternalProvider = false, + string customBackOfficeView = null) + { + Style = style; + Icon = icon; + CallbackPath = callbackPath; + AutoLinkOptions = autoLinkOptions ?? new ExternalSignInAutoLinkOptions(); + DenyLocalLogin = denyLocalLogin; + AutoRedirectLoginToExternalProvider = autoRedirectLoginToExternalProvider; + CustomBackOfficeView = customBackOfficeView; + } + + public string Style { get; } + public string Icon { get; } + public string CallbackPath { get; } + + /// /// Options used to control how users can be auto-linked/created/updated based on the external login provider /// - public ExternalSignInAutoLinkOptions AutoLinkOptions { get; set; } = new ExternalSignInAutoLinkOptions(); + public ExternalSignInAutoLinkOptions AutoLinkOptions { get; } /// /// When set to true will disable all local user login functionality /// - public bool DenyLocalLogin { get; set; } + public bool DenyLocalLogin { get; } /// /// When specified this will automatically redirect to the OAuth login provider instead of prompting the user to click on the OAuth button first. @@ -26,7 +49,7 @@ namespace Umbraco.Web.Common.Security /// This is generally used in conjunction with . If more than one OAuth provider specifies this, the last registered /// provider's redirect settings will win. /// - public bool AutoRedirectLoginToExternalProvider { get; set; } + public bool AutoRedirectLoginToExternalProvider { get; } /// /// A virtual path to a custom angular view that is used to replace the entire UI that renders the external login button that the user interacts with @@ -35,6 +58,6 @@ namespace Umbraco.Web.Common.Security /// If this view is specified it is 100% up to the user to render the html responsible for rendering the link/un-link buttons along with showing any errors /// that occur. This overrides what Umbraco normally does by default. /// - public string CustomBackOfficeView { get; set; } + public string CustomBackOfficeView { get; } } } diff --git a/src/Umbraco.Web.Common/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs similarity index 99% rename from src/Umbraco.Web.Common/Security/BackOfficeSignInManager.cs rename to src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs index bef94c0ada..0cbcd85923 100644 --- a/src/Umbraco.Web.Common/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs @@ -14,6 +14,8 @@ using Umbraco.Extensions; namespace Umbraco.Web.Common.Security { + // TODO: This is only for the back office, does it need to be in common? + using Constants = Umbraco.Core.Constants; // TODO: There's potential to extract an interface for this for only what we use and put that in Core without aspnetcore refs, but we need to wait till were done with it since there's a bit to implement @@ -23,7 +25,7 @@ namespace Umbraco.Web.Common.Security // borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs private const string LoginProviderKey = "LoginProvider"; // borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs - private const string XsrfKey = "XsrfId"; // TODO: See BackOfficeController.XsrfKey + private const string XsrfKey = "XsrfId"; private BackOfficeUserManager _userManager; diff --git a/src/Umbraco.Web.Common/Security/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs similarity index 89% rename from src/Umbraco.Web.Common/Security/ExternalSignInAutoLinkOptions.cs rename to src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs index 0a81a503dd..2098d90773 100644 --- a/src/Umbraco.Web.Common/Security/ExternalSignInAutoLinkOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs @@ -5,8 +5,9 @@ using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration.Models; using SecurityConstants = Umbraco.Core.Constants.Security; -namespace Umbraco.Web.Common.Security +namespace Umbraco.Web.BackOffice.Security { + // TODO: This is only for the back office, does it need to be in common? /// /// Options used to configure auto-linking external OAuth providers @@ -22,10 +23,12 @@ namespace Umbraco.Web.Common.Security public ExternalSignInAutoLinkOptions( bool autoLinkExternalAccount = false, string[] defaultUserGroups = null, - string defaultCulture = null) + string defaultCulture = null, + bool allowManualLinking = true) { DefaultUserGroups = defaultUserGroups ?? new[] { SecurityConstants.EditorGroupAlias }; AutoLinkExternalAccount = autoLinkExternalAccount; + AllowManualLinking = allowManualLinking; _defaultCulture = defaultCulture; } @@ -33,7 +36,7 @@ namespace Umbraco.Web.Common.Security /// By default this is true which allows the user to manually link and unlink the external provider, if set to false the back office user /// will not see and cannot perform manual linking or unlinking of the external provider. /// - public bool AllowManualLinking { get; set; } = true; + public bool AllowManualLinking { get; } /// /// A callback executed during account auto-linking and before the user is persisted diff --git a/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs new file mode 100644 index 0000000000..b631227470 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs @@ -0,0 +1,106 @@ +using Microsoft.AspNetCore.Authentication.OAuth; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; + + +namespace Umbraco.Web.BackOffice.Security +{ + // TODO: This is only for the back office, does it need to be in common? + + // TODO: We need to implement this and extend it to support the back office external login options + // basically migrate things from AuthenticationManagerExtensions & AuthenticationOptionsExtensions + // and use this to get the back office external login infos + public interface IBackOfficeExternalLoginProviders + { + /// + /// Register a login provider for the back office + /// + /// + void Register(BackOfficeExternalLoginProvider provider); + + BackOfficeExternalLoginProviderOptions Get(string authenticationType); + + IEnumerable GetBackOfficeProviders(); + + /// + /// Returns the authentication type for the last registered external login (oauth) provider that specifies an auto-login redirect option + /// + /// + /// + string GetAutoLoginProvider(); + + bool HasDenyLocalLogin(); + } + + // TODO: This class is just a placeholder for later + public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProviders + { + private ConcurrentDictionary _providers = new ConcurrentDictionary(); + + public void Register(BackOfficeExternalLoginProvider provider) + { + _providers.TryAdd(provider.AuthenticationType, provider); + + // TODO: we need to be able to set things like we were doing in ForUmbracoBackOffice. + // Ok, most is done but we'll also need to take into account the callback path to ignore when we + // do front-end routing + } + + public BackOfficeExternalLoginProviderOptions Get(string authenticationType) + { + return _providers.TryGetValue(authenticationType, out var opt) ? opt.Options : null; + } + + public string GetAutoLoginProvider() + { + var found = _providers.Where(x => x.Value.Options.AutoRedirectLoginToExternalProvider).ToList(); + return found.Count > 0 ? found[0].Key : null; + } + + public IEnumerable GetBackOfficeProviders() + { + return _providers.Values; + } + + public bool HasDenyLocalLogin() + { + var found = _providers.Where(x => x.Value.Options.DenyLocalLogin).ToList(); + return found.Count > 0; + } + } + + public class BackOfficeExternalLoginProvider : IEquatable + { + public BackOfficeExternalLoginProvider(string name, string authenticationType, BackOfficeExternalLoginProviderOptions properties) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + AuthenticationType = authenticationType ?? throw new ArgumentNullException(nameof(authenticationType)); + Options = properties ?? throw new ArgumentNullException(nameof(properties)); + } + + public string Name { get; } + public string AuthenticationType { get; } + public BackOfficeExternalLoginProviderOptions Options { get; } + + public override bool Equals(object obj) + { + return Equals(obj as BackOfficeExternalLoginProvider); + } + + public bool Equals(BackOfficeExternalLoginProvider other) + { + return other != null && + Name == other.Name && + AuthenticationType == other.AuthenticationType; + } + + public override int GetHashCode() + { + return HashCode.Combine(Name, AuthenticationType); + } + } + +} diff --git a/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs b/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs index dc312ed9ca..23ff63a328 100644 --- a/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs +++ b/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs @@ -13,6 +13,7 @@ using Umbraco.Extensions; namespace Umbraco.Web.Common.Security { + // TODO: This is only for the back office, does it need to be in common? public class BackOfficeSecurity : IBackOfficeSecurity { diff --git a/src/Umbraco.Web.Common/Security/BackofficeSecurityFactory.cs b/src/Umbraco.Web.Common/Security/BackofficeSecurityFactory.cs index 0a3c362971..d212f5a1e3 100644 --- a/src/Umbraco.Web.Common/Security/BackofficeSecurityFactory.cs +++ b/src/Umbraco.Web.Common/Security/BackofficeSecurityFactory.cs @@ -9,6 +9,8 @@ using Umbraco.Core.Services; namespace Umbraco.Web.Common.Security { + // TODO: This is only for the back office, does it need to be in common? + public class BackOfficeSecurityFactory: IBackOfficeSecurityFactory { private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; diff --git a/src/Umbraco.Web.Common/Security/IBackOfficeExternalLoginProviders.cs b/src/Umbraco.Web.Common/Security/IBackOfficeExternalLoginProviders.cs deleted file mode 100644 index 85dbf95272..0000000000 --- a/src/Umbraco.Web.Common/Security/IBackOfficeExternalLoginProviders.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Web.Common.Security -{ - // TODO: We need to implement this and extend it to support the back office external login options - // basically migrate things from AuthenticationManagerExtensions & AuthenticationOptionsExtensions - // and use this to get the back office external login infos - public interface IBackOfficeExternalLoginProviders - { - ExternalSignInAutoLinkOptions Get(string authenticationType); - - IEnumerable GetBackOfficeProviders(); - - /// - /// Returns the authentication type for the last registered external login (oauth) provider that specifies an auto-login redirect option - /// - /// - /// - string GetAutoLoginProvider(); - - bool HasDenyLocalLogin(); - } - - // TODO: This class is just a placeholder for later - public class NopBackOfficeExternalLoginProviders : IBackOfficeExternalLoginProviders - { - public ExternalSignInAutoLinkOptions Get(string authenticationType) - { - return null; - } - - public string GetAutoLoginProvider() - { - return null; - } - - public IEnumerable GetBackOfficeProviders() - { - return Enumerable.Empty(); - } - - public bool HasDenyLocalLogin() - { - return false; - } - } - - // TODO: we'll need to register these somehow - public class BackOfficeExternalLoginProvider - { - public string Name { get; set; } - public string AuthenticationType { get; set; } - - // TODO: I believe this should be replaced with just a reference to BackOfficeExternalLoginProviderOptions - public IReadOnlyDictionary Properties { get; set; } - } - -} diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/AuthorizeUpgrade.cshtml b/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/AuthorizeUpgrade.cshtml index 3cf342e6e0..40ea7dba90 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/AuthorizeUpgrade.cshtml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/AuthorizeUpgrade.cshtml @@ -1,7 +1,7 @@ @using Microsoft.Extensions.Options; @using Umbraco.Core @using Umbraco.Web.WebAssets -@using Umbraco.Web.Common.Security +@using Umbraco.Web.BackOffice.Security @using Umbraco.Core.WebAssets @using Umbraco.Core.Configuration @using Umbraco.Core.Configuration.Models diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Default.cshtml b/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Default.cshtml index ec21d810e9..c36d7dbf0f 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Default.cshtml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/Default.cshtml @@ -1,7 +1,7 @@ @using Microsoft.Extensions.Options; @using Umbraco.Core @using Umbraco.Web.WebAssets -@using Umbraco.Web.Common.Security +@using Umbraco.Web.BackOffice.Security @using Umbraco.Core.WebAssets @using Umbraco.Core.Configuration @using Umbraco.Core.Configuration.Models From d6357e8fde738af42e82f893a7de1e288e7ee158 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 27 Nov 2020 13:34:32 +0100 Subject: [PATCH 12/37] Revert "Revert "Gets oauth working (with google) now need to test others and debug why the styles aren't working"" Signed-off-by: Bjarke Berg --- .../Controllers/AuthenticationController.cs | 2 +- .../Controllers/BackOfficeController.cs | 6 +- .../Controllers/BackOfficeServerVariables.cs | 13 +- .../BackOfficeExternalLoginProviderOptions.cs | 8 +- .../Security/BackOfficeSignInManager.cs | 1 - .../Security/ExternalSignInAutoLinkOptions.cs | 2 - .../IBackOfficeExternalLoginProviders.cs | 131 ++++++++++++++---- .../services/externallogininfo.service.js | 2 +- .../src/views/common/overlays/user/user.html | 6 +- .../components/application/umb-login.html | 2 +- 10 files changed, 126 insertions(+), 47 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 8189641c3a..9d76e58982 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -180,7 +180,7 @@ namespace Umbraco.Web.BackOffice.Controllers } else { - if (!opt.AutoLinkOptions.AllowManualLinking) + if (!opt.Options.AutoLinkOptions.AllowManualLinking) { // If AllowManualLinking is disabled for this provider we cannot unlink return BadRequest(); diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index 2a0bba8c1f..c905876f51 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -414,7 +414,7 @@ namespace Umbraco.Web.BackOffice.Controllers } else { - autoLinkOptions = _externalLogins.Get(authType.Name)?.AutoLinkOptions; + autoLinkOptions = _externalLogins.Get(authType.Name)?.Options?.AutoLinkOptions; } // Sign in the user with this external login provider if the user already has a login @@ -445,7 +445,7 @@ namespace Umbraco.Web.BackOffice.Controllers ViewData.SetExternalSignInProviderErrors( new BackOfficeExternalLoginProviderErrors( loginInfo.LoginProvider, - new[] { "The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account" })); + new[] { "The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account, the provider must be linked from the back office." })); } //Remove the cookie otherwise this message will keep appearing @@ -462,7 +462,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (autoLinkOptions.AutoLinkExternalAccount == false) { - return true; // TODO: This seems weird to return true, but it was like that before so must be a reason? + return false; } var email = loginInfo.Principal.FindFirstValue(ClaimTypes.Email); diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index 2fa3f62cb8..470e62c2c2 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -135,7 +135,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// Returns the server variables for authenticated users /// /// - internal async Task> GetServerVariablesAsync() + internal Task> GetServerVariablesAsync() { var globalSettings = _globalSettings; var backOfficeControllerName = ControllerExtensions.GetControllerName(); @@ -149,8 +149,8 @@ namespace Umbraco.Web.BackOffice.Controllers // having each url defined here explicitly - we can do that in v8! for now // for umbraco services we'll stick to explicitly defining the endpoints. - // {"externalLoginsUrl", _linkGenerator.GetPathByAction(nameof(BackOfficeController.ExternalLogin), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })}, - // {"externalLinkLoginsUrl", _linkGenerator.GetPathByAction(nameof(BackOfficeController.LinkLogin), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })}, + {"externalLoginsUrl", _linkGenerator.GetPathByAction(nameof(BackOfficeController.ExternalLogin), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })}, + {"externalLinkLoginsUrl", _linkGenerator.GetPathByAction(nameof(BackOfficeController.LinkLogin), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })}, {"gridConfig", _linkGenerator.GetPathByAction(nameof(BackOfficeController.GetGridConfig), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })}, // TODO: This is ultra confusing! this same key is used for different things, when returning the full app when authenticated it is this URL but when not auth'd it's actually the ServerVariables address {"serverVarsJs", _linkGenerator.GetPathByAction(nameof(BackOfficeController.Application), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })}, @@ -418,10 +418,13 @@ namespace Umbraco.Web.BackOffice.Controllers "externalLogins", new Dictionary { { + // TODO: It would be nicer to not have to manually translate these properties + // but then needs to be changed in quite a few places in angular "providers", _externalLogins.GetBackOfficeProviders() .Select(p => new { - authType = p.AuthenticationType, caption = p.Name, + authType = p.AuthenticationType, + caption = p.Name, properties = p.Options }) .ToArray() @@ -441,7 +444,7 @@ namespace Umbraco.Web.BackOffice.Controllers } } }; - return defaultVals; + return Task.FromResult(defaultVals); } [DataContract] diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviderOptions.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviderOptions.cs index 58ec79e51b..a3ce87e404 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviderOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviderOptions.cs @@ -4,7 +4,6 @@ using System.Runtime.Serialization; namespace Umbraco.Web.BackOffice.Security { - // TODO: This is only for the back office, does it need to be in common? /// /// Options used to configure back office external login providers @@ -12,13 +11,13 @@ namespace Umbraco.Web.BackOffice.Security public class BackOfficeExternalLoginProviderOptions { public BackOfficeExternalLoginProviderOptions( - string style, string icon, string callbackPath, + string buttonStyle, string icon, string callbackPath, ExternalSignInAutoLinkOptions autoLinkOptions = null, bool denyLocalLogin = false, bool autoRedirectLoginToExternalProvider = false, string customBackOfficeView = null) { - Style = style; + ButtonStyle = buttonStyle; Icon = icon; CallbackPath = callbackPath; AutoLinkOptions = autoLinkOptions ?? new ExternalSignInAutoLinkOptions(); @@ -27,11 +26,10 @@ namespace Umbraco.Web.BackOffice.Security CustomBackOfficeView = customBackOfficeView; } - public string Style { get; } + public string ButtonStyle { get; } public string Icon { get; } public string CallbackPath { get; } - /// /// Options used to control how users can be auto-linked/created/updated based on the external login provider /// diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs index 0cbcd85923..6f34a85c79 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs @@ -14,7 +14,6 @@ using Umbraco.Extensions; namespace Umbraco.Web.Common.Security { - // TODO: This is only for the back office, does it need to be in common? using Constants = Umbraco.Core.Constants; diff --git a/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs index 2098d90773..8d9f57945b 100644 --- a/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ExternalSignInAutoLinkOptions.cs @@ -7,8 +7,6 @@ using SecurityConstants = Umbraco.Core.Constants.Security; namespace Umbraco.Web.BackOffice.Security { - // TODO: This is only for the back office, does it need to be in common? - /// /// Options used to configure auto-linking external OAuth providers /// diff --git a/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs index b631227470..6d0b64e84f 100644 --- a/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs +++ b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs @@ -1,27 +1,109 @@ -using Microsoft.AspNetCore.Authentication.OAuth; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.OAuth; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; - +using Umbraco.Core; +using Umbraco.Core.Builder; namespace Umbraco.Web.BackOffice.Security { - // TODO: This is only for the back office, does it need to be in common? + /// + /// Custom used to associate external logins with umbraco external login options + /// + public class BackOfficeAuthenticationBuilder : AuthenticationBuilder + { + private readonly BackOfficeExternalLoginProviderOptions _loginProviderOptions; + + public BackOfficeAuthenticationBuilder(IServiceCollection services, BackOfficeExternalLoginProviderOptions loginProviderOptions) + : base(services) + { + _loginProviderOptions = loginProviderOptions; + } + + /// + /// Overridden to track the final authenticationScheme being registered for the external login + /// + /// + /// + /// + /// + /// + /// + public override AuthenticationBuilder AddRemoteScheme(string authenticationScheme, string displayName, Action configureOptions) + { + //Ensure the prefix is set + if (!authenticationScheme.StartsWith(Constants.Security.BackOfficeExternalAuthenticationTypePrefix)) + { + authenticationScheme = Constants.Security.BackOfficeExternalAuthenticationTypePrefix + authenticationScheme; + } + + // add our login provider to the container along with a custom options configuration + Services.AddSingleton(x => new BackOfficeExternalLoginProvider(displayName, authenticationScheme, _loginProviderOptions)); + Services.TryAddEnumerable(ServiceDescriptor.Singleton, EnsureBackOfficeScheme>()); + + return base.AddRemoteScheme(authenticationScheme, displayName, configureOptions); + } + + // TODO: We could override and throw NotImplementedException for other methods? + + // Ensures that the sign in scheme is always the Umbraco back office external type + private class EnsureBackOfficeScheme : IPostConfigureOptions where TOptions : RemoteAuthenticationOptions + { + public void PostConfigure(string name, TOptions options) + { + options.SignInScheme = Constants.Security.BackOfficeExternalAuthenticationType; + } + } + } + + /// + /// Used to add back office login providers + /// + public class BackOfficeExternalLoginsBuilder + { + public BackOfficeExternalLoginsBuilder(IServiceCollection services) + { + _services = services; + } + + private readonly IServiceCollection _services; + + /// + /// Add a back office login provider with options + /// + /// + /// + /// + public BackOfficeExternalLoginsBuilder AddBackOfficeLogin( + BackOfficeExternalLoginProviderOptions loginProviderOptions, + Action build) + { + build(new BackOfficeAuthenticationBuilder(_services, loginProviderOptions)); + return this; + } + } + + public static class AuthenticationBuilderExtensions + { + public static IUmbracoBuilder AddBackOfficeExternalLogins(this IUmbracoBuilder umbracoBuilder, Action builder) + { + builder(new BackOfficeExternalLoginsBuilder(umbracoBuilder.Services)); + return umbracoBuilder; + } + } // TODO: We need to implement this and extend it to support the back office external login options // basically migrate things from AuthenticationManagerExtensions & AuthenticationOptionsExtensions // and use this to get the back office external login infos public interface IBackOfficeExternalLoginProviders { - /// - /// Register a login provider for the back office - /// - /// - void Register(BackOfficeExternalLoginProvider provider); - - BackOfficeExternalLoginProviderOptions Get(string authenticationType); + BackOfficeExternalLoginProvider Get(string authenticationType); IEnumerable GetBackOfficeProviders(); @@ -32,42 +114,41 @@ namespace Umbraco.Web.BackOffice.Security /// string GetAutoLoginProvider(); + /// + /// Returns true if there is any external provider that has the Deny Local Login option configured + /// + /// bool HasDenyLocalLogin(); } - // TODO: This class is just a placeholder for later public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProviders { - private ConcurrentDictionary _providers = new ConcurrentDictionary(); - - public void Register(BackOfficeExternalLoginProvider provider) + public BackOfficeExternalLoginProviders(IEnumerable externalLogins) { - _providers.TryAdd(provider.AuthenticationType, provider); - - // TODO: we need to be able to set things like we were doing in ForUmbracoBackOffice. - // Ok, most is done but we'll also need to take into account the callback path to ignore when we - // do front-end routing + _externalLogins = externalLogins; } - public BackOfficeExternalLoginProviderOptions Get(string authenticationType) + private readonly IEnumerable _externalLogins; + + public BackOfficeExternalLoginProvider Get(string authenticationType) { - return _providers.TryGetValue(authenticationType, out var opt) ? opt.Options : null; + return _externalLogins.FirstOrDefault(x => x.AuthenticationType == authenticationType); } public string GetAutoLoginProvider() { - var found = _providers.Where(x => x.Value.Options.AutoRedirectLoginToExternalProvider).ToList(); - return found.Count > 0 ? found[0].Key : null; + var found = _externalLogins.Where(x => x.Options.AutoRedirectLoginToExternalProvider).ToList(); + return found.Count > 0 ? found[0].AuthenticationType : null; } public IEnumerable GetBackOfficeProviders() { - return _providers.Values; + return _externalLogins; } public bool HasDenyLocalLogin() { - var found = _providers.Where(x => x.Value.Options.DenyLocalLogin).ToList(); + var found = _externalLogins.Where(x => x.Options.DenyLocalLogin).ToList(); return found.Count > 0; } } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js b/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js index 31914f4e58..f1dbb0f651 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js @@ -50,7 +50,7 @@ function externalLoginInfoService(externalLoginInfo, umbRequestHelper) { return true; } else { - return x.properties.ExternalSignInAutoLinkOptions.AllowManualLinking; + return x.properties.AutoLinkOptions.AllowManualLinking; } }); return providers; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html index fdd2671200..330a57ab7d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html @@ -52,11 +52,11 @@
-
+
/// [HttpGet] + // TODO: We need to move this since we are going to delete OverrideAuthorization [UmbracoBackOfficeAuthorize, OverrideAuthorization] public bool AllowsCultureVariation() { diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs index 9d18727c62..8a5fc47a58 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs @@ -5,14 +5,12 @@ using System.Linq; using System.Net; using System.Net.Mime; using System.Text; -using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using Microsoft.Extensions.Logging; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Dictionary; using Umbraco.Core.Hosting; using Umbraco.Core.IO; @@ -31,7 +29,6 @@ using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Editors; -using Umbraco.Web.Security; using ContentType = Umbraco.Core.Models.ContentType; using Umbraco.Core.Configuration.Models; using Microsoft.Extensions.Options; @@ -39,18 +36,15 @@ using Umbraco.Core.Serialization; namespace Umbraco.Web.BackOffice.Controllers { - // TODO: We'll need to be careful about the security on this controller, when we start implementing - // methods to modify content types we'll need to enforce security on the individual methods, we - // cannot put security on the whole controller because things like - // GetAllowedChildren, GetPropertyTypeScaffold, GetAllPropertyTypeAliases are required for content editing. - /// /// An API controller used for dealing with content types /// - [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] - [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] + [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] public class ContentTypeController : ContentTypeControllerBase { + // TODO: Split this controller apart so that authz is consistent, currently we need to authz each action individually. + // It would be possible to have something like a ContentTypeInfoController for the GetAllPropertyTypeAliases/GetCount/GetAllowedChildren/etc... actions + private readonly IEntityXmlSerializer _serializer; private readonly GlobalSettings _globalSettings; private readonly PropertyEditorCollection _propertyEditors; @@ -130,6 +124,7 @@ namespace Umbraco.Web.BackOffice.Controllers _jsonSerializer = jsonSerializer; } + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public int GetCount() { return _contentTypeService.Count(); @@ -148,6 +143,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public DocumentTypeDisplay GetById(int id) { var ct = _contentTypeService.Get(id); @@ -166,6 +162,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public DocumentTypeDisplay GetById(Guid id) { var contentType = _contentTypeService.Get(id); @@ -184,6 +181,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public DocumentTypeDisplay GetById(Udi id) { var guidUdi = id as GuidUdi; @@ -207,6 +205,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [HttpDelete] [HttpPost] + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public IActionResult DeleteById(int id) { var foundType = _contentTypeService.Get(id); @@ -253,6 +252,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [HttpPost] + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public IActionResult GetAvailableCompositeContentTypes(GetAvailableCompositionsFilter filter) { var result = PerformGetAvailableCompositeContentTypes(filter.ContentTypeId, UmbracoObjectTypes.DocumentType, filter.FilterContentTypes, filter.FilterPropertyTypes, filter.IsElement) @@ -270,6 +270,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [HttpPost] + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public IActionResult GetWhereCompositionIsUsedInContentTypes(GetAvailableCompositionsFilter filter) { var result = PerformGetWhereCompositionIsUsedInContentTypes(filter.ContentTypeId, UmbracoObjectTypes.DocumentType) @@ -312,6 +313,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [HttpDelete] [HttpPost] + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public IActionResult DeleteContainer(int id) { _contentTypeService.DeleteContainer(id, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); @@ -319,6 +321,7 @@ namespace Umbraco.Web.BackOffice.Controllers return Ok(); } + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public IActionResult PostCreateContainer(int parentId, string name) { var result = _contentTypeService.CreateContainer(parentId, name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); @@ -328,6 +331,7 @@ namespace Umbraco.Web.BackOffice.Controllers : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); } + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public IActionResult PostRenameContainer(int id, string name) { var result = _contentTypeService.RenameContainer(id, name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); @@ -337,6 +341,7 @@ namespace Umbraco.Web.BackOffice.Controllers : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); } + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public CreatedContentTypeCollectionResult PostCreateCollection(int parentId, string collectionName, bool collectionCreateTemplate, string collectionItemName, bool collectionItemCreateTemplate, string collectionIcon, string collectionItemIcon) { // create item doctype @@ -393,6 +398,7 @@ namespace Umbraco.Web.BackOffice.Controllers }; } + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public DocumentTypeDisplay PostSave(DocumentTypeSave contentTypeSave) { //Before we send this model into this saving/mapping pipeline, we need to do some cleanup on variations. @@ -445,6 +451,7 @@ namespace Umbraco.Web.BackOffice.Controllers return display; } + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public ActionResult PostCreateDefaultTemplate(int id) { var contentType = _contentTypeService.Get(id); @@ -485,6 +492,7 @@ namespace Umbraco.Web.BackOffice.Controllers ///
/// /// + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public DocumentTypeDisplay GetEmpty(int parentId) { IContentType ct; @@ -506,6 +514,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Returns all content type objects /// + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public IEnumerable GetAll() { var types = _contentTypeService.GetAll(); @@ -578,6 +587,7 @@ namespace Umbraco.Web.BackOffice.Controllers ///
/// /// + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public IActionResult PostMove(MoveOrCopy move) { return PerformMove( @@ -591,6 +601,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public IActionResult PostCopy(MoveOrCopy copy) { return PerformCopy( @@ -600,6 +611,7 @@ namespace Umbraco.Web.BackOffice.Controllers } [HttpGet] + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public IActionResult Export(int id) { var contentType = _contentTypeService.Get(id); @@ -616,6 +628,7 @@ namespace Umbraco.Web.BackOffice.Controllers } [HttpPost] + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] public IActionResult Import(string file) { var filePath = Path.Combine(_ioHelper.MapPath(Core.Constants.SystemDirectories.Data), file); @@ -648,7 +661,8 @@ namespace Umbraco.Web.BackOffice.Controllers } [HttpPost] - public async Task> Upload(List file) + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] + public ActionResult Upload(List file) { var model = new ContentTypeImportModel(); diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs index 969400e213..88a83ed217 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Extensions; +using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Editors; @@ -26,7 +27,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// Am abstract API controller providing functionality used for dealing with content and media types /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] - //[PrefixlessBodyModelValidator] //TODO reintroduce + [PrefixlessBodyModelValidator] public abstract class ContentTypeControllerBase : UmbracoAuthorizedJsonController where TContentType : class, IContentTypeComposition { diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs index cd834e79fb..b682b59e2f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs @@ -15,21 +15,18 @@ using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Editors; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Security; namespace Umbraco.Web.BackOffice.Controllers { - // TODO: We'll need to be careful about the security on this controller, when we start implementing - // methods to modify content types we'll need to enforce security on the individual methods, we - // cannot put security on the whole controller because things like GetAllowedChildren are required for content editing. - /// - /// An API controller used for dealing with content types + /// An API controller used for dealing with content types /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] - [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] public class MediaTypeController : ContentTypeControllerBase { + // TODO: Split this controller apart so that authz is consistent, currently we need to authz each action individually. + // It would be possible to have something like a MediaTypeInfoController for the GetById/GetAllowedChildren/etc... actions + private readonly IContentTypeService _contentTypeService; private readonly IEntityService _entityService; private readonly ILocalizedTextService _localizedTextService; @@ -140,6 +137,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [HttpDelete] [HttpPost] + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] public IActionResult DeleteById(int id) { var foundType = _mediaTypeService.Get(id); @@ -175,6 +173,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [HttpPost] + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] public IActionResult GetAvailableCompositeMediaTypes(GetAvailableCompositionsFilter filter) { var result = PerformGetAvailableCompositeContentTypes(filter.ContentTypeId, UmbracoObjectTypes.MediaType, @@ -195,6 +194,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [HttpPost] + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] public IActionResult GetWhereCompositionIsUsedInContentTypes(GetAvailableCompositionsFilter filter) { var result = @@ -206,6 +206,7 @@ namespace Umbraco.Web.BackOffice.Controllers return Ok(result); } + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] public MediaTypeDisplay GetEmpty(int parentId) { IMediaType mt; @@ -227,19 +228,21 @@ namespace Umbraco.Web.BackOffice.Controllers /// - /// Returns all media types + /// Returns all media types /// + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] public IEnumerable GetAll() => _mediaTypeService.GetAll() .Select(_umbracoMapper.Map); /// - /// Deletes a media type container with a given ID + /// Deletes a media type container with a given ID /// /// /// [HttpDelete] [HttpPost] + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] public IActionResult DeleteContainer(int id) { _mediaTypeService.DeleteContainer(id, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); @@ -247,6 +250,7 @@ namespace Umbraco.Web.BackOffice.Controllers return Ok(); } + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] public IActionResult PostCreateContainer(int parentId, string name) { var result = _mediaTypeService.CreateContainer(parentId, name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); @@ -256,6 +260,7 @@ namespace Umbraco.Web.BackOffice.Controllers : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); } + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] public IActionResult PostRenameContainer(int id, string name) { var result = _mediaTypeService.RenameContainer(id, name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); @@ -265,6 +270,7 @@ namespace Umbraco.Web.BackOffice.Controllers : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); } + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] public MediaTypeDisplay PostSave(MediaTypeSave contentTypeSave) { var savedCt = PerformPostSave( @@ -282,10 +288,11 @@ namespace Umbraco.Web.BackOffice.Controllers } /// - /// Move the media type + /// Move the media type /// /// /// + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] public IActionResult PostMove(MoveOrCopy move) { return PerformMove( @@ -295,10 +302,11 @@ namespace Umbraco.Web.BackOffice.Controllers } /// - /// Copy the media type + /// Copy the media type /// /// /// + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] public IActionResult PostCopy(MoveOrCopy copy) { return PerformCopy( @@ -311,7 +319,7 @@ namespace Umbraco.Web.BackOffice.Controllers #region GetAllowedChildren /// - /// Returns the allowed child content type objects for the content item id passed in - based on an INT id + /// Returns the allowed child content type objects for the content item id passed in - based on an INT id /// /// [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] diff --git a/src/Umbraco.Web.BackOffice/Filters/OverrideAuthorizationAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/OverrideAuthorizationAttribute.cs index ed05d831f4..3d7b68cc80 100644 --- a/src/Umbraco.Web.BackOffice/Filters/OverrideAuthorizationAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/OverrideAuthorizationAttribute.cs @@ -3,12 +3,13 @@ using Microsoft.AspNetCore.Mvc.Filters; namespace Umbraco.Web.BackOffice.Filters { + // TODO: This should probably be deleted, anything requiring this should move to a different controller public class OverrideAuthorizationAttribute : ActionFilterAttribute { /// /// Ensures a special type of authorization filter is ignored. Defaults to . /// - /// The type of authorication filter to override. if null then is used. + /// The type of authorization filter to override. if null then is used. /// /// https://stackoverflow.com/questions/33558095/overrideauthorizationattribute-in-asp-net-5 /// diff --git a/src/Umbraco.Web.BackOffice/Filters/OverrideAuthorizationFilterProvider.cs b/src/Umbraco.Web.BackOffice/Filters/OverrideAuthorizationFilterProvider.cs index 6dbf6d747a..0593031a3c 100644 --- a/src/Umbraco.Web.BackOffice/Filters/OverrideAuthorizationFilterProvider.cs +++ b/src/Umbraco.Web.BackOffice/Filters/OverrideAuthorizationFilterProvider.cs @@ -4,6 +4,7 @@ using Umbraco.Core; namespace Umbraco.Web.BackOffice.Filters { + // TODO: This should probably be deleted, anything requiring this should move to a different controller public class OverrideAuthorizationFilterProvider : IFilterProvider, IFilterMetadata { public void OnProvidersExecuted(FilterProviderContext context) diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index 511b6da945..66f248c410 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -78,7 +78,7 @@ module.exports = { assets: "./src/assets/**" } }, - roots: ["../Umbraco.Web.UI/", "../Umbraco.Web.UI.NetCore/wwwroot/"], + root: "../Umbraco.Web.UI.NetCore/wwwroot/", targets: { js: "umbraco/js/", lib: "umbraco/lib/", diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js index 179faeb843..73d0b4e205 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js @@ -21,7 +21,7 @@ function dependencies() { const nodeModules = [ { "name": "ace-builds", - "src": [ + "src": [ "./node_modules/ace-builds/src-min-noconflict/ace.js", "./node_modules/ace-builds/src-min-noconflict/ext-language_tools.js", "./node_modules/ace-builds/src-min-noconflict/ext-searchbox.js", @@ -43,23 +43,23 @@ function dependencies() { }, { "name": "angular", - "src": ["./node_modules/angular/angular.js"], + "src": ["./node_modules/angular/angular.js"], "base": "./node_modules/angular" }, { "name": "angular-aria", - "src": ["./node_modules/angular-aria/angular-aria.min.js", - "./node_modules/angular-aria/angular-aria.min.js.map"], + "src": ["./node_modules/angular-aria/angular-aria.min.js", + "./node_modules/angular-aria/angular-aria.min.js.map"], "base": "./node_modules/angular-aria" }, { "name": "angular-cookies", - "src": ["./node_modules/angular-cookies/angular-cookies.js"], + "src": ["./node_modules/angular-cookies/angular-cookies.js"], "base": "./node_modules/angular-cookies" }, { "name": "angular-dynamic-locale", - "src": [ + "src": [ "./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js", "./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js.map" ], @@ -67,32 +67,32 @@ function dependencies() { }, { "name": "angular-sanitize", - "src": ["./node_modules/angular-sanitize/angular-sanitize.js"], + "src": ["./node_modules/angular-sanitize/angular-sanitize.js"], "base": "./node_modules/angular-sanitize" }, { "name": "angular-touch", - "src": ["./node_modules/angular-touch/angular-touch.js"], + "src": ["./node_modules/angular-touch/angular-touch.js"], "base": "./node_modules/angular-touch" }, { "name": "angular-ui-sortable", - "src": ["./node_modules/angular-ui-sortable/dist/sortable.js"], + "src": ["./node_modules/angular-ui-sortable/dist/sortable.js"], "base": "./node_modules/angular-ui-sortable/dist" }, { "name": "angular-route", - "src": ["./node_modules/angular-route/angular-route.js"], + "src": ["./node_modules/angular-route/angular-route.js"], "base": "./node_modules/angular-route" }, { "name": "angular-animate", - "src": ["./node_modules/angular-animate/angular-animate.js"], + "src": ["./node_modules/angular-animate/angular-animate.js"], "base": "./node_modules/angular-animate" }, { "name": "angular-i18n", - "src": [ + "src": [ "./node_modules/angular-i18n/angular-i18n.js", "./node_modules/angular-i18n/angular-locale_*.js" ], @@ -100,7 +100,7 @@ function dependencies() { }, { "name": "angular-local-storage", - "src": [ + "src": [ "./node_modules/angular-local-storage/dist/angular-local-storage.min.js", "./node_modules/angular-local-storage/dist/angular-local-storage.min.js.map" ], @@ -108,48 +108,48 @@ function dependencies() { }, { "name": "angular-messages", - "src": ["./node_modules/angular-messages/angular-messages.js"], + "src": ["./node_modules/angular-messages/angular-messages.js"], "base": "./node_modules/angular-messages" - }, + }, { "name": "angular-mocks", - "src": ["./node_modules/angular-mocks/angular-mocks.js"], + "src": ["./node_modules/angular-mocks/angular-mocks.js"], "base": "./node_modules/angular-mocks" }, { "name": "animejs", - "src": ["./node_modules/animejs/anime.min.js"], + "src": ["./node_modules/animejs/anime.min.js"], "base": "./node_modules/animejs" }, { "name": "bootstrap-social", - "src": ["./node_modules/bootstrap-social/bootstrap-social.css"], + "src": ["./node_modules/bootstrap-social/bootstrap-social.css"], "base": "./node_modules/bootstrap-social" }, { "name": "angular-chart.js", - "src": ["./node_modules/angular-chart.js/dist/angular-chart.min.js"], + "src": ["./node_modules/angular-chart.js/dist/angular-chart.min.js"], "base": "./node_modules/angular-chart.js/dist" }, { "name": "chart.js", - "src": ["./node_modules/chart.js/dist/Chart.min.js"], + "src": ["./node_modules/chart.js/dist/Chart.min.js"], "base": "./node_modules/chart.js/dist" }, { "name": "clipboard", - "src": ["./node_modules/clipboard/dist/clipboard.min.js"], + "src": ["./node_modules/clipboard/dist/clipboard.min.js"], "base": "./node_modules/clipboard/dist" }, { "name": "jsdiff", - "src": ["./node_modules/diff/dist/diff.min.js"], + "src": ["./node_modules/diff/dist/diff.min.js"], "base": "./node_modules/diff/dist" }, { "name": "flatpickr", - "src": [ + "src": [ "./node_modules/flatpickr/dist/flatpickr.js", "./node_modules/flatpickr/dist/flatpickr.css", "./node_modules/flatpickr/dist/l10n/*.js" @@ -158,7 +158,7 @@ function dependencies() { }, { "name": "font-awesome", - "src": [ + "src": [ "./node_modules/font-awesome/fonts/*", "./node_modules/font-awesome/css/font-awesome.min.css" ], @@ -166,7 +166,7 @@ function dependencies() { }, { "name": "jquery", - "src": [ + "src": [ "./node_modules/jquery/dist/jquery.min.js", "./node_modules/jquery/dist/jquery.min.map" ], @@ -174,37 +174,37 @@ function dependencies() { }, { "name": "jquery-ui", - "src": ["./node_modules/jquery-ui-dist/jquery-ui.min.js"], + "src": ["./node_modules/jquery-ui-dist/jquery-ui.min.js"], "base": "./node_modules/jquery-ui-dist" }, { "name": "jquery-ui-touch-punch", - "src": ["./node_modules/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js"], + "src": ["./node_modules/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js"], "base": "./node_modules/jquery-ui-touch-punch" }, { "name": "lazyload-js", - "src": ["./node_modules/lazyload-js/LazyLoad.min.js"], + "src": ["./node_modules/lazyload-js/LazyLoad.min.js"], "base": "./node_modules/lazyload-js" }, { "name": "moment", - "src": ["./node_modules/moment/min/moment.min.js"], + "src": ["./node_modules/moment/min/moment.min.js"], "base": "./node_modules/moment/min" }, { "name": "moment", - "src": ["./node_modules/moment/locale/*.js"], + "src": ["./node_modules/moment/locale/*.js"], "base": "./node_modules/moment/locale" }, { "name": "ng-file-upload", - "src": ["./node_modules/ng-file-upload/dist/ng-file-upload.min.js"], + "src": ["./node_modules/ng-file-upload/dist/ng-file-upload.min.js"], "base": "./node_modules/ng-file-upload/dist" }, { "name": "nouislider", - "src": [ + "src": [ "./node_modules/nouislider/distribute/nouislider.min.js", "./node_modules/nouislider/distribute/nouislider.min.css" ], @@ -212,14 +212,14 @@ function dependencies() { }, { "name": "signalr", - "src": [ + "src": [ "./node_modules/@microsoft/signalr/dist/browser/signalr.min.js", ], "base": "./node_modules/@microsoft/signalr/dist/browser" }, { "name": "spectrum", - "src": [ + "src": [ "./node_modules/spectrum-colorpicker2/dist/spectrum.js", "./node_modules/spectrum-colorpicker2/dist/spectrum.css" ], @@ -227,7 +227,7 @@ function dependencies() { }, { "name": "tinymce", - "src": [ + "src": [ "./node_modules/tinymce/tinymce.min.js", "./node_modules/tinymce/plugins/**", "./node_modules/tinymce/skins/**", @@ -237,12 +237,12 @@ function dependencies() { }, { "name": "typeahead.js", - "src": ["./node_modules/typeahead.js/dist/typeahead.bundle.min.js"], + "src": ["./node_modules/typeahead.js/dist/typeahead.bundle.min.js"], "base": "./node_modules/typeahead.js/dist" }, { "name": "underscore", - "src": ["node_modules/underscore/underscore-min.js"], + "src": ["node_modules/underscore/underscore-min.js"], "base": "./node_modules/underscore" }, { @@ -257,71 +257,58 @@ function dependencies() { // add streams for node modules nodeModules.forEach(module => { - var task = gulp.src(module.src, { base: module.base, allowEmpty: true }); - - _.forEach(config.roots, function(root){ - task = task.pipe(gulp.dest(root + config.targets.lib + "/" + module.name)) - }); - - stream.add(task); + stream.add( + gulp.src(module.src, + { base: module.base, allowEmpty: true }) + .pipe(gulp.dest(config.root + config.targets.lib + "/" + module.name)) + ); }); //copy over libs which are not on npm (/lib) - var libTask = gulp.src(config.sources.globs.lib, { allowEmpty: true }); - - _.forEach(config.roots, function(root){ - libTask = libTask.pipe(gulp.dest(root + config.targets.lib)) - }); - - stream.add(libTask); + stream.add( + gulp.src(config.sources.globs.lib, { allowEmpty: true }) + .pipe(gulp.dest(config.root + config.targets.lib)) + ); //Copies all static assets into /root / assets folder //css, fonts and image files - + var assetsTask = gulp.src(config.sources.globs.assets, { allowEmpty: true }); assetsTask = assetsTask.pipe(imagemin([ - imagemin.gifsicle({interlaced: true}), - imagemin.mozjpeg({progressive: true}), - imagemin.optipng({optimizationLevel: 5}), + imagemin.gifsicle({ interlaced: true }), + imagemin.mozjpeg({ progressive: true }), + imagemin.optipng({ optimizationLevel: 5 }), imagemin.svgo({ plugins: [ - {removeViewBox: true}, - {cleanupIDs: false} + { removeViewBox: true }, + { cleanupIDs: false } ] }) ])); - _.forEach(config.roots, function(root){ - assetsTask = assetsTask.pipe(gulp.dest(root + config.targets.assets)); - }); - - + assetsTask = assetsTask.pipe(gulp.dest(config.root + config.targets.assets)); + + stream.add(assetsTask); // Copies all the less files related to the preview into their folder //these are not pre-processed as preview has its own less compiler client side - var lessTask = gulp.src("src/canvasdesigner/editors/*.less", { allowEmpty: true }); + stream.add( + gulp.src("src/canvasdesigner/editors/*.less", { allowEmpty: true }) + .pipe(gulp.dest(config.root + config.targets.assets + "/less")) + ); - _.forEach(config.roots, function(root){ - lessTask = lessTask.pipe(gulp.dest(root + config.targets.assets + "/less")); - }); - stream.add(lessTask); + // TODO: check if we need these fileSize + stream.add( + gulp.src("src/views/propertyeditors/grid/config/*.*", { allowEmpty: true }) + .pipe(gulp.dest(config.root + config.targets.views + "/propertyeditors/grid/config")) + ); + stream.add( + gulp.src("src/views/dashboard/default/*.jpg", { allowEmpty: true }) + .pipe(gulp.dest(config.root + config.targets.views + "/dashboard/default")) + ); - - // TODO: check if we need these fileSize - var configTask = gulp.src("src/views/propertyeditors/grid/config/*.*", { allowEmpty: true }); - _.forEach(config.roots, function(root){ - configTask = configTask.pipe(gulp.dest(root + config.targets.views + "/propertyeditors/grid/config")); - }); - stream.add(configTask); - - var dashboardTask = gulp.src("src/views/dashboard/default/*.jpg", { allowEmpty: true }); - _.forEach(config.roots, function(root){ - dashboardTask = dashboardTask .pipe(gulp.dest(root + config.targets.views + "/dashboard/default")); - }); - stream.add(dashboardTask); - return stream; }; diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/js.js b/src/Umbraco.Web.UI.Client/gulp/tasks/js.js index b46e105942..c82eafa845 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/js.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/js.js @@ -16,19 +16,17 @@ function js() { //we run multiple streams, so merge them all together var stream = new MergeStream(); - var task = gulp.src(config.sources.globs.js); - _.forEach(config.roots, function(root){ - task = task.pipe( gulp.dest(root + config.targets.js) ) - }) - stream.add(task); - + stream.add( + gulp.src(config.sources.globs.js).pipe(gulp.dest(config.root + config.targets.js)) + ); + _.forEach(config.sources.js, function (group) { stream.add( processJs(group.files, group.out) ); }); - return stream; + return stream; }; module.exports = { js: js }; diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/views.js b/src/Umbraco.Web.UI.Client/gulp/tasks/views.js index ab6d0c8d61..ffaa2ddc29 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/views.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/views.js @@ -14,19 +14,17 @@ function views() { _.forEach(config.sources.views, function (group) { var task = gulp.src(group.files) - .pipe(rename(function(path) { - path.dirname = path.dirname.toLowerCase(); - path.basename = path.basename.toLowerCase(); - path.extname = path.extname.toLowerCase(); - })); + .pipe(rename(function (path) { + path.dirname = path.dirname.toLowerCase(); + path.basename = path.basename.toLowerCase(); + path.extname = path.extname.toLowerCase(); + })); - _.forEach(config.roots, function(root){ - var destPath = root + config.targets.views + group.folder; - console.log("copying " + group.files + " to " + destPath) - task = task.pipe( gulp.dest(destPath)); - }) + var destPath = config.root + config.targets.views + group.folder; + console.log("copying " + group.files + " to " + destPath) + task = task.pipe(gulp.dest(destPath)); - stream.add (task); + stream.add(task); }); diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js index 7afb8d363f..9374aec4ee 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js @@ -1,7 +1,7 @@ 'use strict'; const config = require('../config'); -const {watch, series, parallel, dest, src} = require('gulp'); +const { watch, series, parallel, dest, src } = require('gulp'); var _ = require('lodash'); var rename = require('gulp-rename'); @@ -10,7 +10,7 @@ var MergeStream = require('merge-stream'); var processJs = require('../util/processJs'); var processLess = require('../util/processLess'); -var {js} = require('./js'); +var { js } = require('./js'); function watchTask(cb) { @@ -18,14 +18,14 @@ function watchTask(cb) { //Setup a watcher for all groups of JS files _.forEach(config.sources.js, function (group) { - if(group.watch !== false) { - watch(group.files, { ignoreInitial: true, interval: watchInterval }, function JS_Group_Compile() { return processJs(group.files, group.out);}); + if (group.watch !== false) { + watch(group.files, { ignoreInitial: true, interval: watchInterval }, function JS_Group_Compile() { return processJs(group.files, group.out); }); } }); //Setup a watcher for all groups of LESS files _.forEach(config.sources.less, function (group) { - if(group.watch !== false) { + if (group.watch !== false) { watch(group.watch, { ignoreInitial: true, interval: watchInterval }, function Less_Group_Compile() { return processLess(group.files, group.out); }); } }); @@ -33,22 +33,21 @@ function watchTask(cb) { //Setup a watcher for all groups of view files var viewWatcher; _.forEach(config.sources.views, function (group) { - if(group.watch !== false) { + if (group.watch !== false) { viewWatcher = watch(group.files, { ignoreInitial: true, interval: watchInterval }, parallel( function MoveViewsAndRegenerateJS() { - var task = src(group.files) - .pipe(rename(function(path) { - path.dirname = path.dirname.toLowerCase(); - path.basename = path.basename.toLowerCase(); - path.extname = path.extname.toLowerCase(); - })); - _.forEach(config.roots, function(root){ - var destPath = root + config.targets.views + group.folder; - console.log("copying " + group.files + " to " + destPath); - task = task.pipe( dest(destPath) ); - }); + var task = src(group.files) + .pipe(rename(function (path) { + path.dirname = path.dirname.toLowerCase(); + path.basename = path.basename.toLowerCase(); + path.extname = path.extname.toLowerCase(); + })); + + var destPath = config.root + config.targets.views + group.folder; + console.log("copying " + group.files + " to " + destPath); + task = task.pipe(dest(destPath)); }, js ) diff --git a/src/Umbraco.Web.UI.Client/gulp/util/processJs.js b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js index 6c6f1276e7..24ea0eec0c 100644 --- a/src/Umbraco.Web.UI.Client/gulp/util/processJs.js +++ b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js @@ -11,11 +11,9 @@ var embedTemplates = require('gulp-angular-embed-templates'); var _ = require('lodash'); module.exports = function (files, out) { - - _.forEach(config.roots, function(root){ - console.log("JS: ", files, " -> ", root + config.targets.js + out) - }) - + + console.log("JS: ", files, " -> ", config.root + config.targets.js + out) + var task = gulp.src(files); // check for js errors @@ -31,13 +29,10 @@ module.exports = function (files, out) { if(config.compile.current.embedtemplates === true) { task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } })); } - - task = task.pipe(concat(out)).pipe(wrap('(function(){\n%= body %\n})();')) - _.forEach(config.roots, function(root){ - task = task.pipe(gulp.dest(root + config.targets.js)); - }) - + task = task.pipe(concat(out)) + .pipe(wrap('(function(){\n%= body %\n})();')) + .pipe(gulp.dest(config.root + config.targets.js)); return task; diff --git a/src/Umbraco.Web.UI.Client/gulp/util/processLess.js b/src/Umbraco.Web.UI.Client/gulp/util/processLess.js index eea8fc31b0..e5b4cdf0a2 100644 --- a/src/Umbraco.Web.UI.Client/gulp/util/processLess.js +++ b/src/Umbraco.Web.UI.Client/gulp/util/processLess.js @@ -17,10 +17,8 @@ module.exports = function(files, out) { autoprefixer, cssnano({zindex: false}) ]; - _.forEach(config.roots, function(root){ - console.log("LESS: ", files, " -> ", root + config.targets.css + out); - }) - + + console.log("LESS: ", files, " -> ", config.root + config.targets.css + out) var task = gulp.src(files); @@ -37,12 +35,7 @@ module.exports = function(files, out) { task = task.pipe(sourcemaps.write('./maps')); } - _.forEach(config.roots, function(root){ - task = task.pipe(gulp.dest(root + config.targets.css)); - }) - - - + task = task.pipe(gulp.dest(config.root + config.targets.css)); return task; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js b/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js index f1dbb0f651..b44f79dd65 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js @@ -14,8 +14,8 @@ function externalLoginInfoService(externalLoginInfo, umbRequestHelper) { } function getLoginProviderView(provider) { - if (provider && provider.properties && provider.properties.UmbracoBackOfficeExternalLoginOptions && provider.properties.UmbracoBackOfficeExternalLoginOptions.CustomBackOfficeView) { - return umbRequestHelper.convertVirtualToAbsolutePath(provider.properties.UmbracoBackOfficeExternalLoginOptions.CustomBackOfficeView); + if (provider && provider.properties && provider.properties.CustomBackOfficeView) { + return umbRequestHelper.convertVirtualToAbsolutePath(provider.properties.CustomBackOfficeView); } return null; } @@ -26,10 +26,10 @@ function externalLoginInfoService(externalLoginInfo, umbRequestHelper) { */ function hasDenyLocalLogin(provider) { if (!provider) { - return _.some(externalLoginInfo.providers, x => x.properties && x.properties.UmbracoBackOfficeExternalLoginOptions && (x.properties.UmbracoBackOfficeExternalLoginOptions.DenyLocalLogin === true)); + return _.some(externalLoginInfo.providers, x => x.properties && (x.properties.DenyLocalLogin === true)); } else { - return provider && provider.properties && provider.properties.UmbracoBackOfficeExternalLoginOptions && (provider.properties.UmbracoBackOfficeExternalLoginOptions.DenyLocalLogin === true); + return provider && provider.properties && (provider.properties.DenyLocalLogin === true); } } From 2261ca8e455378fb8cbabacf09fffb4189467ef5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 30 Nov 2020 21:19:02 +0000 Subject: [PATCH 23/37] Update to be editable via the UI --- linting/codeanalysis.ruleset | 26 +---------- linting/codeanalysis.tests.ruleset | 75 ++++++++---------------------- 2 files changed, 21 insertions(+), 80 deletions(-) diff --git a/linting/codeanalysis.ruleset b/linting/codeanalysis.ruleset index b94fa22670..4fde2bef8d 100644 --- a/linting/codeanalysis.ruleset +++ b/linting/codeanalysis.ruleset @@ -1,38 +1,16 @@  - + - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/linting/codeanalysis.tests.ruleset b/linting/codeanalysis.tests.ruleset index 7fa60db024..0ac055b328 100644 --- a/linting/codeanalysis.tests.ruleset +++ b/linting/codeanalysis.tests.ruleset @@ -1,63 +1,26 @@  - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + \ No newline at end of file From 927335149d87ad998777dc0a9fa8f2a7f0b056fd Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 1 Dec 2020 15:02:28 +1100 Subject: [PATCH 24/37] try fixing watch task --- src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js index 9374aec4ee..ded59a8fe1 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js @@ -48,6 +48,8 @@ function watchTask(cb) { var destPath = config.root + config.targets.views + group.folder; console.log("copying " + group.files + " to " + destPath); task = task.pipe(dest(destPath)); + + return task; }, js ) From 20b4f556645d8f15daa84bebd6ee6ebea8f37c8d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 1 Dec 2020 17:24:23 +1100 Subject: [PATCH 25/37] Fixes up a bunch of TODOs moves user manager to the back office project so we have acess to necessary services, splits apart files, removes old code, starts implementing the 2fa stuff --- .../BackOffice/BackOfficeUserStore.cs | 4 +- .../BackOffice/IBackOfficeUserManager.cs | 5 +- .../AutoFixture/AutoMoqDataAttribute.cs | 1 + .../Testing/TestingTests/MockTests.cs | 1 - .../Controllers/AuthenticationController.cs | 55 +++--- .../BackOfficeServiceCollectionExtensions.cs | 1 + .../AuthenticationBuilderExtensions.cs | 15 ++ .../BackOfficeAuthenticationBuilder.cs | 64 +++++++ .../BackOfficeExternalLoginProvider.cs | 39 +++++ .../BackOfficeExternalLoginProviders.cs | 38 ++++ .../BackOfficeExternalLoginsBuilder.cs | 33 ++++ .../Security/BackOfficeSignInManager.cs | 8 +- .../Security}/BackOfficeUserManager.cs | 71 ++++---- .../IBackOfficeExternalLoginProviders.cs | 164 +----------------- .../Security/IBackOfficeTwoFactorOptions.cs | 16 ++ .../NoopBackOfficeTwoFactorOptions.cs | 8 + .../BackOfficeUserAuditEventsComponent.cs | 29 ++-- src/Umbraco.Web/Editors/PasswordChanger.cs | 103 ----------- src/Umbraco.Web/OwinExtensions.cs | 19 -- .../Security/BackOfficeOwinUserManager.cs | 142 --------------- .../Security/BackOfficeUserManagerMarker.cs | 25 --- .../Security/IBackOfficeUserManagerMarker.cs | 14 -- src/Umbraco.Web/Security/MembershipHelper.cs | 35 +--- src/Umbraco.Web/Umbraco.Web.csproj | 4 - .../WebApi/UmbracoAuthorizedApiController.cs | 7 - 25 files changed, 307 insertions(+), 594 deletions(-) create mode 100644 src/Umbraco.Web.BackOffice/Security/AuthenticationBuilderExtensions.cs create mode 100644 src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs create mode 100644 src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProvider.cs create mode 100644 src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs create mode 100644 src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginsBuilder.cs rename src/{Umbraco.Infrastructure/BackOffice => Umbraco.Web.BackOffice/Security}/BackOfficeUserManager.cs (86%) create mode 100644 src/Umbraco.Web.BackOffice/Security/IBackOfficeTwoFactorOptions.cs create mode 100644 src/Umbraco.Web.BackOffice/Security/NoopBackOfficeTwoFactorOptions.cs delete mode 100644 src/Umbraco.Web/Editors/PasswordChanger.cs delete mode 100644 src/Umbraco.Web/Security/BackOfficeOwinUserManager.cs delete mode 100644 src/Umbraco.Web/Security/BackOfficeUserManagerMarker.cs delete mode 100644 src/Umbraco.Web/Security/IBackOfficeUserManagerMarker.cs diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs index 7ac3701c5c..cdd81a3fe3 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs +++ b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs @@ -156,7 +156,7 @@ namespace Umbraco.Core.BackOffice /// /// /// - public async Task UpdateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) + public Task UpdateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -192,7 +192,7 @@ namespace Umbraco.Core.BackOffice } } - return IdentityResult.Success; + return Task.FromResult(IdentityResult.Success); } /// diff --git a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs b/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs index ee61359a70..664c957f57 100644 --- a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs +++ b/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs @@ -311,13 +311,14 @@ namespace Umbraco.Core.BackOffice /// 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); - void RaiseLoginSuccessEvent(TUser currentUser, int userId); - bool HasSendingUserInviteEventHandler { get; } } } diff --git a/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs b/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs index 5f258bcb87..78d5d5554c 100644 --- a/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs +++ b/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs @@ -18,6 +18,7 @@ using Umbraco.Tests.Common.Builders; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.BackOffice.Routing; using Umbraco.Web.Common.Install; +using Umbraco.Web.Common.Security; using Umbraco.Web.WebApi; namespace Umbraco.Tests.UnitTests.AutoFixture diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index 2d9c66806b..0eaa141250 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -111,7 +111,6 @@ namespace Umbraco.Tests.Testing.TestingTests var memberService = Mock.Of(); var memberTypeService = Mock.Of(); var membershipProvider = new MembersMembershipProvider(memberService, memberTypeService, Mock.Of(), TestHelper.GetHostingEnvironment(), TestHelper.GetIpResolver()); - var membershipHelper = new MembershipHelper(Mock.Of(), Mock.Of(), membershipProvider, Mock.Of(), memberService, memberTypeService, Mock.Of(), AppCaches.Disabled, NullLoggerFactory.Instance, ShortStringHelper, Mock.Of()); var umbracoMapper = new UmbracoMapper(new MapDefinitionCollection(new[] { Mock.Of() })); var umbracoApiController = new FakeUmbracoApiController(new GlobalSettings(), Mock.Of(), Mock.Of(), Mock.Of(), ServiceContext.CreatePartial(), AppCaches.NoCache, profilingLogger , Mock.Of(), umbracoMapper, Mock.Of()); diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 6aa6c46eee..5c95be2afc 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -5,6 +5,7 @@ using System.Net; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; @@ -66,6 +67,7 @@ namespace Umbraco.Web.BackOffice.Controllers private readonly IRequestAccessor _requestAccessor; private readonly LinkGenerator _linkGenerator; private readonly IBackOfficeExternalLoginProviders _externalAuthenticationOptions; + private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions; // TODO: We need to import the logic from Umbraco.Web.Editors.AuthenticationController // TODO: We need to review all _userManager.Raise calls since many/most should be on the usermanager or signinmanager, very few should be here @@ -87,7 +89,8 @@ namespace Umbraco.Web.BackOffice.Controllers Core.Hosting.IHostingEnvironment hostingEnvironment, IRequestAccessor requestAccessor, LinkGenerator linkGenerator, - IBackOfficeExternalLoginProviders externalAuthenticationOptions) + IBackOfficeExternalLoginProviders externalAuthenticationOptions, + IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions) { _backofficeSecurityAccessor = backofficeSecurityAccessor; _userManager = backOfficeUserManager; @@ -106,6 +109,7 @@ namespace Umbraco.Web.BackOffice.Controllers _requestAccessor = requestAccessor; _linkGenerator = linkGenerator; _externalAuthenticationOptions = externalAuthenticationOptions; + _backOfficeTwoFactorOptions = backOfficeTwoFactorOptions; } /// @@ -303,7 +307,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [SetAngularAntiForgeryTokens] [DenyLocalLoginAuthorization] - public async Task PostLogin(LoginModel loginModel) + public async Task> PostLogin(LoginModel loginModel) { // Sign the user in with username/password, this also gives a chance for developers to // custom verify the credentials and auto-link user accounts with a custom IBackOfficePasswordChecker @@ -318,40 +322,25 @@ namespace Umbraco.Web.BackOffice.Controllers if (result.RequiresTwoFactor) { - throw new NotImplementedException("Implement MFA/2FA, we need to have some IOptions or similar to configure this"); + var twofactorView = _backOfficeTwoFactorOptions.GetTwoFactorView(loginModel.Username); + if (twofactorView.IsNullOrWhiteSpace()) + { + return new ValidationErrorResult($"The registered {typeof(IBackOfficeTwoFactorOptions)} of type {_backOfficeTwoFactorOptions.GetType()} did not return a view for two factor auth "); + } - //var twofactorOptions = UserManager as IUmbracoBackOfficeTwoFactorOptions; - //if (twofactorOptions == null) - //{ - // throw new HttpResponseException( - // Request.CreateErrorResponse( - // HttpStatusCode.BadRequest, - // "UserManager does not implement " + typeof(IUmbracoBackOfficeTwoFactorOptions))); - //} + var attemptedUser = _userService.GetByUsername(loginModel.Username); - //var twofactorView = twofactorOptions.GetTwoFactorView( - // owinContext, - // UmbracoContext, - // loginModel.Username); + // create a with information to display a custom two factor send code view + var verifyResponse = new ObjectResult(new + { + twoFactorView = twofactorView, + userId = attemptedUser.Id + }) + { + StatusCode = StatusCodes.Status402PaymentRequired + }; - //if (twofactorView.IsNullOrWhiteSpace()) - //{ - // throw new HttpResponseException( - // Request.CreateErrorResponse( - // HttpStatusCode.BadRequest, - // typeof(IUmbracoBackOfficeTwoFactorOptions) + ".GetTwoFactorView returned an empty string")); - //} - - //var attemptedUser = Services.UserService.GetByUsername(loginModel.Username); - - //// create a with information to display a custom two factor send code view - //var verifyResponse = Request.CreateResponse(HttpStatusCode.PaymentRequired, new - //{ - // twoFactorView = twofactorView, - // userId = attemptedUser.Id - //}); - - //_userManager.RaiseLoginRequiresVerificationEvent(User, attemptedUser.Id); + //return verifyResponse; } diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs index 1b8e6fb80a..8d3223a79f 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs @@ -63,6 +63,7 @@ namespace Umbraco.Extensions services.TryAddScoped(); services.TryAddScoped(); services.TryAddSingleton(); + services.TryAddSingleton(); /* * IdentityBuilderExtensions.AddUserManager adds UserManager to service collection diff --git a/src/Umbraco.Web.BackOffice/Security/AuthenticationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Security/AuthenticationBuilderExtensions.cs new file mode 100644 index 0000000000..e2a7aeccaf --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/AuthenticationBuilderExtensions.cs @@ -0,0 +1,15 @@ +using System; +using Umbraco.Core.Builder; + +namespace Umbraco.Web.BackOffice.Security +{ + public static class AuthenticationBuilderExtensions + { + public static IUmbracoBuilder AddBackOfficeExternalLogins(this IUmbracoBuilder umbracoBuilder, Action builder) + { + builder(new BackOfficeExternalLoginsBuilder(umbracoBuilder.Services)); + return umbracoBuilder; + } + } + +} diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs new file mode 100644 index 0000000000..b3418697e2 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs @@ -0,0 +1,64 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using System; +using Umbraco.Core; + +namespace Umbraco.Web.BackOffice.Security +{ + /// + /// Custom used to associate external logins with umbraco external login options + /// + public class BackOfficeAuthenticationBuilder : AuthenticationBuilder + { + private readonly BackOfficeExternalLoginProviderOptions _loginProviderOptions; + + public BackOfficeAuthenticationBuilder(IServiceCollection services, BackOfficeExternalLoginProviderOptions loginProviderOptions) + : base(services) + { + _loginProviderOptions = loginProviderOptions; + } + + public string SchemeForBackOffice(string scheme) + { + return Constants.Security.BackOfficeExternalAuthenticationTypePrefix + scheme; + } + + /// + /// Overridden to track the final authenticationScheme being registered for the external login + /// + /// + /// + /// + /// + /// + /// + public override AuthenticationBuilder AddRemoteScheme(string authenticationScheme, string displayName, Action configureOptions) + { + // Validate that the prefix is set + if (!authenticationScheme.StartsWith(Constants.Security.BackOfficeExternalAuthenticationTypePrefix)) + { + throw new InvalidOperationException($"The {nameof(authenticationScheme)} is not prefixed with {Constants.Security.BackOfficeExternalAuthenticationTypePrefix}. The scheme must be created with a call to the method {nameof(SchemeForBackOffice)}"); + } + + // add our login provider to the container along with a custom options configuration + Services.AddSingleton(x => new BackOfficeExternalLoginProvider(displayName, authenticationScheme, _loginProviderOptions)); + Services.TryAddEnumerable(ServiceDescriptor.Singleton, EnsureBackOfficeScheme>()); + + return base.AddRemoteScheme(authenticationScheme, displayName, configureOptions); + } + + // TODO: We could override and throw NotImplementedException for other methods? + + // Ensures that the sign in scheme is always the Umbraco back office external type + private class EnsureBackOfficeScheme : IPostConfigureOptions where TOptions : RemoteAuthenticationOptions + { + public void PostConfigure(string name, TOptions options) + { + options.SignInScheme = Constants.Security.BackOfficeExternalAuthenticationType; + } + } + } + +} diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProvider.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProvider.cs new file mode 100644 index 0000000000..18e5b066dc --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProvider.cs @@ -0,0 +1,39 @@ +using System; + +namespace Umbraco.Web.BackOffice.Security +{ + /// + /// An external login (OAuth) provider for the back office + /// + public class BackOfficeExternalLoginProvider : IEquatable + { + public BackOfficeExternalLoginProvider(string name, string authenticationType, BackOfficeExternalLoginProviderOptions properties) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + AuthenticationType = authenticationType ?? throw new ArgumentNullException(nameof(authenticationType)); + Options = properties ?? throw new ArgumentNullException(nameof(properties)); + } + + public string Name { get; } + public string AuthenticationType { get; } + public BackOfficeExternalLoginProviderOptions Options { get; } + + public override bool Equals(object obj) + { + return Equals(obj as BackOfficeExternalLoginProvider); + } + + public bool Equals(BackOfficeExternalLoginProvider other) + { + return other != null && + Name == other.Name && + AuthenticationType == other.AuthenticationType; + } + + public override int GetHashCode() + { + return HashCode.Combine(Name, AuthenticationType); + } + } + +} diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs new file mode 100644 index 0000000000..08ef32dc57 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Web.BackOffice.Security +{ + public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProviders + { + public BackOfficeExternalLoginProviders(IEnumerable externalLogins) + { + _externalLogins = externalLogins; + } + + private readonly IEnumerable _externalLogins; + + public BackOfficeExternalLoginProvider Get(string authenticationType) + { + return _externalLogins.FirstOrDefault(x => x.AuthenticationType == authenticationType); + } + + public string GetAutoLoginProvider() + { + var found = _externalLogins.Where(x => x.Options.AutoRedirectLoginToExternalProvider).ToList(); + return found.Count > 0 ? found[0].AuthenticationType : null; + } + + public IEnumerable GetBackOfficeProviders() + { + return _externalLogins; + } + + public bool HasDenyLocalLogin() + { + var found = _externalLogins.Where(x => x.Options.DenyLocalLogin).ToList(); + return found.Count > 0; + } + } + +} diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginsBuilder.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginsBuilder.cs new file mode 100644 index 0000000000..402ad8b948 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginsBuilder.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Umbraco.Web.BackOffice.Security +{ + /// + /// Used to add back office login providers + /// + public class BackOfficeExternalLoginsBuilder + { + public BackOfficeExternalLoginsBuilder(IServiceCollection services) + { + _services = services; + } + + private readonly IServiceCollection _services; + + /// + /// Add a back office login provider with options + /// + /// + /// + /// + public BackOfficeExternalLoginsBuilder AddBackOfficeLogin( + BackOfficeExternalLoginProviderOptions loginProviderOptions, + Action build) + { + build(new BackOfficeAuthenticationBuilder(_services, loginProviderOptions)); + return this; + } + } + +} diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs index f08be0bd99..d2050e214f 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs @@ -10,8 +10,11 @@ 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; using Umbraco.Extensions; +using Umbraco.Net; using Umbraco.Web.BackOffice.Security; namespace Umbraco.Web.Common.Security @@ -436,16 +439,17 @@ namespace Umbraco.Web.Common.Security Logger.LogInformation("User: {UserName} logged in from IP address {IpAddress}", username, Context.Connection.RemoteIpAddress); if (user != null) { - _userManager.RaiseLoginSuccessEvent(user, user.Id); + _userManager.RaiseLoginSuccessEvent(Context.User, user.Id); } } else if (result.IsLockedOut) { - _userManager.RaiseAccountLockedEvent(user, user.Id); + _userManager.RaiseAccountLockedEvent(Context.User, user.Id); Logger.LogInformation("Login attempt failed for username {UserName} from IP address {IpAddress}, the user is locked", username, Context.Connection.RemoteIpAddress); } else if (result.RequiresTwoFactor) { + _userManager.RaiseLoginRequiresVerificationEvent(Context.User, user.Id); Logger.LogInformation("Login attempt requires verification for username {UserName} from IP address {IpAddress}", username, Context.Connection.RemoteIpAddress); } else if (!result.Succeeded || result.IsNotAllowed) diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs similarity index 86% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs rename to src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs index 1e5cd8436e..3501242eb5 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs @@ -1,11 +1,14 @@ -using System; +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.Membership; @@ -14,7 +17,8 @@ using Umbraco.Extensions; using Umbraco.Net; using Umbraco.Web.Models.ContentEditing; -namespace Umbraco.Core.BackOffice + +namespace Umbraco.Web.Common.Security { public class BackOfficeUserManager : BackOfficeUserManager, IBackOfficeUserManager { @@ -28,9 +32,10 @@ namespace Umbraco.Core.BackOffice BackOfficeLookupNormalizer keyNormalizer, BackOfficeIdentityErrorDescriber errors, IServiceProvider services, + IHttpContextAccessor httpContextAccessor, ILogger> logger, IOptions passwordConfiguration) - : base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger, passwordConfiguration) + : base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, httpContextAccessor, logger, passwordConfiguration) { } } @@ -39,6 +44,7 @@ namespace Umbraco.Core.BackOffice where T : BackOfficeIdentityUser { private PasswordGenerator _passwordGenerator; + private readonly IHttpContextAccessor _httpContextAccessor; public BackOfficeUserManager( IIpResolver ipResolver, @@ -50,11 +56,13 @@ namespace Umbraco.Core.BackOffice 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)); } @@ -95,7 +103,7 @@ namespace Umbraco.Core.BackOffice { 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 (userSessionStore == null) return true; return await userSessionStore.ValidateSessionIdAsync(userId, sessionId); } @@ -140,7 +148,7 @@ namespace Umbraco.Core.BackOffice { if (user == null) throw new ArgumentNullException(nameof(user)); - if (user.IsApproved == false) return true; + if (user.IsApproved == false) return true; return await base.IsLockedOutAsync(user); } @@ -211,7 +219,9 @@ namespace Umbraco.Core.BackOffice var result = await base.ResetPasswordAsync(user, token, newPassword); if (result.Succeeded) - RaisePasswordChangedEvent(null, userId); // TODO: How can we get the current user? we have not HttpContext (netstandard), we can make our own IPrincipalAccessor? + { + RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, userId); + } return result; } @@ -219,7 +229,9 @@ namespace Umbraco.Core.BackOffice { var result = await base.ChangePasswordAsync(user, currentPassword, newPassword); if (result.Succeeded) - RaisePasswordChangedEvent(null, user.Id); // TODO: How can we get the current user? we have not HttpContext (netstandard), we can make our own IPrincipalAccessor? + { + RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, user.Id); + } return result; } @@ -297,11 +309,11 @@ namespace Umbraco.Core.BackOffice // The way we unlock is by setting the lockoutEnd date to the current datetime if (result.Succeeded && lockoutEnd >= DateTimeOffset.UtcNow) { - RaiseAccountLockedEvent(null, user.Id); // TODO: How can we get the current user? we have not HttpContext (netstandard), we can make our own IPrincipalAccessor? + RaiseAccountLockedEvent(_httpContextAccessor.HttpContext?.User, user.Id); } else { - RaiseAccountUnlockedEvent(null, user.Id); // TODO: How can we get the current user? we have not HttpContext (netstandard), we can make our own IPrincipalAccessor? + RaiseAccountUnlockedEvent(_httpContextAccessor.HttpContext?.User, user.Id); //Resets the login attempt fails back to 0 when unlock is clicked await ResetAccessFailedCountAsync(user); } @@ -321,7 +333,7 @@ namespace Umbraco.Core.BackOffice await lockoutStore.ResetAccessFailedCountAsync(user, CancellationToken.None); //raise the event now that it's reset - RaiseResetAccessFailedCountEvent(null, user.Id); // TODO: How can we get the current user? we have not HttpContext (netstandard), we can make our own IPrincipalAccessor? + RaiseResetAccessFailedCountEvent(_httpContextAccessor.HttpContext?.User, user.Id); return await UpdateAsync(user); } @@ -357,8 +369,7 @@ namespace Umbraco.Core.BackOffice //Slightly confusing: this will return a Success if we successfully update the AccessFailed count if (result.Succeeded) { - // TODO: This may no longer be the case in netcore, we'll need to see about that - RaiseLoginFailedEvent(null, user.Id); // TODO: How can we get the current user? we have not HttpContext (netstandard), we can make our own IPrincipalAccessor? + RaiseLoginFailedEvent(_httpContextAccessor.HttpContext?.User, user.Id); } return result; @@ -367,7 +378,7 @@ namespace Umbraco.Core.BackOffice private int GetCurrentUserId(IPrincipal currentUser) { var umbIdentity = currentUser?.GetUmbracoIdentity(); - var currentUserId = umbIdentity?.GetUserId() ?? Constants.Security.SuperUserId; + var currentUserId = umbIdentity?.GetUserId() ?? Core.Constants.Security.SuperUserId; return currentUserId; } private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, IPrincipal currentUser, int affectedUserId, string affectedUsername) @@ -383,9 +394,9 @@ namespace Umbraco.Core.BackOffice return new IdentityAuditEventArgs(auditEvent, ip, currentUserId, string.Empty, affectedUserId, affectedUsername); } - // 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 - // In some cases it will be nicer/easier to not pass in IPrincipal - public void RaiseAccountLockedEvent(BackOfficeIdentityUser currentUser, int userId) => OnAccountLocked(CreateArgs(AuditEvent.AccountLocked, currentUser, userId, string.Empty)); + // 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 RaiseAccountUnlockedEvent(IPrincipal currentUser, int userId) => OnAccountUnlocked(CreateArgs(AuditEvent.AccountUnlocked, currentUser, userId, string.Empty)); @@ -395,11 +406,11 @@ namespace Umbraco.Core.BackOffice public void RaiseLoginFailedEvent(IPrincipal currentUser, int userId) => OnLoginFailed(CreateArgs(AuditEvent.LoginFailed, currentUser, userId, string.Empty)); - public void RaiseInvalidLoginAttemptEvent(IPrincipal currentUser, string username) => OnLoginFailed(CreateArgs(AuditEvent.LoginFailed, currentUser, Constants.Security.SuperUserId, username)); + //public void RaiseInvalidLoginAttemptEvent(IPrincipal currentUser, string username) => OnLoginFailed(CreateArgs(AuditEvent.LoginFailed, currentUser, Constants.Security.SuperUserId, username)); public void RaiseLoginRequiresVerificationEvent(IPrincipal currentUser, int userId) => OnLoginRequiresVerification(CreateArgs(AuditEvent.LoginRequiresVerification, currentUser, userId, string.Empty)); - public void RaiseLoginSuccessEvent(BackOfficeIdentityUser currentUser, int userId) => OnLoginSuccess(CreateArgs(AuditEvent.LoginSucces, currentUser, userId, string.Empty)); + public void RaiseLoginSuccessEvent(IPrincipal currentUser, int userId) => OnLoginSuccess(CreateArgs(AuditEvent.LoginSucces, currentUser, userId, string.Empty)); public SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId) { @@ -408,7 +419,7 @@ namespace Umbraco.Core.BackOffice OnLogoutSuccess(args); return args; } - + public void RaisePasswordChangedEvent(IPrincipal currentUser, int userId) => OnPasswordChanged(CreateArgs(AuditEvent.LogoutSuccess, currentUser, userId, string.Empty)); public void RaiseResetAccessFailedCountEvent(IPrincipal currentUser, int userId) => OnResetAccessFailedCount(CreateArgs(AuditEvent.ResetAccessFailedCount, currentUser, userId, string.Empty)); @@ -424,17 +435,17 @@ namespace Umbraco.Core.BackOffice public bool HasSendingUserInviteEventHandler => SendingUserInvite != null; - public static event EventHandler AccountLocked; - public static event EventHandler AccountUnlocked; - public static event EventHandler ForgotPasswordRequested; - public static event EventHandler ForgotPasswordChangedSuccess; - public static event EventHandler LoginFailed; - public static event EventHandler LoginRequiresVerification; - public static event EventHandler LoginSuccess; - public static event EventHandler LogoutSuccess; - public static event EventHandler PasswordChanged; - public static event EventHandler PasswordReset; - public static event EventHandler ResetAccessFailedCount; + public event EventHandler AccountLocked; + public event EventHandler AccountUnlocked; + public event EventHandler ForgotPasswordRequested; + public event EventHandler ForgotPasswordChangedSuccess; + public event EventHandler LoginFailed; + public event EventHandler LoginRequiresVerification; + public event EventHandler LoginSuccess; + public event EventHandler LogoutSuccess; + public event EventHandler PasswordChanged; + public event EventHandler PasswordReset; + public event EventHandler ResetAccessFailedCount; /// /// Raised when a user is invited diff --git a/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs index 6b78e58ead..2274df14a3 100644 --- a/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs +++ b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs @@ -1,111 +1,14 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.OAuth; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Options; -using System; +using Microsoft.AspNetCore.Authentication.OAuth; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Text; -using Umbraco.Core; -using Umbraco.Core.Builder; namespace Umbraco.Web.BackOffice.Security { - /// - /// Custom used to associate external logins with umbraco external login options - /// - public class BackOfficeAuthenticationBuilder : AuthenticationBuilder - { - private readonly BackOfficeExternalLoginProviderOptions _loginProviderOptions; - - public BackOfficeAuthenticationBuilder(IServiceCollection services, BackOfficeExternalLoginProviderOptions loginProviderOptions) - : base(services) - { - _loginProviderOptions = loginProviderOptions; - } - - public string SchemeForBackOffice(string scheme) - { - return Constants.Security.BackOfficeExternalAuthenticationTypePrefix + scheme; - } - - /// - /// Overridden to track the final authenticationScheme being registered for the external login - /// - /// - /// - /// - /// - /// - /// - public override AuthenticationBuilder AddRemoteScheme(string authenticationScheme, string displayName, Action configureOptions) - { - // Validate that the prefix is set - if (!authenticationScheme.StartsWith(Constants.Security.BackOfficeExternalAuthenticationTypePrefix)) - { - throw new InvalidOperationException($"The {nameof(authenticationScheme)} is not prefixed with {Constants.Security.BackOfficeExternalAuthenticationTypePrefix}. The scheme must be created with a call to the method {nameof(SchemeForBackOffice)}"); - } - - // add our login provider to the container along with a custom options configuration - Services.AddSingleton(x => new BackOfficeExternalLoginProvider(displayName, authenticationScheme, _loginProviderOptions)); - Services.TryAddEnumerable(ServiceDescriptor.Singleton, EnsureBackOfficeScheme>()); - - return base.AddRemoteScheme(authenticationScheme, displayName, configureOptions); - } - - // TODO: We could override and throw NotImplementedException for other methods? - - // Ensures that the sign in scheme is always the Umbraco back office external type - private class EnsureBackOfficeScheme : IPostConfigureOptions where TOptions : RemoteAuthenticationOptions - { - public void PostConfigure(string name, TOptions options) - { - options.SignInScheme = Constants.Security.BackOfficeExternalAuthenticationType; - } - } - } /// - /// Used to add back office login providers + /// Service to return instances /// - public class BackOfficeExternalLoginsBuilder - { - public BackOfficeExternalLoginsBuilder(IServiceCollection services) - { - _services = services; - } - - private readonly IServiceCollection _services; - - /// - /// Add a back office login provider with options - /// - /// - /// - /// - public BackOfficeExternalLoginsBuilder AddBackOfficeLogin( - BackOfficeExternalLoginProviderOptions loginProviderOptions, - Action build) - { - build(new BackOfficeAuthenticationBuilder(_services, loginProviderOptions)); - return this; - } - } - - public static class AuthenticationBuilderExtensions - { - public static IUmbracoBuilder AddBackOfficeExternalLogins(this IUmbracoBuilder umbracoBuilder, Action builder) - { - builder(new BackOfficeExternalLoginsBuilder(umbracoBuilder.Services)); - return umbracoBuilder; - } - } - - // TODO: We need to implement this and extend it to support the back office external login options - // basically migrate things from AuthenticationManagerExtensions & AuthenticationOptionsExtensions - // and use this to get the back office external login infos public interface IBackOfficeExternalLoginProviders { BackOfficeExternalLoginProvider Get(string authenticationType); @@ -126,67 +29,4 @@ namespace Umbraco.Web.BackOffice.Security bool HasDenyLocalLogin(); } - public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProviders - { - public BackOfficeExternalLoginProviders(IEnumerable externalLogins) - { - _externalLogins = externalLogins; - } - - private readonly IEnumerable _externalLogins; - - public BackOfficeExternalLoginProvider Get(string authenticationType) - { - return _externalLogins.FirstOrDefault(x => x.AuthenticationType == authenticationType); - } - - public string GetAutoLoginProvider() - { - var found = _externalLogins.Where(x => x.Options.AutoRedirectLoginToExternalProvider).ToList(); - return found.Count > 0 ? found[0].AuthenticationType : null; - } - - public IEnumerable GetBackOfficeProviders() - { - return _externalLogins; - } - - public bool HasDenyLocalLogin() - { - var found = _externalLogins.Where(x => x.Options.DenyLocalLogin).ToList(); - return found.Count > 0; - } - } - - public class BackOfficeExternalLoginProvider : IEquatable - { - public BackOfficeExternalLoginProvider(string name, string authenticationType, BackOfficeExternalLoginProviderOptions properties) - { - Name = name ?? throw new ArgumentNullException(nameof(name)); - AuthenticationType = authenticationType ?? throw new ArgumentNullException(nameof(authenticationType)); - Options = properties ?? throw new ArgumentNullException(nameof(properties)); - } - - public string Name { get; } - public string AuthenticationType { get; } - public BackOfficeExternalLoginProviderOptions Options { get; } - - public override bool Equals(object obj) - { - return Equals(obj as BackOfficeExternalLoginProvider); - } - - public bool Equals(BackOfficeExternalLoginProvider other) - { - return other != null && - Name == other.Name && - AuthenticationType == other.AuthenticationType; - } - - public override int GetHashCode() - { - return HashCode.Combine(Name, AuthenticationType); - } - } - } diff --git a/src/Umbraco.Web.BackOffice/Security/IBackOfficeTwoFactorOptions.cs b/src/Umbraco.Web.BackOffice/Security/IBackOfficeTwoFactorOptions.cs new file mode 100644 index 0000000000..a05d71f3cb --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/IBackOfficeTwoFactorOptions.cs @@ -0,0 +1,16 @@ +namespace Umbraco.Web.BackOffice.Security +{ + /// + /// Options used to control 2FA for the Umbraco back office + /// + public interface IBackOfficeTwoFactorOptions + { + /// + /// Returns the angular view for handling 2FA interaction + /// + /// + /// + string GetTwoFactorView(string username); + } + +} diff --git a/src/Umbraco.Web.BackOffice/Security/NoopBackOfficeTwoFactorOptions.cs b/src/Umbraco.Web.BackOffice/Security/NoopBackOfficeTwoFactorOptions.cs new file mode 100644 index 0000000000..bbc0b3e049 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/NoopBackOfficeTwoFactorOptions.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Web.BackOffice.Security +{ + public class NoopBackOfficeTwoFactorOptions : IBackOfficeTwoFactorOptions + { + public string GetTwoFactorView(string username) => null; + } + +} diff --git a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs index bfb80924d1..d8210556fd 100644 --- a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs +++ b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs @@ -10,6 +10,7 @@ using Umbraco.Web.Security; namespace Umbraco.Web.Compose { + // TODO: Move to netcore public sealed class BackOfficeUserAuditEventsComponent : IComponent { private readonly IAuditService _auditService; @@ -27,14 +28,14 @@ namespace Umbraco.Web.Compose { //BackOfficeUserManager.AccountLocked += ; //BackOfficeUserManager.AccountUnlocked += ; - BackOfficeOwinUserManager.ForgotPasswordRequested += OnForgotPasswordRequest; - BackOfficeOwinUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange; - BackOfficeOwinUserManager.LoginFailed += OnLoginFailed; + //BackOfficeOwinUserManager.ForgotPasswordRequested += OnForgotPasswordRequest; + //BackOfficeOwinUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange; + //BackOfficeOwinUserManager.LoginFailed += OnLoginFailed; //BackOfficeUserManager.LoginRequiresVerification += ; - BackOfficeOwinUserManager.LoginSuccess += OnLoginSuccess; - BackOfficeOwinUserManager.LogoutSuccess += OnLogoutSuccess; - BackOfficeOwinUserManager.PasswordChanged += OnPasswordChanged; - BackOfficeOwinUserManager.PasswordReset += OnPasswordReset; + //BackOfficeOwinUserManager.LoginSuccess += OnLoginSuccess; + //BackOfficeOwinUserManager.LogoutSuccess += OnLogoutSuccess; + //BackOfficeOwinUserManager.PasswordChanged += OnPasswordChanged; + //BackOfficeOwinUserManager.PasswordReset += OnPasswordReset; //BackOfficeUserManager.ResetAccessFailedCount += ; } @@ -42,14 +43,14 @@ namespace Umbraco.Web.Compose { //BackOfficeUserManager.AccountLocked -= ; //BackOfficeUserManager.AccountUnlocked -= ; - BackOfficeOwinUserManager.ForgotPasswordRequested -= OnForgotPasswordRequest; - BackOfficeOwinUserManager.ForgotPasswordChangedSuccess -= OnForgotPasswordChange; - BackOfficeOwinUserManager.LoginFailed -= OnLoginFailed; + //BackOfficeOwinUserManager.ForgotPasswordRequested -= OnForgotPasswordRequest; + //BackOfficeOwinUserManager.ForgotPasswordChangedSuccess -= OnForgotPasswordChange; + //BackOfficeOwinUserManager.LoginFailed -= OnLoginFailed; //BackOfficeUserManager.LoginRequiresVerification -= ; - BackOfficeOwinUserManager.LoginSuccess -= OnLoginSuccess; - BackOfficeOwinUserManager.LogoutSuccess -= OnLogoutSuccess; - BackOfficeOwinUserManager.PasswordChanged -= OnPasswordChanged; - BackOfficeOwinUserManager.PasswordReset -= OnPasswordReset; + //BackOfficeOwinUserManager.LoginSuccess -= OnLoginSuccess; + //BackOfficeOwinUserManager.LogoutSuccess -= OnLogoutSuccess; + //BackOfficeOwinUserManager.PasswordChanged -= OnPasswordChanged; + //BackOfficeOwinUserManager.PasswordReset -= OnPasswordReset; //BackOfficeUserManager.ResetAccessFailedCount -= ; } diff --git a/src/Umbraco.Web/Editors/PasswordChanger.cs b/src/Umbraco.Web/Editors/PasswordChanger.cs deleted file mode 100644 index be6a20e7cc..0000000000 --- a/src/Umbraco.Web/Editors/PasswordChanger.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Extensions; -using Umbraco.Web.Models; -using Umbraco.Web.Security; -using IUser = Umbraco.Core.Models.Membership.IUser; - -//Migrated to .NET CORE -namespace Umbraco.Web.Editors -{ - internal class PasswordChanger - { - private readonly ILogger _logger; - - public PasswordChanger(ILogger logger) - { - _logger = logger; - } - - /// - /// Changes the password for a user based on the many different rules and config options - /// - /// The user performing the password save action - /// The user who's password is being changed - /// - /// - /// - public async Task> ChangePasswordWithIdentityAsync( - IUser currentUser, - IUser savingUser, - ChangingPasswordModel passwordModel, - BackOfficeOwinUserManager userMgr) - { - if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel)); - if (userMgr == null) throw new ArgumentNullException(nameof(userMgr)); - - if (passwordModel.NewPassword.IsNullOrWhiteSpace()) - { - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) }); - } - - var backOfficeIdentityUser = await userMgr.FindByIdAsync(savingUser.Id.ToString()); - if (backOfficeIdentityUser == null) - { - //this really shouldn't ever happen... but just in case - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password could not be verified", new[] { "oldPassword" }) }); - } - - //Are we just changing another user's password? - if (passwordModel.OldPassword.IsNullOrWhiteSpace()) - { - //if it's the current user, the current user cannot reset their own password - if (currentUser.Username == savingUser.Username) - { - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset is not allowed", new[] { "value" }) }); - } - - //if the current user has access to reset/manually change the password - if (currentUser.HasSectionAccess(Umbraco.Core.Constants.Applications.Users) == false) - { - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("The current user is not authorized", new[] { "value" }) }); - } - - //ok, we should be able to reset it - var resetToken = await userMgr.GeneratePasswordResetTokenAsync(backOfficeIdentityUser); - - var resetResult = await userMgr.ChangePasswordWithResetAsync(savingUser.Id, resetToken, passwordModel.NewPassword); - - if (resetResult.Succeeded == false) - { - var errors = resetResult.Errors.ToErrorMessage(); - _logger.LogWarning("Could not reset user password {PasswordErrors}", errors); - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult(errors, new[] { "value" }) }); - } - - return Attempt.Succeed(new PasswordChangedModel()); - } - - //is the old password correct? - var validateResult = await userMgr.CheckPasswordAsync(backOfficeIdentityUser, passwordModel.OldPassword); - if (validateResult == false) - { - //no, fail with an error message for "oldPassword" - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Incorrect password", new[] { "oldPassword" }) }); - } - //can we change to the new password? - var changeResult = await userMgr.ChangePasswordAsync(backOfficeIdentityUser, passwordModel.OldPassword, passwordModel.NewPassword); - if (changeResult.Succeeded == false) - { - //no, fail with error messages for "password" - var errors = changeResult.Errors.ToErrorMessage(); - _logger.LogWarning("Could not change user password {PasswordErrors}", errors); - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult(errors, new[] { "password" }) }); - } - return Attempt.Succeed(new PasswordChangedModel()); - } - - } -} diff --git a/src/Umbraco.Web/OwinExtensions.cs b/src/Umbraco.Web/OwinExtensions.cs index 62f1643074..801ceae191 100644 --- a/src/Umbraco.Web/OwinExtensions.cs +++ b/src/Umbraco.Web/OwinExtensions.cs @@ -52,25 +52,6 @@ namespace Umbraco.Web return ctx == null ? Attempt.Fail() : Attempt.Succeed(ctx); } - - /// - /// Gets the back office user manager out of OWIN - /// - /// - /// - /// - /// This is required because to extract the user manager we need to user a custom service since owin only deals in generics and - /// developers could register their own user manager types - /// - public static BackOfficeOwinUserManager GetBackOfficeUserManager(this IOwinContext owinContext) - { - var marker = owinContext.Get(BackOfficeOwinUserManager.OwinMarkerKey) - ?? throw new NullReferenceException($"No {typeof (IBackOfficeUserManagerMarker)}, i.e. no Umbraco back-office, has been registered with Owin."); - - return marker.GetManager(owinContext) - ?? throw new NullReferenceException($"Could not resolve an instance of {typeof (BackOfficeOwinUserManager)} from the {typeof (IOwinContext)}."); - } - /// /// Adapted from Microsoft.AspNet.Identity.Owin.OwinContextExtensions /// diff --git a/src/Umbraco.Web/Security/BackOfficeOwinUserManager.cs b/src/Umbraco.Web/Security/BackOfficeOwinUserManager.cs deleted file mode 100644 index 2f5e858687..0000000000 --- a/src/Umbraco.Web/Security/BackOfficeOwinUserManager.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Security.Claims; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.Owin.Security.DataProtection; -using Umbraco.Core; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Mapping; -using Umbraco.Core.Services; -using Umbraco.Net; - -namespace Umbraco.Web.Security -{ - // TODO: Most of this is already migrated to netcore, there's probably not much more to go and then we can complete remove it - public class BackOfficeOwinUserManager : BackOfficeUserManager - { - public const string OwinMarkerKey = "Umbraco.Web.Security.Identity.BackOfficeUserManagerMarker"; - - public BackOfficeOwinUserManager( - IOptions passwordConfiguration, - IIpResolver ipResolver, - IUserStore store, - IOptions optionsAccessor, - IEnumerable> userValidators, - IEnumerable> passwordValidators, - BackOfficeLookupNormalizer keyNormalizer, - BackOfficeIdentityErrorDescriber errors, - IDataProtectionProvider dataProtectionProvider, - ILogger> logger) - : base(ipResolver, store, optionsAccessor, null, userValidators, passwordValidators, keyNormalizer, errors, null, logger, passwordConfiguration) - { - PasswordConfiguration = passwordConfiguration.Value; - InitUserManager(this, dataProtectionProvider); - } - - #region Static Create methods - - /// - /// Creates a BackOfficeUserManager instance with all default options and the default BackOfficeUserManager - /// - public static BackOfficeOwinUserManager Create( - IUserService userService, - IEntityService entityService, - IExternalLoginService externalLoginService, - IOptions globalSettings, - UmbracoMapper mapper, - IOptions passwordConfiguration, - IIpResolver ipResolver, - BackOfficeIdentityErrorDescriber errors, - IDataProtectionProvider dataProtectionProvider, - ILogger> logger) - { - var store = new BackOfficeUserStore(userService, entityService, externalLoginService, globalSettings, mapper); - - return Create( - passwordConfiguration, - ipResolver, - store, - errors, - dataProtectionProvider, - logger); - } - - /// - /// Creates a BackOfficeUserManager instance with all default options and a custom BackOfficeUserManager instance - /// - public static BackOfficeOwinUserManager Create( - IOptions passwordConfiguration, - IIpResolver ipResolver, - IUserStore customUserStore, - BackOfficeIdentityErrorDescriber errors, - IDataProtectionProvider dataProtectionProvider, - ILogger> logger) - { - var options = new BackOfficeIdentityOptions(); - - // Configure validation logic for usernames - var userValidators = new List> { new BackOfficeUserValidator() }; - options.User.RequireUniqueEmail = true; - - // Configure validation logic for passwords - var passwordValidators = new List> { new PasswordValidator() }; - options.Password.RequiredLength = passwordConfiguration.Value.RequiredLength; - options.Password.RequireNonAlphanumeric = passwordConfiguration.Value.RequireNonLetterOrDigit; - options.Password.RequireDigit = passwordConfiguration.Value.RequireDigit; - options.Password.RequireLowercase = passwordConfiguration.Value.RequireLowercase; - options.Password.RequireUppercase = passwordConfiguration.Value.RequireUppercase; - - // Ensure Umbraco security stamp claim type is used - options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier; - options.ClaimsIdentity.UserNameClaimType = ClaimTypes.Name; - options.ClaimsIdentity.RoleClaimType = ClaimTypes.Role; - options.ClaimsIdentity.SecurityStampClaimType = Constants.Security.SecurityStampClaimType; - - options.Lockout.AllowedForNewUsers = true; - options.Lockout.MaxFailedAccessAttempts = passwordConfiguration.Value.MaxFailedAccessAttemptsBeforeLockout; - //NOTE: This just needs to be in the future, we currently don't support a lockout timespan, it's either they are locked - // or they are not locked, but this determines what is set on the account lockout date which corresponds to whether they are - // locked out or not. - options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromDays(30); - - return new BackOfficeOwinUserManager( - passwordConfiguration, - ipResolver, - customUserStore, - new OptionsWrapper(options), - userValidators, - passwordValidators, - new BackOfficeLookupNormalizer(), - errors, - dataProtectionProvider, - logger); - } - - #endregion - - protected void InitUserManager(BackOfficeOwinUserManager manager, IDataProtectionProvider dataProtectionProvider) - { - // use a custom hasher based on our membership provider - PasswordHasher = GetDefaultPasswordHasher(PasswordConfiguration); - - // set OWIN data protection token provider as default - if (dataProtectionProvider != null) - { - manager.RegisterTokenProvider( - TokenOptions.DefaultProvider, - new OwinDataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")) - { - TokenLifespan = TimeSpan.FromDays(3) - }); - } - - // register ASP.NET Core Identity token providers - manager.RegisterTokenProvider(TokenOptions.DefaultEmailProvider, new EmailTokenProvider()); - manager.RegisterTokenProvider(TokenOptions.DefaultPhoneProvider, new PhoneNumberTokenProvider()); - manager.RegisterTokenProvider(TokenOptions.DefaultAuthenticatorProvider, new AuthenticatorTokenProvider()); - } - } -} diff --git a/src/Umbraco.Web/Security/BackOfficeUserManagerMarker.cs b/src/Umbraco.Web/Security/BackOfficeUserManagerMarker.cs deleted file mode 100644 index dd657b48bf..0000000000 --- a/src/Umbraco.Web/Security/BackOfficeUserManagerMarker.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using Microsoft.Owin; -using Umbraco.Core.BackOffice; - -namespace Umbraco.Web.Security -{ - /// - /// This class is only here due to the fact that IOwinContext Get / Set only work in generics, if they worked - /// with regular 'object' then we wouldn't have to use this work around but because of that we have to use this - /// class to resolve the 'real' type of the registered user manager - /// - /// - /// - internal class BackOfficeUserManagerMarker : IBackOfficeUserManagerMarker - where TManager : BackOfficeUserManager - where TUser : BackOfficeIdentityUser - { - public BackOfficeOwinUserManager GetManager(IOwinContext owin) - { - var mgr = owin.Get() as BackOfficeOwinUserManager; - if (mgr == null) throw new InvalidOperationException("Could not cast the registered back office user of type " + typeof(TManager) + " to " + typeof(BackOfficeUserManager)); - return mgr; - } - } -} diff --git a/src/Umbraco.Web/Security/IBackOfficeUserManagerMarker.cs b/src/Umbraco.Web/Security/IBackOfficeUserManagerMarker.cs deleted file mode 100644 index 16c0666c9c..0000000000 --- a/src/Umbraco.Web/Security/IBackOfficeUserManagerMarker.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.Owin; - -namespace Umbraco.Web.Security -{ - /// - /// This interface is only here due to the fact that IOwinContext Get / Set only work in generics, if they worked - /// with regular 'object' then we wouldn't have to use this work around but because of that we have to use this - /// class to resolve the 'real' type of the registered user manager - /// - internal interface IBackOfficeUserManagerMarker - { - BackOfficeOwinUserManager GetManager(IOwinContext owin); - } -} diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index 1e26782d4a..65251320ca 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -18,9 +18,7 @@ using Umbraco.Web.Security.Providers; namespace Umbraco.Web.Security { - /// - /// A helper class for handling Members - /// + // MIGRATED TO NETCORE public class MembershipHelper { private readonly MembersMembershipProvider _membershipProvider; @@ -680,37 +678,6 @@ namespace Umbraco.Web.Security return allowAction; } - /// - /// Changes password for a member/user given the membership provider name and the password change model - /// - /// - /// - /// - /// - public virtual Attempt ChangePassword(string username, ChangingPasswordModel passwordModel, string membershipProviderName) - { - var provider = Membership.Providers[membershipProviderName]; - if (provider == null) - { - throw new InvalidOperationException("Could not find provider with name " + membershipProviderName); - } - - return ChangePassword(username, passwordModel, provider); - } - - /// - /// Changes password for a member/user given the membership provider and the password change model - /// - /// - /// - /// - /// - public virtual Attempt ChangePassword(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) - { - var passwordChanger = new PasswordChanger(_loggerFactory.CreateLogger()); - return ChangePasswordWithMembershipProvider(username, passwordModel, membershipProvider); - } - /// /// Updates a membership user with all of it's writable properties /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 1b4ec244d8..7a2ec6e0f7 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -137,7 +137,6 @@ - @@ -164,8 +163,6 @@ - - @@ -181,7 +178,6 @@ - diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs index f744213276..37e552d818 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs @@ -31,8 +31,6 @@ namespace Umbraco.Web.WebApi [EnableDetailedErrors] public abstract class UmbracoAuthorizedApiController : UmbracoApiController { - private BackOfficeOwinUserManager _userManager; - protected UmbracoAuthorizedApiController() { } @@ -42,10 +40,5 @@ namespace Umbraco.Web.WebApi { } - /// - /// Gets the user manager. - /// - protected BackOfficeOwinUserManager UserManager - => _userManager ?? (_userManager = TryGetOwinContext().Result.GetBackOfficeUserManager()); } } From 4671d9d23b5217161abdb4bf5c76dc4caeecf231 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 1 Dec 2020 17:51:52 +1100 Subject: [PATCH 26/37] moves the back office user auditing logic --- .../BackOffice/IBackOfficeUserManager.cs | 13 ++ .../BackOfficeApplicationBuilderExtensions.cs | 11 ++ .../BackOfficeServiceCollectionExtensions.cs | 1 + .../Security/BackOfficeUserManager.cs | 1 + .../Security/BackOfficeUserManagerAuditer.cs | 163 ++++++++++++++++++ .../BackOfficeUserAuditEventsComponent.cs | 163 ------------------ .../BackOfficeUserAuditEventsComposer.cs | 7 - .../IUmbracoBackOfficeTwoFactorOptions.cs | 12 -- src/Umbraco.Web/Umbraco.Web.csproj | 3 - 9 files changed, 189 insertions(+), 185 deletions(-) create mode 100644 src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs delete mode 100644 src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs delete mode 100644 src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComposer.cs delete mode 100644 src/Umbraco.Web/Security/IUmbracoBackOfficeTwoFactorOptions.cs diff --git a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs b/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs index 664c957f57..47de2e3956 100644 --- a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs +++ b/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs @@ -320,5 +320,18 @@ namespace Umbraco.Core.BackOffice UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser); bool HasSendingUserInviteEventHandler { get; } + + + event EventHandler AccountLocked; + event EventHandler AccountUnlocked; + event EventHandler ForgotPasswordRequested; + event EventHandler ForgotPasswordChangedSuccess; + event EventHandler LoginFailed; + event EventHandler LoginRequiresVerification; + event EventHandler LoginSuccess; + event EventHandler LogoutSuccess; + event EventHandler PasswordChanged; + event EventHandler PasswordReset; + event EventHandler ResetAccessFailedCount; } } diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs index d32351fdc6..a097ead4a1 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs @@ -2,8 +2,10 @@ 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; namespace Umbraco.Extensions { @@ -30,6 +32,8 @@ namespace Umbraco.Extensions { if (app == null) throw new ArgumentNullException(nameof(app)); + app.UseBackOfficeUserManagerAuditing(); + // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. // TODO: Since we are dependent on these we need to register them but what happens when we call this multiple times since we are dependent on this for UseUmbracoBackOffice too? app.UseImageSharp(); @@ -64,5 +68,12 @@ namespace Umbraco.Extensions return app; } + + private static IApplicationBuilder UseBackOfficeUserManagerAuditing(this IApplicationBuilder app) + { + var auditer = app.ApplicationServices.GetRequiredService(); + auditer.Start(); + return app; + } } } diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs index 8d3223a79f..ac844b0340 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs @@ -64,6 +64,7 @@ namespace Umbraco.Extensions services.TryAddScoped(); services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); /* * IdentityBuilderExtensions.AddUserManager adds UserManager to service collection diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs index 3501242eb5..30deb3d436 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs @@ -20,6 +20,7 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Common.Security { + public class BackOfficeUserManager : BackOfficeUserManager, IBackOfficeUserManager { public BackOfficeUserManager( diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs new file mode 100644 index 0000000000..afa50ee8cd --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs @@ -0,0 +1,163 @@ +using System; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Core.BackOffice; +using Umbraco.Core.Compose; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Common.Security +{ + /// + /// Binds to events to write audit logs for the + /// + internal class BackOfficeUserManagerAuditer : IDisposable + { + private readonly IBackOfficeUserManager _backOfficeUserManager; + private readonly IAuditService _auditService; + private readonly IUserService _userService; + private readonly GlobalSettings _globalSettings; + private bool _disposedValue; + + public BackOfficeUserManagerAuditer(IBackOfficeUserManager backOfficeUserManager, IAuditService auditService, IUserService userService, GlobalSettings globalSettings) + { + _backOfficeUserManager = backOfficeUserManager; + _auditService = auditService; + _userService = userService; + _globalSettings = globalSettings; + } + + /// + /// Binds to events to start auditing + /// + public void Start() + { + // NOTE: This was migrated as-is from v8 including these missing entries + //_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) + { + var found = userId >= 0 ? _userService.GetUserById(userId) : null; + return found ?? AuditEventsComponent.UnknownUser(_globalSettings); + } + + private static string FormatEmail(IMembershipUser user) + { + return user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; + } + + private void OnLoginSuccess(object sender, IdentityAuditEventArgs args) + { + var performingUser = GetPerformingUser(args.PerformingUser); + WriteAudit(performingUser, args.AffectedUser, args.IpAddress, "umbraco/user/sign-in/login", "login success"); + } + + private void OnLogoutSuccess(object sender, IdentityAuditEventArgs args) + { + var 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 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 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 WriteAudit(int performingId, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) + { + var performingUser = _userService.GetUserById(performingId); + + var performingDetails = performingUser == null + ? $"User UNKNOWN:{performingId}" + : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; + + WriteAudit(performingId, performingDetails, affectedId, ipAddress, eventType, eventDetails, affectedDetails); + } + + private void WriteAudit(IUser performingUser, int 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); + } + + 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); + affectedDetails = affectedUser == null + ? $"User UNKNOWN:{affectedId}" + : $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}"; + } + + _auditService.Write(performingId, performingDetails, + ipAddress, + DateTime.UtcNow, + affectedId, affectedDetails, + eventType, eventDetails); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + 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; + _backOfficeUserManager.PasswordReset -= OnPasswordReset; + } + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs deleted file mode 100644 index d8210556fd..0000000000 --- a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using Umbraco.Core; -using Umbraco.Core.Compose; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; -using Umbraco.Web.Security; - -namespace Umbraco.Web.Compose -{ - // TODO: Move to netcore - public sealed class BackOfficeUserAuditEventsComponent : IComponent - { - private readonly IAuditService _auditService; - private readonly IUserService _userService; - private readonly GlobalSettings _globalSettings; - - public BackOfficeUserAuditEventsComponent(IAuditService auditService, IUserService userService, GlobalSettings globalSettings) - { - _auditService = auditService; - _userService = userService; - _globalSettings = globalSettings; - } - - public void Initialize() - { - //BackOfficeUserManager.AccountLocked += ; - //BackOfficeUserManager.AccountUnlocked += ; - //BackOfficeOwinUserManager.ForgotPasswordRequested += OnForgotPasswordRequest; - //BackOfficeOwinUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange; - //BackOfficeOwinUserManager.LoginFailed += OnLoginFailed; - //BackOfficeUserManager.LoginRequiresVerification += ; - //BackOfficeOwinUserManager.LoginSuccess += OnLoginSuccess; - //BackOfficeOwinUserManager.LogoutSuccess += OnLogoutSuccess; - //BackOfficeOwinUserManager.PasswordChanged += OnPasswordChanged; - //BackOfficeOwinUserManager.PasswordReset += OnPasswordReset; - //BackOfficeUserManager.ResetAccessFailedCount += ; - } - - public void Terminate() - { - //BackOfficeUserManager.AccountLocked -= ; - //BackOfficeUserManager.AccountUnlocked -= ; - //BackOfficeOwinUserManager.ForgotPasswordRequested -= OnForgotPasswordRequest; - //BackOfficeOwinUserManager.ForgotPasswordChangedSuccess -= OnForgotPasswordChange; - //BackOfficeOwinUserManager.LoginFailed -= OnLoginFailed; - //BackOfficeUserManager.LoginRequiresVerification -= ; - //BackOfficeOwinUserManager.LoginSuccess -= OnLoginSuccess; - //BackOfficeOwinUserManager.LogoutSuccess -= OnLogoutSuccess; - //BackOfficeOwinUserManager.PasswordChanged -= OnPasswordChanged; - //BackOfficeOwinUserManager.PasswordReset -= OnPasswordReset; - //BackOfficeUserManager.ResetAccessFailedCount -= ; - } - - private IUser GetPerformingUser(int userId) - { - var found = userId >= 0 ? _userService.GetUserById(userId) : null; - return found ?? AuditEventsComponent.UnknownUser(_globalSettings); - } - - private static string FormatEmail(IMembershipUser user) - { - return user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; - } - - private void OnLoginSuccess(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs) - { - var performingUser = GetPerformingUser(identityArgs.PerformingUser); - WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/login", "login success"); - } - } - - private void OnLogoutSuccess(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs) - { - var performingUser = GetPerformingUser(identityArgs.PerformingUser); - WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/logout", "logout success"); - } - } - - private void OnPasswordReset(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) - { - WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/reset", "password reset"); - } - } - - private void OnPasswordChanged(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) - { - WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/change", "password change"); - } - } - - private void OnLoginFailed(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) - { - WriteAudit(identityArgs.PerformingUser, 0, identityArgs.IpAddress, "umbraco/user/sign-in/failed", "login failed", affectedDetails: ""); - } - } - - private void OnForgotPasswordChange(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) - { - WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/forgot/change", "password forgot/change"); - } - } - - private void OnForgotPasswordRequest(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) - { - WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.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) - { - var performingUser = _userService.GetUserById(performingId); - - var performingDetails = performingUser == null - ? $"User UNKNOWN:{performingId}" - : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; - - WriteAudit(performingId, performingDetails, affectedId, ipAddress, eventType, eventDetails, affectedDetails); - } - - private void WriteAudit(IUser performingUser, int 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); - } - - 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); - affectedDetails = affectedUser == null - ? $"User UNKNOWN:{affectedId}" - : $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}"; - } - - _auditService.Write(performingId, performingDetails, - ipAddress, - DateTime.UtcNow, - affectedId, affectedDetails, - eventType, eventDetails); - } - } -} diff --git a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComposer.cs b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComposer.cs deleted file mode 100644 index 38065f98ce..0000000000 --- a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComposer.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Umbraco.Core.Composing; - -namespace Umbraco.Web.Compose -{ - public sealed class BackOfficeUserAuditEventsComposer : ComponentComposer, ICoreComposer - { } -} diff --git a/src/Umbraco.Web/Security/IUmbracoBackOfficeTwoFactorOptions.cs b/src/Umbraco.Web/Security/IUmbracoBackOfficeTwoFactorOptions.cs deleted file mode 100644 index 0b43342594..0000000000 --- a/src/Umbraco.Web/Security/IUmbracoBackOfficeTwoFactorOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Owin; - -namespace Umbraco.Web.Security -{ - /// - /// Used to display a custom view in the back office if developers choose to implement their own custom 2 factor authentication - /// - public interface IUmbracoBackOfficeTwoFactorOptions - { - string GetTwoFactorView(IOwinContext owinContext, IUmbracoContext umbracoContext, string username); - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7a2ec6e0f7..8316c20418 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -135,8 +135,6 @@ - - @@ -212,7 +210,6 @@ - From fe5dcd83bb9e60a9b4537fbfa26b4b60bb52a2c5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 1 Dec 2020 18:14:37 +1100 Subject: [PATCH 27/37] removes the 2FA store implementation since that will need to be manually enabled --- .../BackOffice/BackOfficeUserStore.cs | 86 +++++++------------ .../BackOffice/IBackOfficeUserManager.cs | 13 --- .../Controllers/AuthenticationController.cs | 4 +- .../Security/BackOfficeUserManager.cs | 25 +++--- .../Security/BackOfficeUserManagerAuditer.cs | 52 +++++------ 5 files changed, 73 insertions(+), 107 deletions(-) diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs index cdd81a3fe3..b271f5aa41 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs +++ b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Scoping; using Umbraco.Core.Services; namespace Umbraco.Core.BackOffice @@ -22,15 +23,17 @@ namespace Umbraco.Core.BackOffice IUserLoginStore, IUserRoleStore, IUserSecurityStampStore, - IUserLockoutStore, - IUserTwoFactorStore, + IUserLockoutStore, IUserSessionStore - // 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 + // 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 { + private readonly IScopeProvider _scopeProvider; private readonly IUserService _userService; private readonly IEntityService _entityService; private readonly IExternalLoginService _externalLoginService; @@ -38,8 +41,9 @@ namespace Umbraco.Core.BackOffice private readonly UmbracoMapper _mapper; private bool _disposed = false; - public BackOfficeUserStore(IUserService userService, IEntityService entityService, IExternalLoginService externalLoginService, IOptions globalSettings, UmbracoMapper mapper) + public BackOfficeUserStore(IScopeProvider scopeProvider, IUserService userService, IEntityService entityService, IExternalLoginService externalLoginService, IOptions globalSettings, UmbracoMapper mapper) { + _scopeProvider = scopeProvider; _userService = userService; _entityService = entityService; _externalLoginService = externalLoginService; @@ -168,28 +172,31 @@ namespace Umbraco.Core.BackOffice throw new InvalidOperationException("The user id must be an integer to work with the Umbraco"); } - // TODO: Wrap this in a scope! - - var found = _userService.GetUserById(asInt.Result); - if (found != null) + using (var scope = _scopeProvider.CreateScope()) { - // we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it. - var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(BackOfficeIdentityUser.Logins)); - - if (UpdateMemberProperties(found, user)) + var found = _userService.GetUserById(asInt.Result); + if (found != null) { - _userService.Save(found); + // we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it. + var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(BackOfficeIdentityUser.Logins)); + + if (UpdateMemberProperties(found, user)) + { + _userService.Save(found); + } + + if (isLoginsPropertyDirty) + { + _externalLoginService.Save( + found.Id, + user.Logins.Select(x => new ExternalLogin( + x.LoginProvider, + x.ProviderKey, + x.UserData))); + } } - if (isLoginsPropertyDirty) - { - _externalLoginService.Save( - found.Id, - user.Logins.Select(x => new ExternalLogin( - x.LoginProvider, - x.ProviderKey, - x.UserData))); - } + scope.Complete(); } return Task.FromResult(IdentityResult.Success); @@ -627,35 +634,6 @@ namespace Umbraco.Core.BackOffice return user; } - /// - /// Sets whether two factor authentication is enabled for the user - /// - /// - /// - /// - /// - public virtual Task SetTwoFactorEnabledAsync(BackOfficeIdentityUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - - user.TwoFactorEnabled = false; - return Task.CompletedTask; - } - - /// - /// Returns whether two factor authentication is enabled for the user - /// - /// - /// - public virtual Task GetTwoFactorEnabledAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - - return Task.FromResult(false); - } - #region IUserLockoutStore /// diff --git a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs b/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs index 47de2e3956..c026c256f5 100644 --- a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs +++ b/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs @@ -318,20 +318,7 @@ namespace Umbraco.Core.BackOffice void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, int userId); SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId); UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser); - bool HasSendingUserInviteEventHandler { get; } - - event EventHandler AccountLocked; - event EventHandler AccountUnlocked; - event EventHandler ForgotPasswordRequested; - event EventHandler ForgotPasswordChangedSuccess; - event EventHandler LoginFailed; - event EventHandler LoginRequiresVerification; - event EventHandler LoginSuccess; - event EventHandler LogoutSuccess; - event EventHandler PasswordChanged; - event EventHandler PasswordReset; - event EventHandler ResetAccessFailedCount; } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 5c95be2afc..8275d427da 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -340,9 +340,7 @@ namespace Umbraco.Web.BackOffice.Controllers StatusCode = StatusCodes.Status402PaymentRequired }; - - - //return verifyResponse; + return verifyResponse; } // return BadRequest (400), we don't want to return a 401 because that get's intercepted diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs index 30deb3d436..e774c39eb5 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs @@ -436,17 +436,20 @@ namespace Umbraco.Web.Common.Security public bool HasSendingUserInviteEventHandler => SendingUserInvite != null; - public event EventHandler AccountLocked; - public event EventHandler AccountUnlocked; - public event EventHandler ForgotPasswordRequested; - public event EventHandler ForgotPasswordChangedSuccess; - public event EventHandler LoginFailed; - public event EventHandler LoginRequiresVerification; - public event EventHandler LoginSuccess; - public event EventHandler LogoutSuccess; - public event EventHandler PasswordChanged; - public event EventHandler PasswordReset; - public event EventHandler ResetAccessFailedCount; + // TODO: These static events are problematic. Moving forward we don't want static events at all but we cannot + // have non-static events here because the user manager is a Scoped instance not a singleton + // so we'll have to deal with this a diff way i.e. refactoring how events are done entirely + public static event EventHandler AccountLocked; + public static event EventHandler AccountUnlocked; + public static event EventHandler ForgotPasswordRequested; + public static event EventHandler ForgotPasswordChangedSuccess; + public static event EventHandler LoginFailed; + public static event EventHandler LoginRequiresVerification; + public static event EventHandler LoginSuccess; + public static event EventHandler LogoutSuccess; + public static event EventHandler PasswordChanged; + public static event EventHandler PasswordReset; + public static event EventHandler ResetAccessFailedCount; /// /// Raised when a user is invited diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs index afa50ee8cd..019eed7e39 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.Options; +using System; using System.Threading.Tasks; using Umbraco.Core; using Umbraco.Core.BackOffice; @@ -14,18 +15,16 @@ namespace Umbraco.Web.Common.Security /// internal class BackOfficeUserManagerAuditer : IDisposable { - private readonly IBackOfficeUserManager _backOfficeUserManager; private readonly IAuditService _auditService; private readonly IUserService _userService; private readonly GlobalSettings _globalSettings; private bool _disposedValue; - public BackOfficeUserManagerAuditer(IBackOfficeUserManager backOfficeUserManager, IAuditService auditService, IUserService userService, GlobalSettings globalSettings) + public BackOfficeUserManagerAuditer(IAuditService auditService, IUserService userService, IOptions globalSettings) { - _backOfficeUserManager = backOfficeUserManager; _auditService = auditService; _userService = userService; - _globalSettings = globalSettings; + _globalSettings = globalSettings.Value; } /// @@ -34,17 +33,18 @@ namespace Umbraco.Web.Common.Security public void Start() { // NOTE: This was migrated as-is from v8 including these missing entries - //_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 += ; + // 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) @@ -138,16 +138,16 @@ 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; - _backOfficeUserManager.PasswordReset -= OnPasswordReset; + //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; } _disposedValue = true; } From 999be04285587bbd48378869451a61b557d56d5c Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 2 Dec 2020 12:22:08 +1100 Subject: [PATCH 28/37] cleaning up TODOs --- .../Controllers/AuthenticationController.cs | 7 ++- .../Controllers/BackOfficeController.cs | 46 +++++++++++++++---- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 8275d427da..76288670aa 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -69,7 +69,6 @@ namespace Umbraco.Web.BackOffice.Controllers private readonly IBackOfficeExternalLoginProviders _externalAuthenticationOptions; private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions; - // TODO: We need to import the logic from Umbraco.Web.Editors.AuthenticationController // TODO: We need to review all _userManager.Raise calls since many/most should be on the usermanager or signinmanager, very few should be here public AuthenticationController( @@ -480,14 +479,14 @@ namespace Umbraco.Web.BackOffice.Controllers if (result.IsLockedOut) { - throw HttpResponseException.CreateValidationErrorResponse("User is locked out"); + return new ValidationErrorResult("User is locked out"); } if (result.IsNotAllowed) { - throw HttpResponseException.CreateValidationErrorResponse("User is not allowed"); + return new ValidationErrorResult("User is not allowed"); } - throw HttpResponseException.CreateValidationErrorResponse("Invalid code"); + return new ValidationErrorResult("Invalid code"); } /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index b25bc6d3e7..7704dcf785 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; @@ -33,8 +32,8 @@ using Constants = Umbraco.Core.Constants; using Microsoft.AspNetCore.Identity; using System.Security.Claims; using Microsoft.AspNetCore.Http; -using Umbraco.Web.Security; using Umbraco.Web.BackOffice.Security; +using Umbraco.Web.Common.ActionsResults; namespace Umbraco.Web.BackOffice.Controllers { @@ -58,6 +57,7 @@ namespace Umbraco.Web.BackOffice.Controllers private readonly IJsonSerializer _jsonSerializer; private readonly IBackOfficeExternalLoginProviders _externalLogins; private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions; public BackOfficeController( IBackOfficeUserManager userManager, @@ -73,7 +73,8 @@ namespace Umbraco.Web.BackOffice.Controllers ILogger logger, IJsonSerializer jsonSerializer, IBackOfficeExternalLoginProviders externalLogins, - IHttpContextAccessor httpContextAccessor) + IHttpContextAccessor httpContextAccessor, + IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions) { _userManager = userManager; _runtimeMinifier = runtimeMinifier; @@ -89,6 +90,7 @@ namespace Umbraco.Web.BackOffice.Controllers _jsonSerializer = jsonSerializer; _externalLogins = externalLogins; _httpContextAccessor = httpContextAccessor; + _backOfficeTwoFactorOptions = backOfficeTwoFactorOptions; } [HttpGet] @@ -407,18 +409,44 @@ namespace Umbraco.Web.BackOffice.Controllers if (result == Microsoft.AspNetCore.Identity.SignInResult.Success) { + } + else if (result == Microsoft.AspNetCore.Identity.SignInResult.TwoFactorRequired) + { + + var attemptedUser = await _userManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey); + if (attemptedUser == null) + { + return new ValidationErrorResult($"No local user found for the login provider {loginInfo.LoginProvider} - {loginInfo.ProviderKey}"); + } + + var twofactorView = _backOfficeTwoFactorOptions.GetTwoFactorView(attemptedUser.UserName); + if (twofactorView.IsNullOrWhiteSpace()) + { + return new ValidationErrorResult($"The registered {typeof(IBackOfficeTwoFactorOptions)} of type {_backOfficeTwoFactorOptions.GetType()} did not return a view for two factor auth "); + } + + // create a with information to display a custom two factor send code view + var verifyResponse = new ObjectResult(new + { + twoFactorView = twofactorView, + userId = attemptedUser.Id + }) + { + StatusCode = StatusCodes.Status402PaymentRequired + }; + + return verifyResponse; + } else if (result == Microsoft.AspNetCore.Identity.SignInResult.LockedOut) { - // TODO: We've never actually dealt with this before - } - else if (result == Microsoft.AspNetCore.Identity.SignInResult.TwoFactorRequired) - { - // TODO: We've never actually dealt with this before + errors.Add($"The local user {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} is locked out."); } else if (result == Microsoft.AspNetCore.Identity.SignInResult.NotAllowed) { - // TODO: We've never actually dealt with this before + // This occurs when SignInManager.CanSignInAsync fails which is when RequireConfirmedEmail , RequireConfirmedPhoneNumber or RequireConfirmedAccount fails + // however since we don't enforce those rules (yet) this shouldn't happen. + errors.Add($"The user {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} has not confirmed their details and cannot sign in."); } else if (result == Microsoft.AspNetCore.Identity.SignInResult.Failed) { From 37cf0d3d1667989cb8a2d9d353cbdd541d8da151 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 2 Dec 2020 13:04:02 +1100 Subject: [PATCH 29/37] clean up TODOs --- .../Extensions/UmbracoBuilderExtensions.cs | 9 +++++++-- .../Security/BackOfficeExternalLoginProviders.cs | 5 +++++ .../Security/BackOfficeSignInManager.cs | 9 ++------- .../Security/BackOfficeUserManager.cs | 2 -- .../Security/IBackOfficeExternalLoginProviders.cs | 9 +++++++++ 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs index fe6eacbb35..c47f2ab3e3 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs @@ -40,10 +40,15 @@ namespace Umbraco.Extensions { o.Cookie.Name = Core.Constants.Security.BackOfficeExternalAuthenticationType; o.ExpireTimeSpan = TimeSpan.FromMinutes(5); + }) + // Although we don't natively support this, we add it anyways so that if end-users implement the required logic + // they don't have to worry about manually adding this scheme or modifying the sign in manager + .AddCookie(Core.Constants.Security.BackOfficeTwoFactorAuthenticationType, o => + { + o.Cookie.Name = Core.Constants.Security.BackOfficeTwoFactorAuthenticationType; + o.ExpireTimeSpan = TimeSpan.FromMinutes(5); }); - // TODO: Need to add more cookie options, see https://github.com/dotnet/aspnetcore/blob/3.0/src/Identity/Core/src/IdentityServiceCollectionExtensions.cs#L45 - builder.Services.ConfigureOptions(); return builder; } diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs index 08ef32dc57..21c94308dd 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs @@ -3,6 +3,7 @@ using System.Linq; namespace Umbraco.Web.BackOffice.Security { + /// public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProviders { public BackOfficeExternalLoginProviders(IEnumerable externalLogins) @@ -12,22 +13,26 @@ namespace Umbraco.Web.BackOffice.Security private readonly IEnumerable _externalLogins; + /// public BackOfficeExternalLoginProvider Get(string authenticationType) { return _externalLogins.FirstOrDefault(x => x.AuthenticationType == authenticationType); } + /// public string GetAutoLoginProvider() { var found = _externalLogins.Where(x => x.Options.AutoRedirectLoginToExternalProvider).ToList(); return found.Count > 0 ? found[0].AuthenticationType : null; } + /// public IEnumerable GetBackOfficeProviders() { return _externalLogins; } + /// public bool HasDenyLocalLogin() { var found = _externalLogins.Where(x => x.Options.DenyLocalLogin).ToList(); diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs index d2050e214f..e17067daa0 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs @@ -51,14 +51,10 @@ namespace Umbraco.Web.Common.Security _globalSettings = globalSettings.Value; } - // TODO: Need to migrate more from Umbraco.Web.Security.BackOfficeSignInManager - // Things like dealing with auto-linking, cookie options, and a ton of other stuff. Some might not need to be ported but it - // will be a case by case basis. - // Have a look into RefreshSignInAsync since we might be able to use this new functionality for auto-cookie renewal in our middleware, though + // TODO: Have a look into RefreshSignInAsync since we might be able to use this new functionality for auto-cookie renewal in our middleware, though // i suspect it's taken care of already. - /// public override async Task PasswordSignInAsync(BackOfficeIdentityUser user, string password, bool isPersistent, bool lockoutOnFailure) { @@ -201,8 +197,7 @@ namespace Umbraco.Web.Common.Security await Context.SignOutAsync(Constants.Security.BackOfficeAuthenticationType); await Context.SignOutAsync(Constants.Security.BackOfficeExternalAuthenticationType); - // TODO: Put this back in when we implement it - //await Context.SignOutAsync(Constants.Security.BackOfficeTwoFactorAuthenticationType); + await Context.SignOutAsync(Constants.Security.BackOfficeTwoFactorAuthenticationType); } diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs index e774c39eb5..761bf5c87c 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs @@ -407,8 +407,6 @@ namespace Umbraco.Web.Common.Security public void RaiseLoginFailedEvent(IPrincipal currentUser, int userId) => OnLoginFailed(CreateArgs(AuditEvent.LoginFailed, currentUser, userId, string.Empty)); - //public void RaiseInvalidLoginAttemptEvent(IPrincipal currentUser, string username) => OnLoginFailed(CreateArgs(AuditEvent.LoginFailed, currentUser, Constants.Security.SuperUserId, username)); - public void RaiseLoginRequiresVerificationEvent(IPrincipal currentUser, int userId) => OnLoginRequiresVerification(CreateArgs(AuditEvent.LoginRequiresVerification, currentUser, userId, string.Empty)); public void RaiseLoginSuccessEvent(IPrincipal currentUser, int userId) => OnLoginSuccess(CreateArgs(AuditEvent.LoginSucces, currentUser, userId, string.Empty)); diff --git a/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs index 2274df14a3..ff22b91b0a 100644 --- a/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs +++ b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs @@ -11,8 +11,17 @@ namespace Umbraco.Web.BackOffice.Security /// public interface IBackOfficeExternalLoginProviders { + /// + /// Get the for the specified scheme + /// + /// + /// BackOfficeExternalLoginProvider Get(string authenticationType); + /// + /// Get all registered + /// + /// IEnumerable GetBackOfficeProviders(); /// From 4f7c87d1d2bebe3272928242528e80a9fa3fa361 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 2 Dec 2020 13:19:08 +1100 Subject: [PATCH 30/37] Fix build issues after merge --- .../TestServerTest/TestAuthHandler.cs | 2 +- .../TestServerTest/UmbracoTestServerTestBase.cs | 6 +++--- .../Authorization/DenyLocalLoginHandler.cs | 1 + .../Extensions/UmbracoBuilderExtensions.cs | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs b/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs index 1f7340787e..1163d60055 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs @@ -33,7 +33,7 @@ namespace Umbraco.Tests.Integration.TestServerTest { var principal = await _backOfficeSignInManager.CreateUserPrincipalAsync(_fakeUser); - var ticket = new AuthenticationTicket(principal, TestAuthenticationScheme); + var ticket = new AuthenticationTicket(principal, Constants.Security.BackOfficeAuthenticationType); return AuthenticateResult.Success(ticket); } diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 5867e6522c..b5ab4cacbb 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -48,8 +48,8 @@ namespace Umbraco.Tests.Integration.TestServerTest builder.ConfigureTestServices(services => { // Add a test auth scheme with a test auth handler to authn and assign the user - services.AddAuthentication(TestAuthHandler.TestAuthenticationScheme) - .AddScheme(TestAuthHandler.TestAuthenticationScheme, options => { }); + services.AddAuthentication(Constants.Security.BackOfficeAuthenticationType) + .AddScheme(Constants.Security.BackOfficeAuthenticationType, options => { }); }); }); @@ -144,7 +144,7 @@ namespace Umbraco.Tests.Integration.TestServerTest .AddRuntimeMinifier() .AddBackOffice() .AddBackOfficeIdentity() - .AddBackOfficeAuthorizationPolicies(TestAuthHandler.TestAuthenticationScheme) + .AddBackOfficeAuthorizationPolicies() .AddPreviewSupport() //.WithMiniProfiler() // we don't want this running in tests .AddMvcAndRazor(mvcBuilding: mvcBuilder => diff --git a/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandler.cs index 771f462b8c..96341c5b1f 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandler.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Authorization; using System.Threading.Tasks; +using Umbraco.Web.BackOffice.Security; using Umbraco.Web.Common.Security; namespace Umbraco.Web.BackOffice.Authorization diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs index 35b7e8e859..85ec4dfac0 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs @@ -62,9 +62,9 @@ namespace Umbraco.Extensions return builder; } - public static IUmbracoBuilder AddBackOfficeAuthorizationPolicies(this IUmbracoBuilder builder, string backOfficeAuthenticationScheme = Umbraco.Core.Constants.Security.BackOfficeAuthenticationType) + public static IUmbracoBuilder AddBackOfficeAuthorizationPolicies(this IUmbracoBuilder builder) { - builder.Services.AddBackOfficeAuthorizationPolicies(backOfficeAuthenticationScheme); + builder.Services.AddBackOfficeAuthorizationPolicies(Umbraco.Core.Constants.Security.BackOfficeAuthenticationType); // TODO: See other TODOs in things like UmbracoApiControllerBase ... AFAIK all of this is only used for the back office // so IMO these controllers and the features auth policies should just be moved to the back office project and then this // ext method can be removed. From 372674abde2146f5c26915e97ee22123d2e73edf Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 2 Dec 2020 13:32:14 +1100 Subject: [PATCH 31/37] re-adds test scheme for test authn/authz --- .../TestServerTest/TestAuthHandler.cs | 5 +++-- .../TestServerTest/UmbracoTestServerTestBase.cs | 6 +++--- .../Extensions/UmbracoBuilderExtensions.cs | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs b/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs index 1163d60055..b9acd9529c 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs @@ -14,8 +14,9 @@ namespace Umbraco.Tests.Integration.TestServerTest { public class TestAuthHandler : AuthenticationHandler { - private readonly IBackOfficeSignInManager _backOfficeSignInManager; + public const string TestAuthenticationScheme = "Test"; + private readonly IBackOfficeSignInManager _backOfficeSignInManager; private readonly BackOfficeIdentityUser _fakeUser; public TestAuthHandler(IOptionsMonitor options, @@ -33,7 +34,7 @@ namespace Umbraco.Tests.Integration.TestServerTest { var principal = await _backOfficeSignInManager.CreateUserPrincipalAsync(_fakeUser); - var ticket = new AuthenticationTicket(principal, Constants.Security.BackOfficeAuthenticationType); + var ticket = new AuthenticationTicket(principal, TestAuthenticationScheme); return AuthenticateResult.Success(ticket); } diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index b5ab4cacbb..5867e6522c 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -48,8 +48,8 @@ namespace Umbraco.Tests.Integration.TestServerTest builder.ConfigureTestServices(services => { // Add a test auth scheme with a test auth handler to authn and assign the user - services.AddAuthentication(Constants.Security.BackOfficeAuthenticationType) - .AddScheme(Constants.Security.BackOfficeAuthenticationType, options => { }); + services.AddAuthentication(TestAuthHandler.TestAuthenticationScheme) + .AddScheme(TestAuthHandler.TestAuthenticationScheme, options => { }); }); }); @@ -144,7 +144,7 @@ namespace Umbraco.Tests.Integration.TestServerTest .AddRuntimeMinifier() .AddBackOffice() .AddBackOfficeIdentity() - .AddBackOfficeAuthorizationPolicies() + .AddBackOfficeAuthorizationPolicies(TestAuthHandler.TestAuthenticationScheme) .AddPreviewSupport() //.WithMiniProfiler() // we don't want this running in tests .AddMvcAndRazor(mvcBuilding: mvcBuilder => diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs index 85ec4dfac0..35b7e8e859 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs @@ -62,9 +62,9 @@ namespace Umbraco.Extensions return builder; } - public static IUmbracoBuilder AddBackOfficeAuthorizationPolicies(this IUmbracoBuilder builder) + public static IUmbracoBuilder AddBackOfficeAuthorizationPolicies(this IUmbracoBuilder builder, string backOfficeAuthenticationScheme = Umbraco.Core.Constants.Security.BackOfficeAuthenticationType) { - builder.Services.AddBackOfficeAuthorizationPolicies(Umbraco.Core.Constants.Security.BackOfficeAuthenticationType); + builder.Services.AddBackOfficeAuthorizationPolicies(backOfficeAuthenticationScheme); // TODO: See other TODOs in things like UmbracoApiControllerBase ... AFAIK all of this is only used for the back office // so IMO these controllers and the features auth policies should just be moved to the back office project and then this // ext method can be removed. From 0846fc56902dbac43dd1edf925dcc30d78acf1bb Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 2 Dec 2020 14:28:16 +1100 Subject: [PATCH 32/37] Cleans up IBackofficeSecurity, ensures authn for the AuthenticationController/BackOfficeController --- .../BackOffice/UmbracoBackOfficeIdentity.cs | 4 +++ .../Security/IBackofficeSecurity.cs | 29 +++++++-------- .../Security/ValidateRequestAttempt.cs | 14 -------- .../TestControllerActivatorBase.cs | 2 -- .../Authorization/BackOfficeHandler.cs | 21 ++++++----- .../Controllers/AuthenticationController.cs | 10 ++++++ .../Controllers/BackOfficeController.cs | 31 +++++++++++++--- .../Install/InstallAuthorizeAttribute.cs | 12 ++----- .../Install/InstallController.cs | 9 +++-- .../Security/BackofficeSecurity.cs | 36 ------------------- .../Mvc/UmbracoAuthorizeAttribute.cs | 2 +- .../Security/BackofficeSecurity.cs | 14 -------- .../WebApi/UmbracoAuthorizeAttribute.cs | 20 +++++------ 13 files changed, 82 insertions(+), 122 deletions(-) delete mode 100644 src/Umbraco.Core/Security/ValidateRequestAttempt.cs diff --git a/src/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentity.cs b/src/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentity.cs index 93acb279dc..9a60c5d64f 100644 --- a/src/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentity.cs +++ b/src/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentity.cs @@ -12,6 +12,10 @@ namespace Umbraco.Core.BackOffice [Serializable] public class UmbracoBackOfficeIdentity : ClaimsIdentity { + // 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 diff --git a/src/Umbraco.Core/Security/IBackofficeSecurity.cs b/src/Umbraco.Core/Security/IBackofficeSecurity.cs index 12d6dc1688..8d0e0df6d8 100644 --- a/src/Umbraco.Core/Security/IBackofficeSecurity.cs +++ b/src/Umbraco.Core/Security/IBackofficeSecurity.cs @@ -9,41 +9,38 @@ namespace Umbraco.Core.Security /// /// Gets the current user. /// - /// The current user. + /// The current user that has been authenticated for the request. + /// If authentication hasn't taken place this will be null. + // TODO: This is used a lot but most of it can be refactored to not use this at all since the IUser instance isn't + // needed in most cases. Where an IUser is required this could be an ext method on the ClaimsIdentity/ClaimsPrincipal that passes in + // an IUserService, like HttpContext.User.GetUmbracoUser(_userService); + // This one isn't as easy to remove as the others below. IUser CurrentUser { get; } /// /// Gets the current user's id. /// - /// + /// The current user's Id that has been authenticated for the request. + /// If authentication hasn't taken place this will be unsuccessful. + // TODO: This should just be an extension method on ClaimsIdentity Attempt GetUserId(); - /// - /// Validates the currently logged in user and ensures they are not timed out - /// - /// - bool ValidateCurrentUser(); - - /// - /// Validates the current user assigned to the request and ensures the stored user data is valid - /// - /// set to true if you want exceptions to be thrown if failed - /// If true requires that the user is approved to be validated - /// - ValidateRequestAttempt ValidateCurrentUser(bool throwExceptions, bool requiresApproval = true); - /// /// Checks if the specified user as access to the app /// /// /// /// + /// If authentication hasn't taken place this will be unsuccessful. + // TODO: Should be part of IBackOfficeUserManager bool UserHasSectionAccess(string section, IUser user); /// /// Ensures that a back office user is logged in /// /// + /// This does not force authentication, that must be done before calls to this are made. + // TODO: Should be removed, this should not be necessary bool IsAuthenticated(); } } diff --git a/src/Umbraco.Core/Security/ValidateRequestAttempt.cs b/src/Umbraco.Core/Security/ValidateRequestAttempt.cs deleted file mode 100644 index a88e18d463..0000000000 --- a/src/Umbraco.Core/Security/ValidateRequestAttempt.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Umbraco.Core.Security -{ - public enum ValidateRequestAttempt - { - Success = 0, - - FailedNoPrivileges = 100, - - //FailedTimedOut, - - FailedNoContextId = 101, - FailedNoSsl = 102 - } -} diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 664b00b513..23f7e09f5d 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -120,8 +120,6 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting .Returns(mockUser.Object); //mock Validate - backofficeSecurity.Setup(x => x.ValidateCurrentUser()) - .Returns(() => true); backofficeSecurity.Setup(x => x.UserHasSectionAccess(It.IsAny(), It.IsAny())) .Returns(() => true); diff --git a/src/Umbraco.Web.BackOffice/Authorization/BackOfficeHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/BackOfficeHandler.cs index 6cee04deae..065f60b3db 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/BackOfficeHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/BackOfficeHandler.cs @@ -22,18 +22,17 @@ namespace Umbraco.Web.BackOffice.Authorization protected override Task IsAuthorized(AuthorizationHandlerContext context, BackOfficeRequirement requirement) { - try + // if not configured (install or upgrade) then we can continue + // otherwise we need to ensure that a user is logged in + + switch (_runtimeState.Level) { - // if not configured (install or upgrade) then we can continue - // otherwise we need to ensure that a user is logged in - var isAuth = _runtimeState.Level == RuntimeLevel.Install - || _runtimeState.Level == RuntimeLevel.Upgrade - || _backOfficeSecurity.BackOfficeSecurity?.ValidateCurrentUser(false, requirement.RequireApproval) == ValidateRequestAttempt.Success; - return Task.FromResult(isAuth); - } - catch (Exception) - { - return Task.FromResult(false); + case RuntimeLevel.Install: + case RuntimeLevel.Upgrade: + return Task.FromResult(true); + default: + var userApprovalSucceeded = !requirement.RequireApproval || (_backOfficeSecurity.BackOfficeSecurity.CurrentUser?.IsApproved ?? false); + return Task.FromResult(userApprovalSucceeded); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 7d0e99d0e1..718681ac3b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -51,6 +51,9 @@ namespace Umbraco.Web.BackOffice.Controllers [IsBackOffice] public class AuthenticationController : UmbracoApiControllerBase { + // NOTE: Each action must either be explicitly authorized or explicitly [AllowAnonymous], the latter is optional because + // this controller itself doesn't require authz but it's more clear what the intention is. + private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; private readonly IBackOfficeUserManager _userManager; private readonly IBackOfficeSignInManager _signInManager; @@ -211,6 +214,7 @@ namespace Umbraco.Web.BackOffice.Controllers } [HttpGet] + [AllowAnonymous] public async Task GetRemainingTimeoutSeconds() { // force authentication to occur since this is not an authorized endpoint @@ -242,6 +246,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [HttpGet] + [AllowAnonymous] public async Task IsAuthenticated() { // force authentication to occur since this is not an authorized endpoint @@ -399,6 +404,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [SetAngularAntiForgeryTokens] + [AllowAnonymous] public async Task>> Get2FAProviders() { var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); @@ -413,6 +419,7 @@ namespace Umbraco.Web.BackOffice.Controllers } [SetAngularAntiForgeryTokens] + [AllowAnonymous] public async Task PostSend2FACode([FromBody] string provider) { if (provider.IsNullOrWhiteSpace()) @@ -458,6 +465,7 @@ namespace Umbraco.Web.BackOffice.Controllers } [SetAngularAntiForgeryTokens] + [AllowAnonymous] public async Task> PostVerify2FACode(Verify2FACodeModel model) { if (ModelState.IsValid == false) @@ -495,6 +503,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [SetAngularAntiForgeryTokens] + [AllowAnonymous] public async Task PostSetPassword(SetPasswordModel model) { var identityUser = await _userManager.FindByIdAsync(model.UserId.ToString()); @@ -559,6 +568,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [ValidateAngularAntiForgeryToken] + [AllowAnonymous] public async Task PostLogout() { // force authentication to occur since this is not an authorized endpoint diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index fa9ca98707..59ace894b1 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -36,15 +36,19 @@ 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 { [DisableBrowserCache] - //[UmbracoRequireHttps] //TODO Reintroduce + [UmbracoRequireHttps] [PluginController(Constants.Web.Mvc.BackOfficeArea)] [IsBackOffice] public class BackOfficeController : UmbracoController { + // NOTE: Each action must either be explicitly authorized or explicitly [AllowAnonymous], the latter is optional because + // this controller itself doesn't require authz but it's more clear what the intention is. + private readonly IBackOfficeUserManager _userManager; private readonly IRuntimeMinifier _runtimeMinifier; private readonly GlobalSettings _globalSettings; @@ -96,23 +100,31 @@ namespace Umbraco.Web.BackOffice.Controllers } [HttpGet] + [AllowAnonymous] public async Task Default() { + // force authentication to occur since this is not an authorized endpoint + var result = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType); + var viewPath = Path.Combine(_globalSettings.UmbracoPath , Constants.Web.Mvc.BackOfficeArea, nameof(Default) + ".cshtml") .Replace("\\", "/"); // convert to forward slashes since it's a virtual path return await RenderDefaultOrProcessExternalLoginAsync( + result, () => View(viewPath), () => View(viewPath)); } [HttpGet] + [AllowAnonymous] public async Task VerifyInvite(string invite) { + var authenticate = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType); + //if you are hitting VerifyInvite, you're already signed in as a different user, and the token is invalid //you'll exit on one of the return RedirectToAction(nameof(Default)) but you're still logged in so you just get //dumped at the default admin view with no detail - if (_backofficeSecurityAccessor.BackOfficeSecurity.IsAuthenticated()) + if (authenticate.Succeeded) { await _signInManager.SignOutAsync(); } @@ -174,10 +186,16 @@ namespace Umbraco.Web.BackOffice.Controllers /// [HttpGet] [StatusCodeResult(System.Net.HttpStatusCode.ServiceUnavailable)] + [AllowAnonymous] public async Task AuthorizeUpgrade() { - var viewPath = Path.Combine(_globalSettings.UmbracoPath, Umbraco.Core.Constants.Web.Mvc.BackOfficeArea, nameof(AuthorizeUpgrade) + ".cshtml"); + // force authentication to occur since this is not an authorized endpoint + var result = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType); + + var viewPath = Path.Combine(_globalSettings.UmbracoPath, Constants.Web.Mvc.BackOfficeArea, nameof(AuthorizeUpgrade) + ".cshtml"); + return await RenderDefaultOrProcessExternalLoginAsync( + result, //The default view to render when there is no external login info or errors () => View(viewPath), //The IActionResult to perform if external login is successful @@ -190,6 +208,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [MinifyJavaScriptResult(Order = 0)] [HttpGet] + [AllowAnonymous] public async Task Application() { var result = await _runtimeMinifier.GetScriptForLoadingBackOfficeAsync(_globalSettings, _hostingEnvironment); @@ -203,6 +222,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [HttpGet] + [AllowAnonymous] public Dictionary> LocalizedText(string culture = null) { var isAuthenticated = _backofficeSecurityAccessor.BackOfficeSecurity.IsAuthenticated(); @@ -265,6 +285,7 @@ namespace Umbraco.Web.BackOffice.Controllers } [HttpPost] + [AllowAnonymous] public ActionResult ExternalLogin(string provider, string redirectUrl = null) { if (redirectUrl == null) @@ -297,6 +318,7 @@ namespace Umbraco.Web.BackOffice.Controllers } [HttpGet] + [AllowAnonymous] public async Task ValidatePasswordResetCode([Bind(Prefix = "u")]int userId, [Bind(Prefix = "r")]string resetCode) { var user = await _userManager.FindByIdAsync(userId.ToString()); @@ -362,6 +384,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// private async Task RenderDefaultOrProcessExternalLoginAsync( + AuthenticateResult authenticateResult, Func defaultResponse, Func externalSignInResponse) { @@ -382,7 +405,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (loginInfo == null || loginInfo.Principal == null) { // if the user is not logged in, check if there's any auto login redirects specified - if (!_backofficeSecurityAccessor.BackOfficeSecurity.ValidateCurrentUser()) + if (!authenticateResult.Succeeded) { var oauthRedirectAuthProvider = _externalLogins.GetAutoLoginProvider(); if (!oauthRedirectAuthProvider.IsNullOrWhiteSpace()) diff --git a/src/Umbraco.Web.Common/Install/InstallAuthorizeAttribute.cs b/src/Umbraco.Web.Common/Install/InstallAuthorizeAttribute.cs index 04f743144e..0989de5ba4 100644 --- a/src/Umbraco.Web.Common/Install/InstallAuthorizeAttribute.cs +++ b/src/Umbraco.Web.Common/Install/InstallAuthorizeAttribute.cs @@ -13,38 +13,32 @@ namespace Umbraco.Web.Common.Install /// public class InstallAuthorizeAttribute : TypeFilterAttribute { - // NOTE: This doesn't need to be an authz policy, it's only used for the installer - public InstallAuthorizeAttribute() : base(typeof(InstallAuthorizeFilter)) { } private class InstallAuthorizeFilter : IAuthorizationFilter { - private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly IRuntimeState _runtimeState; private readonly ILogger _logger; public InstallAuthorizeFilter( - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IRuntimeState runtimeState, ILogger logger) { - _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _runtimeState = runtimeState; _logger = logger; } public void OnAuthorization(AuthorizationFilterContext authorizationFilterContext) { - if (!IsAllowed()) + if (!IsAllowed(authorizationFilterContext)) { authorizationFilterContext.Result = new ForbidResult(); } - } - private bool IsAllowed() + private bool IsAllowed(AuthorizationFilterContext authorizationFilterContext) { try { @@ -52,7 +46,7 @@ namespace Umbraco.Web.Common.Install // otherwise we need to ensure that a user is logged in return _runtimeState.Level == RuntimeLevel.Install || _runtimeState.Level == RuntimeLevel.Upgrade - || (_backOfficeSecurityAccessor?.BackOfficeSecurity?.ValidateCurrentUser() ?? false); + || (authorizationFilterContext.HttpContext.User?.Identity?.IsAuthenticated ?? false); } catch (Exception ex) { diff --git a/src/Umbraco.Web.Common/Install/InstallController.cs b/src/Umbraco.Web.Common/Install/InstallController.cs index 1854d8dfbc..5d8d3bf76f 100644 --- a/src/Umbraco.Web.Common/Install/InstallController.cs +++ b/src/Umbraco.Web.Common/Install/InstallController.cs @@ -15,6 +15,7 @@ using Umbraco.Web.Install; using Umbraco.Web.Security; using Umbraco.Core.Configuration.Models; using Microsoft.Extensions.Options; +using Microsoft.AspNetCore.Authentication; namespace Umbraco.Web.Common.Install { @@ -73,13 +74,11 @@ namespace Umbraco.Web.Common.Install // Update ClientDependency version and delete its temp directories to make sure we get fresh caches _runtimeMinifier.Reset(); - var result = _backofficeSecurityAccessor.BackOfficeSecurity.ValidateCurrentUser(false); + var authResult = await HttpContext.AuthenticateAsync(Core.Constants.Security.BackOfficeAuthenticationType); - switch (result) + if (!authResult.Succeeded) { - case ValidateRequestAttempt.FailedNoPrivileges: - case ValidateRequestAttempt.FailedNoContextId: - return Redirect(_globalSettings.UmbracoPath + "/AuthorizeUpgrade?redir=" + Request.GetEncodedUrl()); + return Redirect(_globalSettings.UmbracoPath + "/AuthorizeUpgrade?redir=" + Request.GetEncodedUrl()); } } diff --git a/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs b/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs index 3d9d1e8622..1ea44b1596 100644 --- a/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs +++ b/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs @@ -72,41 +72,5 @@ namespace Umbraco.Web.Common.Security return user.HasSectionAccess(section); } - /// - public bool ValidateCurrentUser() - { - return ValidateCurrentUser(false, true) == ValidateRequestAttempt.Success; - } - - /// - public ValidateRequestAttempt ValidateCurrentUser(bool throwExceptions, bool requiresApproval = true) - { - //This will first check if the current user is already authenticated - which should be the case in nearly all circumstances - // since the authentication happens in the Module, that authentication also checks the ticket expiry. We don't - // need to check it a second time because that requires another decryption phase and nothing can tamper with it during the request. - - if (IsAuthenticated() == false) - { - //There is no user - if (throwExceptions) throw new InvalidOperationException("The user has no umbraco contextid - try logging in"); - return ValidateRequestAttempt.FailedNoContextId; - } - - var user = CurrentUser; - - // TODO: All of this is done as part of identity/backofficesigninmanager - // Check for console access - if (user == null || (requiresApproval && user.IsApproved == false) || (user.IsLockedOut && RequestIsInUmbracoApplication(_httpContextAccessor, _globalSettings, _hostingEnvironment))) - { - if (throwExceptions) throw new ArgumentException("You have no privileges to the umbraco console. Please contact your administrator"); - return ValidateRequestAttempt.FailedNoPrivileges; - } - return ValidateRequestAttempt.Success; - } - - private static bool RequestIsInUmbracoApplication(IHttpContextAccessor httpContextAccessor, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - { - return httpContextAccessor.GetRequiredHttpContext().Request.Path.ToString().IndexOf(hostingEnvironment.ToAbsolute(globalSettings.UmbracoPath), StringComparison.InvariantCultureIgnoreCase) > -1; - } } } diff --git a/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs b/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs index 8ada898c21..73b2674706 100644 --- a/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs +++ b/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs @@ -76,7 +76,7 @@ namespace Umbraco.Web.Mvc // otherwise we need to ensure that a user is logged in return RuntimeState.Level == RuntimeLevel.Install || RuntimeState.Level == RuntimeLevel.Upgrade - || BackOfficeSecurity.ValidateCurrentUser(); + || (httpContext.User?.Identity?.IsAuthenticated ?? false); } catch (Exception) { diff --git a/src/Umbraco.Web/Security/BackofficeSecurity.cs b/src/Umbraco.Web/Security/BackofficeSecurity.cs index 9cd2616c39..839acba834 100644 --- a/src/Umbraco.Web/Security/BackofficeSecurity.cs +++ b/src/Umbraco.Web/Security/BackofficeSecurity.cs @@ -11,11 +11,6 @@ namespace Umbraco.Web.Security { public IUser CurrentUser => throw new NotImplementedException(); - public ValidateRequestAttempt AuthorizeRequest(bool throwExceptions = false) - { - throw new NotImplementedException(); - } - public Attempt GetUserId() { throw new NotImplementedException(); @@ -31,14 +26,5 @@ namespace Umbraco.Web.Security throw new NotImplementedException(); } - public bool ValidateCurrentUser() - { - throw new NotImplementedException(); - } - - public ValidateRequestAttempt ValidateCurrentUser(bool throwExceptions, bool requiresApproval = true) - { - throw new NotImplementedException(); - } } } diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs index dea72b7be9..168c55e71f 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs @@ -53,17 +53,17 @@ namespace Umbraco.Web.WebApi return true; } - try + // if not configured (install or upgrade) then we can continue + // otherwise we need to ensure that a user is logged in + + switch (_runtimeState.Level) { - // if not configured (install or upgrade) then we can continue - // otherwise we need to ensure that a user is logged in - return RuntimeState.Level == RuntimeLevel.Install - || RuntimeState.Level == RuntimeLevel.Upgrade - || BackOfficeSecurity.ValidateCurrentUser(false, _requireApproval) == ValidateRequestAttempt.Success; - } - catch (Exception) - { - return false; + case RuntimeLevel.Install: + case RuntimeLevel.Upgrade: + return true; + default: + var userApprovalSucceeded = !_requireApproval || (BackOfficeSecurity.CurrentUser?.IsApproved ?? false); + return userApprovalSucceeded; } } } From e297bc8c8de5dda41da2acba936f85700a022a03 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 2 Dec 2020 14:43:33 +1100 Subject: [PATCH 33/37] oops still have c# 9 stuff --- src/Umbraco.Web.BackOffice/Security/AutoLinkSignInResult.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Security/AutoLinkSignInResult.cs b/src/Umbraco.Web.BackOffice/Security/AutoLinkSignInResult.cs index 06481d1d86..c3f57f2fcc 100644 --- a/src/Umbraco.Web.BackOffice/Security/AutoLinkSignInResult.cs +++ b/src/Umbraco.Web.BackOffice/Security/AutoLinkSignInResult.cs @@ -19,17 +19,17 @@ namespace Umbraco.Web.Common.Security Succeeded = false }; - public static AutoLinkSignInResult FailedException(string error) => new(new[] { error }) + public static AutoLinkSignInResult FailedException(string error) => new AutoLinkSignInResult(new[] { error }) { Succeeded = false }; - public static AutoLinkSignInResult FailedCreatingUser(IReadOnlyCollection errors) => new(errors) + public static AutoLinkSignInResult FailedCreatingUser(IReadOnlyCollection errors) => new AutoLinkSignInResult(errors) { Succeeded = false }; - public static AutoLinkSignInResult FailedLinkingUser(IReadOnlyCollection errors) => new(errors) + public static AutoLinkSignInResult FailedLinkingUser(IReadOnlyCollection errors) => new AutoLinkSignInResult(errors) { Succeeded = false }; From d0e17d16bc7674b48b3de7d8a1b119104c6adafd Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 2 Dec 2020 15:49:28 +1100 Subject: [PATCH 34/37] ext method for authn back office scheme with null check, fixing tests --- .../Controllers/AuthenticationController.cs | 6 +++--- .../Controllers/BackOfficeController.cs | 6 +++--- .../Extensions/ControllerExtensions.cs | 19 +++++++++++++++++++ .../Install/InstallController.cs | 2 +- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 718681ac3b..efe28763f1 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -218,7 +218,7 @@ namespace Umbraco.Web.BackOffice.Controllers public async Task GetRemainingTimeoutSeconds() { // force authentication to occur since this is not an authorized endpoint - var result = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType); + var result = await this.AuthenticateBackOfficeAsync(); if (!result.Succeeded) { return 0; @@ -250,7 +250,7 @@ namespace Umbraco.Web.BackOffice.Controllers public async Task IsAuthenticated() { // force authentication to occur since this is not an authorized endpoint - var result = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType); + var result = await this.AuthenticateBackOfficeAsync(); return result.Succeeded; } @@ -572,7 +572,7 @@ namespace Umbraco.Web.BackOffice.Controllers public async Task PostLogout() { // force authentication to occur since this is not an authorized endpoint - var result = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType); + var result = await this.AuthenticateBackOfficeAsync(); if (!result.Succeeded) return Ok(); await _signInManager.SignOutAsync(); diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index 59ace894b1..1ce0831502 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -104,7 +104,7 @@ namespace Umbraco.Web.BackOffice.Controllers public async Task Default() { // force authentication to occur since this is not an authorized endpoint - var result = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType); + var result = await this.AuthenticateBackOfficeAsync(); var viewPath = Path.Combine(_globalSettings.UmbracoPath , Constants.Web.Mvc.BackOfficeArea, nameof(Default) + ".cshtml") .Replace("\\", "/"); // convert to forward slashes since it's a virtual path @@ -119,7 +119,7 @@ namespace Umbraco.Web.BackOffice.Controllers [AllowAnonymous] public async Task VerifyInvite(string invite) { - var authenticate = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType); + var authenticate = await this.AuthenticateBackOfficeAsync(); //if you are hitting VerifyInvite, you're already signed in as a different user, and the token is invalid //you'll exit on one of the return RedirectToAction(nameof(Default)) but you're still logged in so you just get @@ -190,7 +190,7 @@ namespace Umbraco.Web.BackOffice.Controllers public async Task AuthorizeUpgrade() { // force authentication to occur since this is not an authorized endpoint - var result = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType); + var result = await this.AuthenticateBackOfficeAsync(); var viewPath = Path.Combine(_globalSettings.UmbracoPath, Constants.Web.Mvc.BackOfficeArea, nameof(AuthorizeUpgrade) + ".cshtml"); diff --git a/src/Umbraco.Web.Common/Extensions/ControllerExtensions.cs b/src/Umbraco.Web.Common/Extensions/ControllerExtensions.cs index cc52349699..b5fa9f946c 100644 --- a/src/Umbraco.Web.Common/Extensions/ControllerExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ControllerExtensions.cs @@ -1,10 +1,29 @@ using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; +using Umbraco.Core; namespace Umbraco.Extensions { public static class ControllerExtensions { + /// + /// Runs the authentication process + /// + /// + /// + public static async Task AuthenticateBackOfficeAsync(this ControllerBase controller) + { + if (controller.HttpContext == null) + { + return AuthenticateResult.NoResult(); + } + + var result = await controller.HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType); + return result; + } + /// /// Return the controller name from the controller type /// diff --git a/src/Umbraco.Web.Common/Install/InstallController.cs b/src/Umbraco.Web.Common/Install/InstallController.cs index 5d8d3bf76f..1e8264a2fc 100644 --- a/src/Umbraco.Web.Common/Install/InstallController.cs +++ b/src/Umbraco.Web.Common/Install/InstallController.cs @@ -74,7 +74,7 @@ namespace Umbraco.Web.Common.Install // Update ClientDependency version and delete its temp directories to make sure we get fresh caches _runtimeMinifier.Reset(); - var authResult = await HttpContext.AuthenticateAsync(Core.Constants.Security.BackOfficeAuthenticationType); + var authResult = await this.AuthenticateBackOfficeAsync(); if (!authResult.Succeeded) { From 9e6baf7b7fdb3a354c19657942e3d50ab213aaea Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 3 Dec 2020 18:52:28 +1100 Subject: [PATCH 35/37] reverts js changes to continue to support multiple destinations but with the fix of returning the task instance from within MoveViewsAndRegenerateJS --- src/Umbraco.Web.UI.Client/gulp/config.js | 2 +- .../gulp/tasks/dependencies.js | 149 ++++++++++-------- src/Umbraco.Web.UI.Client/gulp/tasks/js.js | 12 +- src/Umbraco.Web.UI.Client/gulp/tasks/views.js | 20 +-- .../gulp/tasks/watchTask.js | 32 ++-- .../gulp/util/processJs.js | 17 +- .../gulp/util/processLess.js | 13 +- 7 files changed, 137 insertions(+), 108 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index 66f248c410..511b6da945 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -78,7 +78,7 @@ module.exports = { assets: "./src/assets/**" } }, - root: "../Umbraco.Web.UI.NetCore/wwwroot/", + roots: ["../Umbraco.Web.UI/", "../Umbraco.Web.UI.NetCore/wwwroot/"], targets: { js: "umbraco/js/", lib: "umbraco/lib/", diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js index 73d0b4e205..179faeb843 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js @@ -21,7 +21,7 @@ function dependencies() { const nodeModules = [ { "name": "ace-builds", - "src": [ + "src": [ "./node_modules/ace-builds/src-min-noconflict/ace.js", "./node_modules/ace-builds/src-min-noconflict/ext-language_tools.js", "./node_modules/ace-builds/src-min-noconflict/ext-searchbox.js", @@ -43,23 +43,23 @@ function dependencies() { }, { "name": "angular", - "src": ["./node_modules/angular/angular.js"], + "src": ["./node_modules/angular/angular.js"], "base": "./node_modules/angular" }, { "name": "angular-aria", - "src": ["./node_modules/angular-aria/angular-aria.min.js", - "./node_modules/angular-aria/angular-aria.min.js.map"], + "src": ["./node_modules/angular-aria/angular-aria.min.js", + "./node_modules/angular-aria/angular-aria.min.js.map"], "base": "./node_modules/angular-aria" }, { "name": "angular-cookies", - "src": ["./node_modules/angular-cookies/angular-cookies.js"], + "src": ["./node_modules/angular-cookies/angular-cookies.js"], "base": "./node_modules/angular-cookies" }, { "name": "angular-dynamic-locale", - "src": [ + "src": [ "./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js", "./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js.map" ], @@ -67,32 +67,32 @@ function dependencies() { }, { "name": "angular-sanitize", - "src": ["./node_modules/angular-sanitize/angular-sanitize.js"], + "src": ["./node_modules/angular-sanitize/angular-sanitize.js"], "base": "./node_modules/angular-sanitize" }, { "name": "angular-touch", - "src": ["./node_modules/angular-touch/angular-touch.js"], + "src": ["./node_modules/angular-touch/angular-touch.js"], "base": "./node_modules/angular-touch" }, { "name": "angular-ui-sortable", - "src": ["./node_modules/angular-ui-sortable/dist/sortable.js"], + "src": ["./node_modules/angular-ui-sortable/dist/sortable.js"], "base": "./node_modules/angular-ui-sortable/dist" }, { "name": "angular-route", - "src": ["./node_modules/angular-route/angular-route.js"], + "src": ["./node_modules/angular-route/angular-route.js"], "base": "./node_modules/angular-route" }, { "name": "angular-animate", - "src": ["./node_modules/angular-animate/angular-animate.js"], + "src": ["./node_modules/angular-animate/angular-animate.js"], "base": "./node_modules/angular-animate" }, { "name": "angular-i18n", - "src": [ + "src": [ "./node_modules/angular-i18n/angular-i18n.js", "./node_modules/angular-i18n/angular-locale_*.js" ], @@ -100,7 +100,7 @@ function dependencies() { }, { "name": "angular-local-storage", - "src": [ + "src": [ "./node_modules/angular-local-storage/dist/angular-local-storage.min.js", "./node_modules/angular-local-storage/dist/angular-local-storage.min.js.map" ], @@ -108,48 +108,48 @@ function dependencies() { }, { "name": "angular-messages", - "src": ["./node_modules/angular-messages/angular-messages.js"], + "src": ["./node_modules/angular-messages/angular-messages.js"], "base": "./node_modules/angular-messages" - }, + }, { "name": "angular-mocks", - "src": ["./node_modules/angular-mocks/angular-mocks.js"], + "src": ["./node_modules/angular-mocks/angular-mocks.js"], "base": "./node_modules/angular-mocks" }, { "name": "animejs", - "src": ["./node_modules/animejs/anime.min.js"], + "src": ["./node_modules/animejs/anime.min.js"], "base": "./node_modules/animejs" }, { "name": "bootstrap-social", - "src": ["./node_modules/bootstrap-social/bootstrap-social.css"], + "src": ["./node_modules/bootstrap-social/bootstrap-social.css"], "base": "./node_modules/bootstrap-social" }, { "name": "angular-chart.js", - "src": ["./node_modules/angular-chart.js/dist/angular-chart.min.js"], + "src": ["./node_modules/angular-chart.js/dist/angular-chart.min.js"], "base": "./node_modules/angular-chart.js/dist" }, { "name": "chart.js", - "src": ["./node_modules/chart.js/dist/Chart.min.js"], + "src": ["./node_modules/chart.js/dist/Chart.min.js"], "base": "./node_modules/chart.js/dist" }, { "name": "clipboard", - "src": ["./node_modules/clipboard/dist/clipboard.min.js"], + "src": ["./node_modules/clipboard/dist/clipboard.min.js"], "base": "./node_modules/clipboard/dist" }, { "name": "jsdiff", - "src": ["./node_modules/diff/dist/diff.min.js"], + "src": ["./node_modules/diff/dist/diff.min.js"], "base": "./node_modules/diff/dist" }, { "name": "flatpickr", - "src": [ + "src": [ "./node_modules/flatpickr/dist/flatpickr.js", "./node_modules/flatpickr/dist/flatpickr.css", "./node_modules/flatpickr/dist/l10n/*.js" @@ -158,7 +158,7 @@ function dependencies() { }, { "name": "font-awesome", - "src": [ + "src": [ "./node_modules/font-awesome/fonts/*", "./node_modules/font-awesome/css/font-awesome.min.css" ], @@ -166,7 +166,7 @@ function dependencies() { }, { "name": "jquery", - "src": [ + "src": [ "./node_modules/jquery/dist/jquery.min.js", "./node_modules/jquery/dist/jquery.min.map" ], @@ -174,37 +174,37 @@ function dependencies() { }, { "name": "jquery-ui", - "src": ["./node_modules/jquery-ui-dist/jquery-ui.min.js"], + "src": ["./node_modules/jquery-ui-dist/jquery-ui.min.js"], "base": "./node_modules/jquery-ui-dist" }, { "name": "jquery-ui-touch-punch", - "src": ["./node_modules/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js"], + "src": ["./node_modules/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js"], "base": "./node_modules/jquery-ui-touch-punch" }, { "name": "lazyload-js", - "src": ["./node_modules/lazyload-js/LazyLoad.min.js"], + "src": ["./node_modules/lazyload-js/LazyLoad.min.js"], "base": "./node_modules/lazyload-js" }, { "name": "moment", - "src": ["./node_modules/moment/min/moment.min.js"], + "src": ["./node_modules/moment/min/moment.min.js"], "base": "./node_modules/moment/min" }, { "name": "moment", - "src": ["./node_modules/moment/locale/*.js"], + "src": ["./node_modules/moment/locale/*.js"], "base": "./node_modules/moment/locale" }, { "name": "ng-file-upload", - "src": ["./node_modules/ng-file-upload/dist/ng-file-upload.min.js"], + "src": ["./node_modules/ng-file-upload/dist/ng-file-upload.min.js"], "base": "./node_modules/ng-file-upload/dist" }, { "name": "nouislider", - "src": [ + "src": [ "./node_modules/nouislider/distribute/nouislider.min.js", "./node_modules/nouislider/distribute/nouislider.min.css" ], @@ -212,14 +212,14 @@ function dependencies() { }, { "name": "signalr", - "src": [ + "src": [ "./node_modules/@microsoft/signalr/dist/browser/signalr.min.js", ], "base": "./node_modules/@microsoft/signalr/dist/browser" }, { "name": "spectrum", - "src": [ + "src": [ "./node_modules/spectrum-colorpicker2/dist/spectrum.js", "./node_modules/spectrum-colorpicker2/dist/spectrum.css" ], @@ -227,7 +227,7 @@ function dependencies() { }, { "name": "tinymce", - "src": [ + "src": [ "./node_modules/tinymce/tinymce.min.js", "./node_modules/tinymce/plugins/**", "./node_modules/tinymce/skins/**", @@ -237,12 +237,12 @@ function dependencies() { }, { "name": "typeahead.js", - "src": ["./node_modules/typeahead.js/dist/typeahead.bundle.min.js"], + "src": ["./node_modules/typeahead.js/dist/typeahead.bundle.min.js"], "base": "./node_modules/typeahead.js/dist" }, { "name": "underscore", - "src": ["node_modules/underscore/underscore-min.js"], + "src": ["node_modules/underscore/underscore-min.js"], "base": "./node_modules/underscore" }, { @@ -257,58 +257,71 @@ function dependencies() { // add streams for node modules nodeModules.forEach(module => { - stream.add( - gulp.src(module.src, - { base: module.base, allowEmpty: true }) - .pipe(gulp.dest(config.root + config.targets.lib + "/" + module.name)) - ); + var task = gulp.src(module.src, { base: module.base, allowEmpty: true }); + + _.forEach(config.roots, function(root){ + task = task.pipe(gulp.dest(root + config.targets.lib + "/" + module.name)) + }); + + stream.add(task); }); //copy over libs which are not on npm (/lib) - stream.add( - gulp.src(config.sources.globs.lib, { allowEmpty: true }) - .pipe(gulp.dest(config.root + config.targets.lib)) - ); + var libTask = gulp.src(config.sources.globs.lib, { allowEmpty: true }); + + _.forEach(config.roots, function(root){ + libTask = libTask.pipe(gulp.dest(root + config.targets.lib)) + }); + + stream.add(libTask); //Copies all static assets into /root / assets folder //css, fonts and image files - + var assetsTask = gulp.src(config.sources.globs.assets, { allowEmpty: true }); assetsTask = assetsTask.pipe(imagemin([ - imagemin.gifsicle({ interlaced: true }), - imagemin.mozjpeg({ progressive: true }), - imagemin.optipng({ optimizationLevel: 5 }), + imagemin.gifsicle({interlaced: true}), + imagemin.mozjpeg({progressive: true}), + imagemin.optipng({optimizationLevel: 5}), imagemin.svgo({ plugins: [ - { removeViewBox: true }, - { cleanupIDs: false } + {removeViewBox: true}, + {cleanupIDs: false} ] }) ])); - assetsTask = assetsTask.pipe(gulp.dest(config.root + config.targets.assets)); - - + _.forEach(config.roots, function(root){ + assetsTask = assetsTask.pipe(gulp.dest(root + config.targets.assets)); + }); + + stream.add(assetsTask); // Copies all the less files related to the preview into their folder //these are not pre-processed as preview has its own less compiler client side - stream.add( - gulp.src("src/canvasdesigner/editors/*.less", { allowEmpty: true }) - .pipe(gulp.dest(config.root + config.targets.assets + "/less")) - ); + var lessTask = gulp.src("src/canvasdesigner/editors/*.less", { allowEmpty: true }); - // TODO: check if we need these fileSize - stream.add( - gulp.src("src/views/propertyeditors/grid/config/*.*", { allowEmpty: true }) - .pipe(gulp.dest(config.root + config.targets.views + "/propertyeditors/grid/config")) - ); + _.forEach(config.roots, function(root){ + lessTask = lessTask.pipe(gulp.dest(root + config.targets.assets + "/less")); + }); + stream.add(lessTask); - stream.add( - gulp.src("src/views/dashboard/default/*.jpg", { allowEmpty: true }) - .pipe(gulp.dest(config.root + config.targets.views + "/dashboard/default")) - ); + + // TODO: check if we need these fileSize + var configTask = gulp.src("src/views/propertyeditors/grid/config/*.*", { allowEmpty: true }); + _.forEach(config.roots, function(root){ + configTask = configTask.pipe(gulp.dest(root + config.targets.views + "/propertyeditors/grid/config")); + }); + stream.add(configTask); + + var dashboardTask = gulp.src("src/views/dashboard/default/*.jpg", { allowEmpty: true }); + _.forEach(config.roots, function(root){ + dashboardTask = dashboardTask .pipe(gulp.dest(root + config.targets.views + "/dashboard/default")); + }); + stream.add(dashboardTask); + return stream; }; diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/js.js b/src/Umbraco.Web.UI.Client/gulp/tasks/js.js index c82eafa845..b46e105942 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/js.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/js.js @@ -16,17 +16,19 @@ function js() { //we run multiple streams, so merge them all together var stream = new MergeStream(); - stream.add( - gulp.src(config.sources.globs.js).pipe(gulp.dest(config.root + config.targets.js)) - ); - + var task = gulp.src(config.sources.globs.js); + _.forEach(config.roots, function(root){ + task = task.pipe( gulp.dest(root + config.targets.js) ) + }) + stream.add(task); + _.forEach(config.sources.js, function (group) { stream.add( processJs(group.files, group.out) ); }); - return stream; + return stream; }; module.exports = { js: js }; diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/views.js b/src/Umbraco.Web.UI.Client/gulp/tasks/views.js index ffaa2ddc29..ab6d0c8d61 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/views.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/views.js @@ -14,17 +14,19 @@ function views() { _.forEach(config.sources.views, function (group) { var task = gulp.src(group.files) - .pipe(rename(function (path) { - path.dirname = path.dirname.toLowerCase(); - path.basename = path.basename.toLowerCase(); - path.extname = path.extname.toLowerCase(); - })); + .pipe(rename(function(path) { + path.dirname = path.dirname.toLowerCase(); + path.basename = path.basename.toLowerCase(); + path.extname = path.extname.toLowerCase(); + })); - var destPath = config.root + config.targets.views + group.folder; - console.log("copying " + group.files + " to " + destPath) - task = task.pipe(gulp.dest(destPath)); + _.forEach(config.roots, function(root){ + var destPath = root + config.targets.views + group.folder; + console.log("copying " + group.files + " to " + destPath) + task = task.pipe( gulp.dest(destPath)); + }) - stream.add(task); + stream.add (task); }); diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js index ded59a8fe1..979bdc895d 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js @@ -1,7 +1,7 @@ 'use strict'; const config = require('../config'); -const { watch, series, parallel, dest, src } = require('gulp'); +const {watch, series, parallel, dest, src} = require('gulp'); var _ = require('lodash'); var rename = require('gulp-rename'); @@ -10,7 +10,7 @@ var MergeStream = require('merge-stream'); var processJs = require('../util/processJs'); var processLess = require('../util/processLess'); -var { js } = require('./js'); +var {js} = require('./js'); function watchTask(cb) { @@ -18,14 +18,14 @@ function watchTask(cb) { //Setup a watcher for all groups of JS files _.forEach(config.sources.js, function (group) { - if (group.watch !== false) { - watch(group.files, { ignoreInitial: true, interval: watchInterval }, function JS_Group_Compile() { return processJs(group.files, group.out); }); + if(group.watch !== false) { + watch(group.files, { ignoreInitial: true, interval: watchInterval }, function JS_Group_Compile() { return processJs(group.files, group.out);}); } }); //Setup a watcher for all groups of LESS files _.forEach(config.sources.less, function (group) { - if (group.watch !== false) { + if(group.watch !== false) { watch(group.watch, { ignoreInitial: true, interval: watchInterval }, function Less_Group_Compile() { return processLess(group.files, group.out); }); } }); @@ -33,22 +33,22 @@ function watchTask(cb) { //Setup a watcher for all groups of view files var viewWatcher; _.forEach(config.sources.views, function (group) { - if (group.watch !== false) { + if(group.watch !== false) { viewWatcher = watch(group.files, { ignoreInitial: true, interval: watchInterval }, parallel( function MoveViewsAndRegenerateJS() { - var task = src(group.files) - .pipe(rename(function (path) { - path.dirname = path.dirname.toLowerCase(); - path.basename = path.basename.toLowerCase(); - path.extname = path.extname.toLowerCase(); - })); - - var destPath = config.root + config.targets.views + group.folder; - console.log("copying " + group.files + " to " + destPath); - task = task.pipe(dest(destPath)); + .pipe(rename(function(path) { + path.dirname = path.dirname.toLowerCase(); + path.basename = path.basename.toLowerCase(); + path.extname = path.extname.toLowerCase(); + })); + _.forEach(config.roots, function(root){ + var destPath = root + config.targets.views + group.folder; + console.log("copying " + group.files + " to " + destPath); + task = task.pipe( dest(destPath) ); + }); return task; }, js diff --git a/src/Umbraco.Web.UI.Client/gulp/util/processJs.js b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js index 24ea0eec0c..6c6f1276e7 100644 --- a/src/Umbraco.Web.UI.Client/gulp/util/processJs.js +++ b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js @@ -11,9 +11,11 @@ var embedTemplates = require('gulp-angular-embed-templates'); var _ = require('lodash'); module.exports = function (files, out) { - - console.log("JS: ", files, " -> ", config.root + config.targets.js + out) - + + _.forEach(config.roots, function(root){ + console.log("JS: ", files, " -> ", root + config.targets.js + out) + }) + var task = gulp.src(files); // check for js errors @@ -29,10 +31,13 @@ module.exports = function (files, out) { if(config.compile.current.embedtemplates === true) { task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } })); } + + task = task.pipe(concat(out)).pipe(wrap('(function(){\n%= body %\n})();')) - task = task.pipe(concat(out)) - .pipe(wrap('(function(){\n%= body %\n})();')) - .pipe(gulp.dest(config.root + config.targets.js)); + _.forEach(config.roots, function(root){ + task = task.pipe(gulp.dest(root + config.targets.js)); + }) + return task; diff --git a/src/Umbraco.Web.UI.Client/gulp/util/processLess.js b/src/Umbraco.Web.UI.Client/gulp/util/processLess.js index e5b4cdf0a2..eea8fc31b0 100644 --- a/src/Umbraco.Web.UI.Client/gulp/util/processLess.js +++ b/src/Umbraco.Web.UI.Client/gulp/util/processLess.js @@ -17,8 +17,10 @@ module.exports = function(files, out) { autoprefixer, cssnano({zindex: false}) ]; - - console.log("LESS: ", files, " -> ", config.root + config.targets.css + out) + _.forEach(config.roots, function(root){ + console.log("LESS: ", files, " -> ", root + config.targets.css + out); + }) + var task = gulp.src(files); @@ -35,7 +37,12 @@ module.exports = function(files, out) { task = task.pipe(sourcemaps.write('./maps')); } - task = task.pipe(gulp.dest(config.root + config.targets.css)); + _.forEach(config.roots, function(root){ + task = task.pipe(gulp.dest(root + config.targets.css)); + }) + + + return task; From 8b57e3080ecc2a107c675d384502189d8c0975e5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 3 Dec 2020 19:36:38 +1100 Subject: [PATCH 36/37] Fixing fields vs props (oops) and some other stylecop violations --- .../Security/AutoLinkSignInResult.cs | 6 +-- .../Security/BackOfficeCookieManager.cs | 49 ++++++++++--------- .../Security/BackOfficeUserManager.cs | 18 ++++--- 3 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Security/AutoLinkSignInResult.cs b/src/Umbraco.Web.BackOffice/Security/AutoLinkSignInResult.cs index c3f57f2fcc..54f409e6f8 100644 --- a/src/Umbraco.Web.BackOffice/Security/AutoLinkSignInResult.cs +++ b/src/Umbraco.Web.BackOffice/Security/AutoLinkSignInResult.cs @@ -1,6 +1,6 @@ -using Microsoft.AspNetCore.Identity; using System; using System.Collections.Generic; +using Microsoft.AspNetCore.Identity; namespace Umbraco.Web.Common.Security { @@ -9,12 +9,12 @@ namespace Umbraco.Web.Common.Security /// public class AutoLinkSignInResult : SignInResult { - public static AutoLinkSignInResult FailedNotLinked = new AutoLinkSignInResult() + public static AutoLinkSignInResult FailedNotLinked => new AutoLinkSignInResult() { Succeeded = false }; - public static AutoLinkSignInResult FailedNoEmail = new AutoLinkSignInResult() + public static AutoLinkSignInResult FailedNoEmail => new AutoLinkSignInResult() { Succeeded = false }; diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs index 60bdc9c8ff..8664713c72 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs @@ -1,10 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.AspNetCore.Routing; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; @@ -13,8 +12,6 @@ using Umbraco.Extensions; namespace Umbraco.Web.BackOffice.Security { - using ICookieManager = Microsoft.AspNetCore.Authentication.Cookies.ICookieManager; - /// /// A custom cookie manager that is used to read the cookie from the request. /// @@ -22,7 +19,7 @@ namespace Umbraco.Web.BackOffice.Security /// Umbraco's back office cookie needs to be read on two paths: /umbraco and /install, therefore we cannot just set the cookie path to be /umbraco, /// instead we'll specify our own cookie manager and return null if the request isn't for an acceptable path. /// - public class BackOfficeCookieManager : ChunkingCookieManager, ICookieManager + public class BackOfficeCookieManager : ChunkingCookieManager, Microsoft.AspNetCore.Authentication.Cookies.ICookieManager { private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IRuntimeState _runtime; @@ -36,9 +33,8 @@ namespace Umbraco.Web.BackOffice.Security IRuntimeState runtime, IHostingEnvironment hostingEnvironment, GlobalSettings globalSettings, - IRequestCache requestCache, - LinkGenerator linkGenerator) - : this(umbracoContextAccessor, runtime, hostingEnvironment, globalSettings, requestCache, linkGenerator, null) + IRequestCache requestCache) + : this(umbracoContextAccessor, runtime, hostingEnvironment, globalSettings, requestCache, null) { } public BackOfficeCookieManager( @@ -47,7 +43,6 @@ namespace Umbraco.Web.BackOffice.Security IHostingEnvironment hostingEnvironment, GlobalSettings globalSettings, IRequestCache requestCache, - LinkGenerator linkGenerator, IEnumerable explicitPaths) { _umbracoContextAccessor = umbracoContextAccessor; @@ -61,9 +56,9 @@ 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: /// * it is a back office request @@ -79,19 +74,27 @@ namespace Umbraco.Web.BackOffice.Security // was: app.IsConfigured == false (equiv to !Run) && dbContext.IsDbConfigured == false (equiv to Install) // so, we handle .Install here and NOT .Upgrade if (_runtime.Level == RuntimeLevel.Install) + { return false; + } - //check the explicit paths + // check the explicit paths if (_explicitPaths != null) + { 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 + if (// check the explicit flag + (checkForceAuthTokens && _requestCache.IsAvailable && _requestCache.Get(Constants.Security.ForceReAuthFlag) != null) + + // check back office || requestUri.IsBackOfficeRequest(_globalSettings, _hostingEnvironment) - //check installer + + // check installer || requestUri.IsInstallerRequest(_hostingEnvironment)) + { return true; + } return false; } @@ -99,20 +102,20 @@ namespace Umbraco.Web.BackOffice.Security /// /// Explicitly implement this so that we filter the request /// - /// - /// - /// - string ICookieManager.GetRequestCookie(HttpContext context, string key) + /// + string Microsoft.AspNetCore.Authentication.Cookies.ICookieManager.GetRequestCookie(HttpContext context, string key) { var requestUri = new Uri(context.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute); if (_umbracoContextAccessor.UmbracoContext == null || requestUri.IsClientSideRequest()) + { return null; + } return ShouldAuthenticateRequest(requestUri) == false - //Don't auth request, don't return a cookie + // Don't auth request, don't return a cookie ? null - //Return the default implementation + // Return the default implementation : GetRequestCookie(context, key); } diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs index 761bf5c87c..464f2a38aa 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Security.Principal; using System.Threading; @@ -115,7 +115,7 @@ namespace Umbraco.Web.Common.Security /// protected virtual IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) { - //we can use the user aware password hasher (which will be the default and preferred way) + // we can use the user aware password hasher (which will be the default and preferred way) return new PasswordHasher(); } @@ -140,16 +140,22 @@ namespace Umbraco.Web.Common.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 /// - /// - /// + /// 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 == null) + { + throw new ArgumentNullException(nameof(user)); + } - if (user.IsApproved == false) return true; + if (user.IsApproved == false) + { + return true; + } return await base.IsLockedOutAsync(user); } From d711bce5ef44958c6d0ce6ab841bcc3c47c07d11 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 3 Dec 2020 11:29:17 +0100 Subject: [PATCH 37/37] build fix --- .../Security/BackOfficeCookieManagerTests.cs | 16 ++++++---------- .../Security/ConfigureBackOfficeCookieOptions.cs | 9 ++++----- 2 files changed, 10 insertions(+), 15 deletions(-) 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..d45887b3c3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs @@ -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")); @@ -62,13 +60,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security var runtime = Mock.Of(x => x.Level == RuntimeLevel.Run); + GetMockLinkGenerator(out var remainingTimeoutSecondsPath, out var isAuthPath); 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(), - GetMockLinkGenerator(out var remainingTimeoutSecondsPath, out var isAuthPath)); + Mock.Of()); var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost{remainingTimeoutSecondsPath}")); Assert.IsTrue(result); @@ -89,8 +87,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 == true && x.Get(Constants.Security.ForceReAuthFlag) == "not null")); var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/notbackoffice")); Assert.IsTrue(result); @@ -108,8 +105,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); diff --git a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs index 4a9ebcaf46..bd816e9382 100644 --- a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs @@ -105,8 +105,7 @@ namespace Umbraco.Web.BackOffice.Security _runtimeState, _hostingEnvironment, _globalSettings, - _requestCache, - _linkGenerator); + _requestCache); // _explicitPaths); TODO: Implement this once we do OAuth somehow @@ -119,7 +118,7 @@ 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) @@ -177,7 +176,7 @@ 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; }, @@ -226,7 +225,7 @@ 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(); await validator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context);