Merge remote-tracking branch 'origin/v9/dev' into v10/dev

# Conflicts:
#	build/build.ps1
#	build/templates/UmbracoPackage/.template.config/template.json
#	build/templates/UmbracoProject/.template.config/dotnetcli.host.json
#	build/templates/UmbracoProject/.template.config/ide.host.json
#	build/templates/UmbracoProject/.template.config/template.json
#	src/Umbraco.Core/Constants-System.cs
#	src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs
#	src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs
#	src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs
#	umbraco-netcore-only.sln
This commit is contained in:
Bjarke Berg
2022-04-20 11:09:28 +02:00
171 changed files with 4661 additions and 1695 deletions

View File

@@ -1,93 +0,0 @@
# Umbraco Code of Conduct
## Preamble
We are the friendly CMS. And our friendliness stems from our values. That's why we have set for ourselves, Umbraco HQ, and the community, five values to guide us in everything we do:
* Trust - We believe in and empower people
* Respect - We treat others as we would like to be treated
* Open - We share our thoughts and knowledge
* Hungry - We want to do things better, best is next
* Friendly - We want to build long-lasting relationships
With these values in mind, we want to offer the Umbraco community a code of conduct that specifies a baseline standard of behavior so that people with different social values and communication styles can work together.
This code of conduct is based on the widely used Contributor Covenant, as described in [https://www.contributor-covenant.org/](https://www.contributor-covenant.org/)
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders (e.g. Meetup & festival organizers, moderators, maintainers, ...) are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
Specific enforcement steps are listed in the [Code of Conduct Enforcement Guidelines](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/.github/CODE_OF_CONDUCT_ENFORCEMENT.md) document which is an appendix of this document, updated and maintained by the Code of Conduct Team.
## Scope
This Code of Conduct applies within all community spaces and events supported by Umbraco HQ or using the Umbraco name. It also applies when an individual is officially representing the community in public spaces.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior, may be reported at [conduct@umbraco.com](mailto:conduct@umbraco.com). All complaints will be reviewed and investigated promptly and fairly.
Or alternatively, you can reach out directly to any of the team members behind the address above:
* Sebastiaan Janssen (He, Him - Languages spoken: English, Dutch, Danish(Read)) [sebastiaan@umbraco.com](mailto:sebastiaan@umbraco.com)
* Ilham Boulghallat (She, Her - Languages spoken: English, French, Arabic) [ilham@umbraco.com](mailto:ilham@umbraco.com)
* Arnold Visser (He, Him - Languages spoken: English, Dutch) [arnold@umbraco.com](mailto:arnold@umbraco.com)
* Emma Burstow (She, Her - Languages spoken: English) [ema@umbraco.com](mailto:ema@umbraco.com)
The review process is done with full respect for the privacy and security of the reporter of any incident.
People with a conflict of interest should exclude themselves or if necessary be excluded by the other team members.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
**1. Correction**
Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
**2. Warning**
Community Impact: A violation through a single incident or series of actions.
Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
**3. Temporary Ban**
Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
**4. Permanent Ban**
Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
Consequence: A permanent ban from any sort of public interaction within the community.
## Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html).
This Code of Conduct will be maintained and reviewed by the team listed above.

View File

@@ -1,57 +0,0 @@
# Umbraco Code of Conduct Enforcement guidelines - Consequence Ladder
These are the steps followed by the [Umbraco Code of Conduct Team](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/.github/CODE_OF_CONDUCT.md) when we respond to an issue or incident brought to our attention by a community member.
This is an appendix to the Code of Conduct and is updated and maintained by the Code of Conduct Team.
To make sure that all reports will be reviewed and investigated promptly and fairly, as highlighted in the Umbraco Code of Conduct, we are following [Mozillas Consequence Ladder approach](https://github.com/mozilla/inclusion/blob/master/code-of-conduct-enforcement/consequence-ladder.md).
This approach helps the Team enforce the Code of Conduct in a structured manner and can be used as a way of communicating escalation. Each time the Team takes an action (warning, ban) the individual is made aware of future consequences. The Team can either follow the order of the levels in the ladder or decide to jump levels. When needed, the team can go directly to a permanent ban.
**Level 0: No Action**
Recommendations do not indicate a violation of the Code of Conduct.
**Level 1: Simple Warning Issued**
A private, written warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior.
**Level 2: Warning**
A private, written warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally:
* Communication of next-level consequences if behaviors are repeated (according to this ladder).
**Level 3: Warning + Mandatory Cooling Off Period (Access Retained)**
A private warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally:
* Request to avoid interaction on community messaging platforms (public forums, Our, commenting on issues).
* This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to this ladder.
* Require they do not interact with others in the report, or those who they suspect are involved in the report.
* Suggestions for 'out of office' type of message on platforms, to reduce curiosity, or suspicion among those not involved.
**Level 4: Temporary Ban (Access Revoked)**
Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally:
* 3-6 months imposed break.
* All accounts deactivated, or blocked during this time (Our, HQ Slack if applicable).
* Require to avoid interaction on community messaging platforms (public forums, Our, commenting on issues).
* This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to this ladder.
* All community leadership roles (e.g. Community Teams, Meetup/festival organizer, Commit right on Github..) suspended. (onboarding/reapplication required outside of this process)
* No attendance at Umbraco events during the ban period.
* Not allowed to enter Umbraco HQ offices during the ban period.
* Permission to use the MVP title, if applicable, is revoked during this ban period.
* The community leaders running events and other initiatives are informed of the ban.
**Level 5: Permanent Ban**
Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally:
* All accounts deactivated permanently.
* No attendance at Umbraco events going forward.
* Not allowed to enter Umbraco HQ offices permanently.
* All community leadership roles (e.g. Community Teams, Meetup/festival organizer, Commit right on Github..) permanently suspended.
* Permission to use the MVP title, if applicable, revoked.
* The community leaders running events and other initiatives are informed of the ban.
Sources:
* [Mozilla Code of Conduct - Enforcement Consequence Ladder](https://github.com/mozilla/inclusion/blob/master/code-of-conduct-enforcement/consequence-ladder.md)
* [Drupal Conflict Resolution Policy and Process](https://www.drupal.org/conflict-resolution)
* [Django Code of Conduct - Enforcement Manual](https://www.djangoproject.com/conduct/enforcement-manual/)

View File

@@ -10,7 +10,7 @@ Remember, we're a friendly bunch and are happy with whatever contribution you mi
**Code of conduct**
This project and everyone participating in it, is governed by the [our Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [Sebastiaan Janssen - sj@umbraco.dk](mailto:sj@umbraco.dk).
This project and everyone participating in it, is governed by the [our Code of Conduct](https://github.com/umbraco/.github/blob/main/.github/CODE_OF_CONDUCT.md).
**Table of contents**

4
.github/README.md vendored
View File

@@ -1,4 +1,4 @@
# [Umbraco CMS](https://umbraco.com) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md) [![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=v8/contrib)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![Twitter](https://img.shields.io/twitter/follow/umbraco.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=umbraco)
# [Umbraco CMS](https://umbraco.com) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md) [![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=v8/contrib)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![Twitter](https://img.shields.io/twitter/follow/umbraco.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=umbraco) [![Discord](https://img.shields.io/discord/869656431308189746)](https://discord.gg/umbraco)
Umbraco is the friendliest, most flexible and fastest growing ASP.NET CMS, and used by more than 500,000 websites worldwide. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
@@ -15,7 +15,7 @@ See the official [Umbraco website](https://umbraco.com) for an introduction, cor
- [Community](#join-the-umbraco-community)
- [Contributing](#contributing)
Please also see our [Code of Conduct](CODE_OF_CONDUCT.md).
Please also see our [Code of Conduct](https://github.com/umbraco/.github/blob/main/.github/CODE_OF_CONDUCT.md).
## Getting Started

26
.github/workflows/pr-first-response.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: pr-first-response
on:
pull_request:
types: [opened]
jobs:
send-response:
runs-on: ubuntu-latest
steps:
- name: Fetch random comment 🗣️
uses: JamesIves/fetch-api-data-action@v2.1.0
with:
ENDPOINT: https://collaboratorsv2.euwest01.umbraco.io/umbraco/api/comments/PostComment
CONFIGURATION: '{ "method": "POST", "headers": {"Authorization": "Bearer ${{ secrets.OUR_BOT_API_TOKEN }}", "Content-Type": "application/json" }, "body": { "repo": "${{ github.repository }}", "number": "${{ github.event.number }}", "actor": "${{ github.actor }}", "commentType": "opened-pr-first-comment"} }'
- name: Add PR comment
if: "${{ env.fetch-api-data != '' }}"
uses: actions/github-script@v5
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `${{ env.fetch-api-data }}`
})

6
.gitignore vendored
View File

@@ -85,6 +85,12 @@ src/Umbraco.Web.UI/wwwroot/[Uu]mbraco/lib/*
src/Umbraco.Web.UI/wwwroot/[Uu]mbraco/views/*
src/Umbraco.Web.UI/wwwroot/Media/*
src/Umbraco.Web.UI/Smidge/
src/Umbraco.Web.UI/App_Code/
src/Umbraco.Web.UI/App_Plugins/
src/Umbraco.Web.UI/Views/
!src/Umbraco.Web.UI/Views/Partials/blocklist/
!src/Umbraco.Web.UI/Views/Partials/grid/
!src/Umbraco.Web.UI/Views/_ViewImports.cshtml
# Tests
cypress.env.json

View File

@@ -1,7 +1,8 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DefaultItemExcludes>$(DefaultItemExcludes);wwwroot\is-cache\**;wwwroot\ms-cache\**</DefaultItemExcludes>
<DefaultItemExcludes>$(DefaultItemExcludes);App_Plugins/**</DefaultItemExcludes>
<DefaultItemExcludes>$(DefaultItemExcludes);umbraco/Data/**</DefaultItemExcludes>
<DefaultItemExcludes>$(DefaultItemExcludes);umbraco/Logs/**</DefaultItemExcludes>
<DefaultItemExcludes>$(DefaultItemExcludes);wwwroot/media/**</DefaultItemExcludes>
</PropertyGroup>
</Project>

View File

@@ -1,4 +1,4 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ContentFilesPath>$(MSBuildThisFileDirectory)..\content\umbraco\**\*.*</ContentFilesPath>
@@ -6,16 +6,6 @@
<UmbracoWwwrootName Condition="'$(UmbracoWwwrootName)' == ''">umbraco</UmbracoWwwrootName>
</PropertyGroup>
<PropertyGroup>
<DefaultItemExcludes>$(DefaultItemExcludes);App_Plugins\**;</DefaultItemExcludes>
<DefaultItemExcludes>$(DefaultItemExcludes);umbraco\Data\**;</DefaultItemExcludes>
<DefaultItemExcludes>$(DefaultItemExcludes);umbraco\Logs\**;</DefaultItemExcludes>
<DefaultItemExcludes>$(DefaultItemExcludes);umbraco\mediacache\**;</DefaultItemExcludes>
<DefaultItemExcludes>$(DefaultItemExcludes);wwwroot\media\**;</DefaultItemExcludes>
</PropertyGroup>
<Target Name="CopyUmbracoAssets" BeforeTargets="BeforeBuild">
<ItemGroup>
<ContentFiles Include="$(ContentFilesPath)" />
@@ -39,6 +29,7 @@
<ContentWithTargetPath
Include="@(_AppPluginsFiles)"
Exclude="@(ContentWithTargetPath)"
TargetPath="%(Identity)"
CopyToOutputDirectory="PreserveNewest"
CopyToPublishDirectory="PreserveNewest"/>
@@ -65,6 +56,7 @@
<ContentWithTargetPath
Include="@(_UmbracoFolderFiles)"
Exclude="@(ContentWithTargetPath)"
TargetPath="%(Identity)"
CopyToOutputDirectory="PreserveNewest"
CopyToPublishDirectory="PreserveNewest"/>
@@ -92,7 +84,7 @@
<Target Name="IncludeUmbracoRazorFiles" BeforeTargets="ResolveRazorGenerateInputs">
<ItemGroup>
<Content Include="$(MSBuildProjectDirectory)\umbraco\**\*.cshtml" />
<Content Include="$(MSBuildProjectDirectory)\umbraco\**\*.cshtml" Exclude="@(Content)" />
</ItemGroup>
</Target>

View File

@@ -540,41 +540,26 @@ stages:
$ubuild = build/build.ps1 -get -continue
$version = $ubuild.GetUmbracoVersion()
$isRelease = [regex]::matches($env:BUILD_SOURCEBRANCH,"v\d+\/\d+.\d+.*")
if ($isRelease.Count -gt 0) {
$continuous = $version.Semver
}
else
{
} else {
$date = (Get-Date).ToString("yyyyMMdd")
$continuous = "$($version.release)-preview$date.$(Build.BuildId)"
$ubuild.SetUmbracoVersion($continuous)
#Update the version in templates also
$templatePath =
'build/templates/UmbracoProject/.template.config/template.json'
$a = Get-Content $templatePath -raw | ConvertFrom-Json
$a.symbols.version.defaultValue = $continuous
$a | ConvertTo-Json -depth 32| set-content $templatePath
$templatePath =
'build/templates/UmbracoPackage/.template.config/template.json'
$a = Get-Content $templatePath -raw | ConvertFrom-Json
$a.symbols.version.defaultValue = $continuous
$a | ConvertTo-Json -depth 32| set-content $templatePath
# Update the default Umbraco version in templates
$templatePaths = Get-ChildItem 'templates/**/.template.config/template.json'
foreach ($templatePath in $templatePaths) {
$a = Get-Content $templatePath -Raw | ConvertFrom-Json
if ($a.symbols -and $a.symbols.UmbracoVersion) {
$a.symbols.UmbracoVersion.defaultValue = $continuous
$a | ConvertTo-Json -Depth 32 | Set-Content $templatePath
}
}
}
Write-Host "##vso[build.updatebuildnumber]$continuous.$(Build.BuildId)"

View File

@@ -40,7 +40,7 @@
@{ Continue = $continue })
if ($ubuild.OnError()) { return }
Write-Host "Umbraco Cms Build"
Write-Host "Umbraco CMS Build"
Write-Host "Umbraco.Build v$($ubuild.BuildVersion)"
# ################################################################
@@ -84,7 +84,7 @@
$this.SetEnvVar("NPM_CONFIG_CACHE", $node_npmcache)
$this.SetEnvVar("NPM_CONFIG_PREFIX", $node_npmprefix)
$ignore = $this.ClearEnvVar("NODE_NO_HTTP2")
$this.ClearEnvVar("NODE_NO_HTTP2")
})
$ubuild.DefineMethod("CompileBelle",
@@ -171,11 +171,6 @@
$src = "$($this.SolutionRoot)\src"
$log = "$($this.BuildTemp)\build.umbraco.log"
if ($this.BuildEnv.VisualStudio -eq $null)
{
throw "Build environment does not provide VisualStudio."
}
Write-Host "Compile Umbraco"
Write-Host "Logging to $log"
@@ -191,14 +186,14 @@
# remove extra files
$webAppBin = "$($this.BuildTemp)\WebApp\bin"
$excludeDirs = @("$($webAppBin)\refs","$($webAppBin)\runtimes","$($webAppBin)\Umbraco","$($webAppBin)\wwwroot")
$excludeDirs = @("$($webAppBin)\refs","$($webAppBin)\runtimes","$($webAppBin)\umbraco","$($webAppBin)\wwwroot")
$excludeFiles = @("$($webAppBin)\appsettings.*","$($webAppBin)\*.deps.json","$($webAppBin)\*.exe","$($webAppBin)\*.config","$($webAppBin)\*.runtimeconfig.json")
$this.RemoveDirectory($excludeDirs)
$this.RemoveFile($excludeFiles)
# copy rest of the files into WebApp
$this.CopyFiles("$($this.SolutionRoot)\src\Umbraco.Web.UI\Umbraco", "*", "$($this.BuildTemp)\WebApp\umbraco")
$excludeUmbracoDirs = @("$($this.BuildTemp)\WebApp\umbraco\lib")
$this.CopyFiles("$($this.SolutionRoot)\src\Umbraco.Web.UI\umbraco", "*", "$($this.BuildTemp)\WebApp\umbraco")
$excludeUmbracoDirs = @("$($this.BuildTemp)\WebApp\umbraco\lib","$($this.BuildTemp)\WebApp\umbraco\Data","$($this.BuildTemp)\WebApp\umbraco\Logs")
$this.RemoveDirectory($excludeUmbracoDirs)
$this.CopyFiles("$($this.SolutionRoot)\src\Umbraco.Web.UI\Views", "*", "$($this.BuildTemp)\WebApp\Views")
Copy-Item "$($this.SolutionRoot)\src\Umbraco.Web.UI\appsettings.json" "$($this.BuildTemp)\WebApp"
@@ -251,27 +246,21 @@
$buildConfiguration = "Release"
$log = "$($this.BuildTemp)\msbuild.tests.log"
if ($this.BuildEnv.VisualStudio -eq $null)
{
throw "Build environment does not provide VisualStudio."
}
Write-Host "Compile Tests"
Write-Host "Logging to $log"
# beware of the weird double \\ at the end of paths
# see http://edgylogic.com/blog/powershell-and-external-commands-done-right/
&$this.BuildEnv.VisualStudio.MsBuild "$($this.SolutionRoot)\tests\Umbraco.Tests\Umbraco.Tests.csproj" `
/p:WarningLevel=0 `
/p:Configuration=$buildConfiguration `
/p:Platform=AnyCPU `
/p:UseWPP_CopyWebApplication=True `
/p:PipelineDependsOnBuild=False `
/p:OutDir="$($this.BuildTemp)\tests\\" `
/p:Verbosity=minimal `
/t:Build `
/tv:"$($this.BuildEnv.VisualStudio.ToolsVersion)" `
/p:UmbracoBuild=True `
&dotnet msbuild "$($this.SolutionRoot)\tests\Umbraco.Tests\Umbraco.Tests.csproj" `
-target:Build `
-property:WarningLevel=0 `
-property:Configuration=$buildConfiguration `
-property:Platform=AnyCPU `
-property:UseWPP_CopyWebApplication=True `
-property:PipelineDependsOnBuild=False `
-property:OutDir="$($this.BuildTemp)\tests\\" `
-property:Verbosity=minimal `
-property:UmbracoBuild=True `
> $log
if (-not $?) { throw "Failed to compile tests." }
@@ -285,10 +274,6 @@
$src = "$($this.SolutionRoot)\src"
$tmp = "$($this.BuildTemp)"
$out = "$($this.BuildOutput)"
$templates = "$($this.SolutionRoot)\build\templates"
$buildConfiguration = "Release"
# cleanup build
Write-Host "Clean build"
@@ -302,7 +287,6 @@
# create directories
Write-Host "Create directories"
mkdir "$tmp\WebApp\App_Data" > $null
mkdir "$tmp\Templates" > $null
#mkdir "$tmp\WebApp\Media" > $null
#mkdir "$tmp\WebApp\Views" > $null
@@ -324,17 +308,6 @@
$this.CopyFiles("$src\Umbraco.Web.UI\wwwroot\umbraco\js", "*", "$tmp\WebApp\wwwroot\umbraco\js")
$this.CopyFiles("$src\Umbraco.Web.UI\wwwroot\umbraco\lib", "*", "$tmp\WebApp\wwwroot\umbraco\lib")
$this.CopyFiles("$src\Umbraco.Web.UI\wwwroot\umbraco\views", "*", "$tmp\WebApp\wwwroot\umbraco\views")
# Prepare templates
Write-Host "Copy template files"
$this.CopyFiles("$templates", "*", "$tmp\Templates")
Write-Host "Copy files for dotnet templates"
$this.CopyFiles("$src\Umbraco.Web.UI", "Program.cs", "$tmp\Templates\UmbracoProject")
$this.CopyFiles("$src\Umbraco.Web.UI", "Startup.cs", "$tmp\Templates\UmbracoProject")
$this.CopyFiles("$src\Umbraco.Web.UI\Views", "*", "$tmp\Templates\UmbracoProject\Views")
$this.RemoveDirectory("$tmp\Templates\UmbracoProject\bin")
})
@@ -376,7 +349,7 @@
$ubuild.DefineMethod("PackageNuGet",
{
$nuspecs = "$($this.SolutionRoot)\build\NuSpecs"
$templates = "$($this.BuildTemp)\Templates"
$templates = "$($this.SolutionRoot)\templates"
Write-Host "Create NuGet packages"

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="4.1.0">
<id>Umbraco.Templates</id>
<version>1.0.0</version>
<authors>Umbraco HQ</authors>
<owners>Umbraco HQ</owners>
<license type="expression">MIT</license>
<projectUrl>https://umbraco.com/</projectUrl>
<iconUrl>https://umbraco.com/dist/nuget/logo-small.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Umbraco Cms templates for .NET Core Template Engine available through the dotnet CLI's new command</description>
<language>en-US</language>
<tags>umbraco</tags>
<repository type="git" url="https://github.com/umbraco/umbraco-cms" />
<packageTypes>
<packageType name="Template" />
</packageTypes>
</metadata>
</package>

View File

@@ -1,6 +0,0 @@
{
"$schema": "http://json.schemastore.org/dotnetcli.host",
"symbolInfo": {
}
}

View File

@@ -1,13 +0,0 @@
{
"$schema": "http://json.schemastore.org/vs-2017.3.host",
"order" : 0,
"icon": "icon.png",
"description": {
"id": "UmbracoPackage",
"text": "Umbraco Package - An empty Umbraco CMS package (Plugin)"
},
"symbolInfo": [
]
}

View File

@@ -1,95 +0,0 @@
{
"$schema": "http://json.schemastore.org/template",
"author": "Umbraco HQ",
"description": "An empty Umbraco Package/Plugin ready to get started",
"classifications": [ "Web", "CMS", "Umbraco", "Package", "Plugin"],
"groupIdentity": "Umbraco.Templates.UmbracoPackage",
"identity": "Umbraco.Templates.UmbracoPackage.CSharp",
"name": "Umbraco Package",
"shortName": "umbracopackage",
"defaultName": "UmbracoPackage1",
"preferNameDirectory": true,
"tags": {
"language": "C#",
"type": "project"
},
"primaryOutputs": [
{
"path": "UmbracoPackage.csproj"
}
],
"sourceName": "UmbracoPackage",
"preferNameDirectory": true,
"symbols": {
"version": {
"type": "parameter",
"datatype": "string",
"defaultValue": "10.0.0-rc",
"description": "The version of Umbraco to load using NuGet",
"replaces": "UMBRACO_VERSION_FROM_TEMPLATE"
},
"namespaceReplacer": {
"type": "generated",
"generator": "regex",
"dataType": "string",
"replaces": "UmbracoPackage",
"parameters": {
"source": "name",
"steps": [
{
"regex": "\\s",
"replacement": "_"
},
{
"regex": "-",
"replacement": "_"
},
{
"regex": "^[^a-zA-Z_]+",
"replacement": "_"
}
]
}
},
"msbuildReplacer": {
"type": "generated",
"generator": "regex",
"dataType": "string",
"replaces": "UmbracoPackageMsBuild",
"parameters": {
"source": "name",
"steps": [
{
"regex": "\\s",
"replacement": ""
},
{
"regex": "\\.",
"replacement": ""
},
{
"regex": "-",
"replacement": ""
},
{
"regex": "^[^a-zA-Z_]+",
"replacement": ""
}
]
}
},
"Framework": {
"type": "parameter",
"description": "The target framework for the project.",
"datatype": "choice",
"choices": [
{
"choice": "net6.0",
"description": "Target net6.0"
}
],
"replaces": "net6.0",
"defaultValue": "net6.0"
}
}
}

View File

@@ -1,42 +0,0 @@
{
"$schema": "http://json.schemastore.org/dotnetcli.host",
"symbolInfo": {
"PackageTestSiteName": {
"longName": "PackageTestSiteName",
"shortName": "p"
},
"SkipRestore": {
"longName": "no-restore",
"shortName": ""
},
"FriendlyName": {
"longName": "friendly-name",
"shortName": ""
},
"Email": {
"longName": "email",
"shortName": ""
},
"Password": {
"longName": "password",
"shortName": ""
},
"ConnectionString":{
"longName": "connection-string",
"shortName": ""
},
"NoNodesViewPath":{
"longName": "no-nodes-view-path",
"shortName": ""
},
"UseHttpsRedirect": {
"longName": "use-https-redirect",
"shortName": ""
}
},
"usageExamples": [
"dotnet new umbraco -n MyNewProject",
"dotnet new umbraco -n MyNewProject --no-restore",
"dotnet new umbraco -n MyNewProject --friendly-name \"Friendly User\" --email user@email.com --password password1234 --connection-string \"Server=ConnectionStringHere\""
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1,67 +0,0 @@
{
"$schema": "http://json.schemastore.org/vs-2017.3.host",
"order" : 0,
"icon": "icon.png",
"description": {
"id": "UmbracoProject",
"text": "Umbraco Web Application - An empty Umbraco CMS web application"
},
"symbolInfo": [
{
"id": "SkipRestore",
"name": {
"text": "Skips the automatic NuGet restore of the project on create"
},
"isVisible": "true"
},
{
"id": "PackageTestSiteName",
"name": {
"text": "Optional: Specify the name of a package that this should be a test site for"
},
"isVisible": "true"
},
{
"id": "FriendlyName",
"name": {
"text": "Optional: The friendly name of the user for Umbraco login when using Unattended install"
},
"isVisible": "true"
},
{
"id": "Email",
"name": {
"text": "Optional: Email to use for Umbraco login when using Unattended install"
},
"isVisible": "true"
},
{
"id": "Password",
"name": {
"text": "Optional: Password to use for Umbraco login when using Unattended install"
},
"isVisible": "true"
},
{
"id": "ConnectionString",
"name": {
"text": "Optional: Database connection string when using Unattended install"
},
"isVisible": "true"
},
{
"id": "NoNodesViewPath",
"name": {
"text": "Optional: Path to a custom view presented with the Umbraco installation contains no published content"
},
"isVisible": "true"
},
{
"id": "UseHttpsRedirect",
"name": {
"text": "Optional: Adds code to Startup.cs to redirect HTTP to HTTPS and enables the UseHttps setting."
},
"isVisible": "true"
}
]
}

View File

@@ -1,294 +0,0 @@
{
"$schema": "http://json.schemastore.org/template",
"author": "Umbraco HQ",
"description": "An empty Umbraco Project ready to get started",
"classifications": [ "Web", "CMS", "Umbraco"],
"groupIdentity": "Umbraco.Templates.UmbracoProject",
"identity": "Umbraco.Templates.UmbracoProject.CSharp",
"name": "Umbraco Project",
"shortName": "umbraco",
"defaultName": "UmbracoProject1",
"preferNameDirectory": true,
"tags": {
"language": "C#",
"type": "project"
},
"primaryOutputs": [
{
"path": "UmbracoProject.csproj"
}
],
"postActions": [
{
"condition": "(!SkipRestore)",
"description": "Restore NuGet packages required by this project",
"manualInstructions": [{
"text": "Run 'dotnet restore'"
}],
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
"continueOnError": true
}
],
"sourceName": "UmbracoProject",
"symbols": {
"namespaceReplacer": {
"type": "generated",
"generator": "regex",
"dataType": "string",
"replaces": "Umbraco.Cms.Web.UI",
"parameters": {
"source": "name",
"steps": [
{
"regex": "\\s",
"replacement": "_"
},
{
"regex": "-",
"replacement": "_"
},
{
"regex": "^[^a-zA-Z_]+",
"replacement": "_"
}
]
}
},
"version": {
"type": "parameter",
"datatype": "string",
"defaultValue": "10.0.0-rc",
"description": "The version of Umbraco to load using NuGet",
"replaces": "UMBRACO_VERSION_FROM_TEMPLATE"
},
"PackageTestSiteName": {
"type": "parameter",
"datatype":"text",
"defaultValue": "",
"replaces":"PackageTestSiteName",
"description": "The name of the package this should be a test site for (Default: '')"
},
"Framework": {
"type": "parameter",
"description": "The target framework for the project",
"datatype": "choice",
"choices": [
{
"choice": "net6.0",
"description": "Target net6.0"
}
],
"replaces": "net6.0",
"defaultValue": "net6.0"
},
"SkipRestore": {
"type": "parameter",
"datatype": "bool",
"description": "If specified, skips the automatic restore of the project on create",
"defaultValue": "false"
},
"HttpPort": {
"type": "generated",
"generator": "port",
"replaces": "HTTP_PORT_FROM_TEMPLATE",
"parameters": {
"high": 65535,
"low": 1024,
"fallback": 5000
}
},
"HttpsPort": {
"type": "generated",
"generator": "port",
"replaces": "HTTPS_PORT_FROM_TEMPLATE",
"parameters": {
"low": 44300,
"high": 44399,
"fallback": 5001
}
},
"FriendlyName":{
"type": "parameter",
"datatype":"text",
"description": "The friendly name of the user for Umbraco login when using Unattended install (Without installer wizard UI)",
"defaultValue": ""
},
"FriendlyNameReplaced":{
"type": "generated",
"generator": "regex",
"dataType": "string",
"replaces": "FRIENDLY_NAME_FROM_TEMPLATE",
"parameters": {
"source": "FriendlyName",
"steps": [
{
"regex": "\\\\",
"replacement": "\\\\"
},
{
"regex": "\\\"",
"replacement": "\\\""
},
{
"regex": "\\\n",
"replacement": "\\\n"
},
{
"regex": "\\\t",
"replacement": "\\\t"
}
]
}
},
"Email":{
"type": "parameter",
"datatype":"text",
"description": "Email to use for Umbraco login when using Unattended install (Without installer wizard UI)",
"defaultValue": ""
},
"EmailReplaced":{
"type": "generated",
"generator": "regex",
"dataType": "string",
"replaces": "EMAIL_FROM_TEMPLATE",
"parameters": {
"source": "Email",
"steps": [
{
"regex": "\\\\",
"replacement": "\\\\"
},
{
"regex": "\\\"",
"replacement": "\\\""
},
{
"regex": "\\\n",
"replacement": "\\\n"
},
{
"regex": "\\\t",
"replacement": "\\\t"
}
]
}
},
"Password":{
"type": "parameter",
"datatype":"text",
"description": "Password to use for Umbraco login when using Unattended install (Without installer wizard UI)",
"defaultValue": ""
},
"PasswordReplaced":{
"type": "generated",
"generator": "regex",
"dataType": "string",
"replaces": "PASSWORD_FROM_TEMPLATE",
"parameters": {
"source": "Password",
"steps": [
{
"regex": "\\\\",
"replacement": "\\\\"
},
{
"regex": "\\\"",
"replacement": "\\\""
},
{
"regex": "\\\n",
"replacement": "\\\n"
},
{
"regex": "\\\t",
"replacement": "\\\t"
}
]
}
},
"ConnectionString":{
"type": "parameter",
"datatype":"text",
"description": "Database connection string when using Unattended install (Without installer wizard UI)",
"defaultValue": ""
},
"ConnectionStringReplaced":{
"type": "generated",
"generator": "regex",
"dataType": "string",
"replaces": "CONNECTION_FROM_TEMPLATE",
"parameters": {
"source": "ConnectionString",
"steps": [
{
"regex": "\\\\",
"replacement": "\\\\"
},
{
"regex": "\\\"",
"replacement": "\\\""
},
{
"regex": "\\\n",
"replacement": "\\\n"
},
{
"regex": "\\\t",
"replacement": "\\\t"
}
]
}
},
"NoNodesViewPath":{
"type": "parameter",
"datatype":"text",
"description": "Path to a custom view presented with the Umbraco installation contains no published content",
"defaultValue": ""
},
"NoNodesViewPathReplaced":{
"type": "generated",
"generator": "regex",
"dataType": "string",
"replaces": "NO_NODES_VIEW_PATH_FROM_TEMPLATE",
"parameters": {
"source": "NoNodesViewPath",
"steps": [
{
"regex": "\\\\",
"replacement": "\\\\"
},
{
"regex": "\\\"",
"replacement": "\\\""
},
{
"regex": "\\\n",
"replacement": "\\\n"
},
{
"regex": "\\\t",
"replacement": "\\\t"
}
]
}
},
"HasConnectionString":{
"type": "computed",
"value": "(ConnectionString != \"\")"
},
"HasNoNodesViewPath":{
"type": "computed",
"value": "(NoNodesViewPath != \"\")"
},
"UsingUnattenedInstall":{
"type": "computed",
"value": "(FriendlyName != \"\" && Email != \"\" && Password != \"\" && ConnectionString != \"\")"
},
"UseHttpsRedirect":{
"type": "parameter",
"datatype":"bool",
"defaultValue": "false",
"description": "Adds code to Startup.cs to redirect HTTP to HTTPS and enables the UseHttps setting (Default: false)"
}
}
}

View File

@@ -1,38 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">Umbraco.Cms.Web.UI</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Umbraco.Cms" Version="UMBRACO_VERSION_FROM_TEMPLATE" />
</ItemGroup>
<!-- Force windows to use ICU. Otherwise Windows 10 2019H1+ will do it, but older windows 10 and most if not all winodws servers will run NLS -->
<ItemGroup>
<PackageReference Include="Microsoft.ICU.ICU4C.Runtime" Version="68.2.0.9" />
<RuntimeHostConfigurationOption
Condition="$(RuntimeIdentifier.StartsWith('linux')) Or $(RuntimeIdentifier.StartsWith('win')) Or ('$(RuntimeIdentifier)' == '' And !$([MSBuild]::IsOSPlatform('osx')))"
Include="System.Globalization.AppLocalIcu"
Value="68.2.0.9" />
</ItemGroup>
<Import Project="..\PackageTestSiteName\build\PackageTestSiteName.targets" Condition="'$(PackageTestSiteName)' != ''" />
<ItemGroup Condition="'$(PackageTestSiteName)' != ''">
<ProjectReference Include="..\PackageTestSiteName\PackageTestSiteName.csproj" />
</ItemGroup>
<PropertyGroup>
<CopyRazorGenerateFilesToPublishDirectory>true</CopyRazorGenerateFilesToPublishDirectory>
</PropertyGroup>
<!-- Set this to true if ModelsBuilder mode is not InMemoryAuto-->
<PropertyGroup>
<RazorCompileOnBuild>false</RazorCompileOnBuild>
<RazorCompileOnPublish>false</RazorCompileOnPublish>
</PropertyGroup>
</Project>

View File

@@ -1,8 +0,0 @@
@using Umbraco.Web.UI
@using Umbraco.Extensions
@using Umbraco.Web.PublishedModels
@using Umbraco.Cms.Core.Models.PublishedContent
@using Microsoft.AspNetCore.Html
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Smidge
@inject Smidge.SmidgeHelper SmidgeHelper

View File

@@ -5,7 +5,20 @@
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="NJsonSchema" Version="10.5.2" />
<PackageReference Include="System.Xml.XPath.XmlDocument" Version="4.3.0" />
<PackageReference Include="Umbraco.Deploy.Core" Version="9.2.0" />
<PackageReference Include="Umbraco.Forms.Core" Version="9.2.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="NJsonSchema" Version="10.5.2" />
@@ -21,7 +34,7 @@
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
</ItemGroup>
<!-- Copy forms xml docs-->
<!-- Copy Forms XML docs-->
<PropertyGroup>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
</PropertyGroup>

View File

@@ -198,7 +198,7 @@ namespace Umbraco.Cms.Core.Composing
IEnumerable<Assembly> assemblies = null,
bool onlyConcreteClasses = true)
{
var assemblyList = (assemblies ?? AssembliesToScan).ToList();
var assemblyList = assemblies ?? AssembliesToScan;
return GetClassesWithBaseType(assignTypeFrom, assemblyList, onlyConcreteClasses,
//the additional filter will ensure that any found types also have the attribute applied.
@@ -214,7 +214,7 @@ namespace Umbraco.Cms.Core.Composing
/// <returns></returns>
public IEnumerable<Type> FindClassesOfType(Type assignTypeFrom, IEnumerable<Assembly> assemblies = null, bool onlyConcreteClasses = true)
{
var assemblyList = (assemblies ?? AssembliesToScan).ToList();
var assemblyList = assemblies ?? AssembliesToScan;
return GetClassesWithBaseType(assignTypeFrom, assemblyList, onlyConcreteClasses);
}
@@ -231,7 +231,7 @@ namespace Umbraco.Cms.Core.Composing
IEnumerable<Assembly> assemblies = null,
bool onlyConcreteClasses = true)
{
var assemblyList = (assemblies ?? AssembliesToScan).ToList();
var assemblyList = assemblies ?? AssembliesToScan;
return GetClassesWithAttribute(attributeType, assemblyList, onlyConcreteClasses);
}

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
internal const bool StaticHideTopLevelNodeFromPath = true;
internal const bool StaticUseHttps = false;
internal const int StaticVersionCheckPeriod = 7;
internal const string StaticUmbracoPath = "~/umbraco";
internal const string StaticUmbracoPath = Constants.System.DefaultUmbracoPath;
internal const string StaticIconsPath = "~/umbraco/assets/icons";
internal const string StaticUmbracoCssPath = "~/css";
internal const string StaticUmbracoScriptsPath = "~/scripts";

View File

@@ -12,6 +12,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
public class KeepAliveSettings
{
internal const bool StaticDisableKeepAliveTask = false;
internal const string StaticKeepAlivePingUrl = "~/api/keepalive/ping";
/// <summary>
/// Gets or sets a value indicating whether the keep alive task is disabled.
@@ -20,8 +21,9 @@ namespace Umbraco.Cms.Core.Configuration.Models
public bool DisableKeepAliveTask { get; set; } = StaticDisableKeepAliveTask;
/// <summary>
/// Gets a value for the keep alive ping URL.
/// Gets or sets a value for the keep alive ping URL.
/// </summary>
public string KeepAlivePingUrl => "~/api/keepalive/ping";
[DefaultValue(StaticKeepAlivePingUrl)]
public string KeepAlivePingUrl { get; set; } = StaticKeepAlivePingUrl;
}
}

View File

@@ -13,6 +13,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
{
internal const string StaticNuCacheSerializerType = "MessagePack";
internal const int StaticSqlPageSize = 1000;
internal const int StaticKitBatchSize = 1;
/// <summary>
/// Gets or sets a value defining the BTree block size.
@@ -31,6 +32,12 @@ namespace Umbraco.Cms.Core.Configuration.Models
[DefaultValue(StaticSqlPageSize)]
public int SqlPageSize { get; set; } = StaticSqlPageSize;
/// <summary>
/// The size to use for nucache Kit batches. Higher value means more content loaded into memory at a time.
/// </summary>
[DefaultValue(StaticKitBatchSize)]
public int KitBatchSize { get; set; } = StaticKitBatchSize;
public bool UnPublishedContentCompression { get; set; } = false;
}
}

View File

@@ -62,7 +62,7 @@
public const string UmbracoDefaultDatabaseName = "Umbraco";
public const string UmbracoConnectionName = "umbracoDbDSN";
public const string UmbracoConnectionName = "umbracoDbDSN";public const string DefaultUmbracoPath = "~/umbraco";
}
}
}

View File

@@ -0,0 +1,32 @@
namespace Umbraco.Cms.Core
{
public static partial class Constants
{
public static class Telemetry
{
public static string RootCount = "RootCount";
public static string DomainCount = "DomainCount";
public static string ExamineIndexCount = "ExamineIndexCount";
public static string LanguageCount = "LanguageCount";
public static string MacroCount = "MacroCount";
public static string MediaCount = "MediaCount";
public static string MemberCount = "MemberCount";
public static string TemplateCount = "TemplateCount";
public static string ContentCount = "ContentCount";
public static string DocumentTypeCount = "DocumentTypeCount";
public static string Properties = "Properties";
public static string UserCount = "UserCount";
public static string UserGroupCount = "UserGroupCount";
public static string ServerOs = "ServerOs";
public static string ServerFramework = "ServerFramework";
public static string OsLanguage = "OsLanguage";
public static string WebServer = "WebServer";
public static string ModelsBuilderMode = "ModelBuilderMode";
public static string CustomUmbracoPath = "CustomUmbracoPath";
public static string AspEnvironment = "AspEnvironment";
public static string IsDebug = "IsDebug";
public static string DatabaseProvider = "DatabaseProvider";
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace Umbraco.Cms.Core.Dashboards
{
public class AnalyticsDashboard : IDashboard
{
public string Alias => "settingsAnalytics";
public string[] Sections => new [] { "settings" };
public string View => "views/dashboard/settings/analytics.html";
public IAccessRule[] AccessRules => Array.Empty<IAccessRule>();
}
}

View File

@@ -118,7 +118,8 @@ namespace Umbraco.Cms.Core.DependencyInjection
.Append<Soundcloud>()
.Append<Issuu>()
.Append<Hulu>()
.Append<Giphy>();
.Append<Giphy>()
.Append<LottieFiles>();
builder.SearchableTrees().Add(() => builder.TypeLoader.GetTypes<ISearchableTree>());
builder.BackOfficeAssets();
}

View File

@@ -25,7 +25,7 @@ using Umbraco.Cms.Core.Install;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Mail;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Packaging;
@@ -42,7 +42,6 @@ using Umbraco.Cms.Core.Sync;
using Umbraco.Cms.Core.Telemetry;
using Umbraco.Cms.Core.Templates;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.DependencyInjection
@@ -200,7 +199,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddSingleton<UriUtility>();
Services.AddUnique<IDashboardService, DashboardService>();
Services.AddUnique<IUserDataService, UserDataService>();
Services.AddSingleton<IMetricsConsentService, MetricsConsentService>();
// will be injected in controllers when needed to invoke rest endpoints on Our
Services.AddUnique<IInstallationService, InstallationService>();

View File

@@ -1082,7 +1082,7 @@ namespace Umbraco.Extensions
{
return content.Parent != null
? content.Parent.Children(variationContextAccessor, culture)
: publishedSnapshot.Content.GetAtRoot().WhereIsInvariantOrHasCulture(variationContextAccessor, culture);
: publishedSnapshot.Content.GetAtRoot(culture).WhereIsInvariantOrHasCulture(variationContextAccessor, culture);
}
/// <summary>
@@ -1098,7 +1098,7 @@ namespace Umbraco.Extensions
{
return content.Parent != null
? content.Parent.ChildrenOfType(variationContextAccessor, contentTypeAlias, culture)
: publishedSnapshot.Content.GetAtRoot().OfTypes(contentTypeAlias).WhereIsInvariantOrHasCulture(variationContextAccessor, culture);
: publishedSnapshot.Content.GetAtRoot(culture).OfTypes(contentTypeAlias).WhereIsInvariantOrHasCulture(variationContextAccessor, culture);
}
/// <summary>
@@ -1115,7 +1115,7 @@ namespace Umbraco.Extensions
{
return content.Parent != null
? content.Parent.Children<T>(variationContextAccessor, culture)
: publishedSnapshot.Content.GetAtRoot().OfType<T>().WhereIsInvariantOrHasCulture(variationContextAccessor, culture);
: publishedSnapshot.Content.GetAtRoot(culture).OfType<T>().WhereIsInvariantOrHasCulture(variationContextAccessor, culture);
}
#endregion

View File

@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Umbraco.Cms.Core.Serialization;
namespace Umbraco.Cms.Core.Media.EmbedProviders
{
/// <summary>
/// Embed Provider for lottiefiles.com the popular opensource JSON-based animation format platform.
/// </summary>
public class LottieFiles : OEmbedProviderBase
{
public LottieFiles(IJsonSerializer jsonSerializer) : base(jsonSerializer)
{
}
public override string ApiEndpoint => "https://embed.lottiefiles.com/oembed";
public override string[] UrlSchemeRegex => new string[]
{
@"lottiefiles\.com/*"
};
public override Dictionary<string, string> RequestParams => new Dictionary<string, string>();
public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0)
{
var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight);
OEmbedResponse oembed = base.GetJsonResponse<OEmbedResponse>(requestUrl);
var html = oembed.GetHtml();
//LottieFiles doesn't seem to support maxwidth and maxheight via oembed
// this is therefore a hack... with regexes.. is that ok? HtmlAgility etc etc
// otherwise it always defaults to 300...
if (maxWidth > 0 && maxHeight > 0)
{
html = Regex.Replace(html, "width=\"([0-9]{1,4})\"", "width=\"" + maxWidth + "\"");
html = Regex.Replace(html, "height=\"([0-9]{1,4})\"", "height=\"" + maxHeight + "\"");
}
else
{
//if set to 0, let's default to 100% as an easter egg
html = Regex.Replace(html, "width=\"([0-9]{1,4})\"", "width=\"100%\"");
html = Regex.Replace(html, "height=\"([0-9]{1,4})\"", "height=\"100%\"");
}
return html;
}
}
}

View File

@@ -0,0 +1,12 @@
using System.Runtime.Serialization;
namespace Umbraco.Cms.Core.Models
{
[DataContract]
public enum TelemetryLevel
{
Minimal,
Basic,
Detailed,
}
}

View File

@@ -0,0 +1,11 @@
using System.Runtime.Serialization;
namespace Umbraco.Cms.Core.Models
{
[DataContract]
public class TelemetryResource
{
[DataMember]
public TelemetryLevel TelemetryLevel { get; set; }
}
}

View File

@@ -0,0 +1,20 @@
using System.Runtime.Serialization;
namespace Umbraco.Cms.Core.Models
{
[DataContract]
public class UsageInformation
{
[DataMember(Name = "name")]
public string Name { get; }
[DataMember(Name = "data")]
public object Data { get; }
public UsageInformation(string name, object data)
{
Name = name;
Data = data;
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Runtime.Serialization;
namespace Umbraco.Cms.Core.Models
{
[DataContract]
public class UserTwoFactorProviderModel
{
public UserTwoFactorProviderModel(string providerName, bool isEnabledOnUser)
{
ProviderName = providerName;
IsEnabledOnUser = isEnabledOnUser;
}
[DataMember(Name = "providerName")]
public string ProviderName { get; }
[DataMember(Name = "isEnabledOnUser")]
public bool IsEnabledOnUser { get; }
}
}

View File

@@ -0,0 +1,19 @@
using System.Collections.Generic;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Web;
namespace Umbraco.Cms.Core.Notifications
{
public class SendingAllowedChildrenNotification : INotification
{
public IUmbracoContext UmbracoContext { get; }
public IEnumerable<ContentTypeBasic> Children { get; set; }
public SendingAllowedChildrenNotification(IEnumerable<ContentTypeBasic> children, IUmbracoContext umbracoContext)
{
UmbracoContext = umbracoContext;
Children = children;
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace Umbraco.Cms.Core.Notifications
{
public class UserTwoFactorRequestedNotification : INotification
{
public UserTwoFactorRequestedNotification(Guid userKey)
{
UserKey = userKey;
}
public Guid UserKey { get; }
}
}

View File

@@ -0,0 +1,7 @@
namespace Umbraco.Cms.Core.Services
{
public interface IExamineIndexCountService
{
public int GetCount();
}
}

View File

@@ -0,0 +1,11 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Services
{
public interface IMetricsConsentService
{
TelemetryLevel GetConsentLevel();
void SetConsentLevel(TelemetryLevel telemetryLevel);
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Umbraco.Cms.Core.Services
{
public interface INodeCountService
{
int GetNodeCount(Guid nodeType);
int GetMediaCount();
}
}

View File

@@ -58,4 +58,12 @@ namespace Umbraco.Cms.Core.Services
/// </summary>
Task<IEnumerable<string>> GetEnabledTwoFactorProviderNamesAsync(Guid userOrMemberKey);
}
[Obsolete("This will be merged into ITwoFactorLoginService in Umbraco 11")]
public interface ITwoFactorLoginService2 : ITwoFactorLoginService
{
Task<bool> DisableWithCodeAsync(string providerName, Guid userOrMemberKey, string code);
Task<bool> ValidateAndSaveAsync(string providerName, Guid userKey, string secret, string code);
}
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Services
{
public interface IUsageInformationService
{
IEnumerable<UsageInformation> GetDetailed();
}
}

View File

@@ -0,0 +1,34 @@
using System;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Services
{
public class MetricsConsentService : IMetricsConsentService
{
internal const string Key = "UmbracoAnalyticsLevel";
private readonly IKeyValueService _keyValueService;
public MetricsConsentService(IKeyValueService keyValueService)
{
_keyValueService = keyValueService;
}
public TelemetryLevel GetConsentLevel()
{
var analyticsLevelString = _keyValueService.GetValue(Key);
if (analyticsLevelString is null || Enum.TryParse(analyticsLevelString, out TelemetryLevel analyticsLevel) is false)
{
return TelemetryLevel.Basic;
}
return analyticsLevel;
}
public void SetConsentLevel(TelemetryLevel telemetryLevel)
{
_keyValueService.SetValue(Key, telemetryLevel.ToString());
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
@@ -9,11 +10,13 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Services
{
[Obsolete("Use the IUserDataService interface instead")]
public class UserDataService : IUserDataService
{
private readonly IUmbracoVersion _version;
private readonly ILocalizationService _localizationService;
public UserDataService(IUmbracoVersion version, ILocalizationService localizationService)
{
_version = version;

View File

@@ -84,5 +84,11 @@ namespace Umbraco.Extensions
});
}
public static IUser GetByKey(this IUserService userService, Guid key)
{
int id = BitConverter.ToInt32(key.ToByteArray(), 0);
return userService.GetUserById(id);
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Telemetry.Models
{
@@ -30,5 +31,8 @@ namespace Umbraco.Cms.Core.Telemetry.Models
/// </remarks>
[DataMember(Name = "packages")]
public IEnumerable<PackageTelemetry> Packages { get; set; }
[DataMember(Name = "detailed")]
public IEnumerable<UsageInformation> Detailed { get; set; }
}
}

View File

@@ -5,6 +5,8 @@ using System;
using System.Collections.Generic;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Telemetry.Models;
using Umbraco.Extensions;
@@ -16,6 +18,8 @@ namespace Umbraco.Cms.Core.Telemetry
private readonly IManifestParser _manifestParser;
private readonly IUmbracoVersion _umbracoVersion;
private readonly ISiteIdentifierService _siteIdentifierService;
private readonly IUsageInformationService _usageInformationService;
private readonly IMetricsConsentService _metricsConsentService;
/// <summary>
/// Initializes a new instance of the <see cref="TelemetryService"/> class.
@@ -23,11 +27,15 @@ namespace Umbraco.Cms.Core.Telemetry
public TelemetryService(
IManifestParser manifestParser,
IUmbracoVersion umbracoVersion,
ISiteIdentifierService siteIdentifierService)
ISiteIdentifierService siteIdentifierService,
IUsageInformationService usageInformationService,
IMetricsConsentService metricsConsentService)
{
_manifestParser = manifestParser;
_umbracoVersion = umbracoVersion;
_siteIdentifierService = siteIdentifierService;
_usageInformationService = usageInformationService;
_metricsConsentService = metricsConsentService;
}
/// <inheritdoc/>
@@ -42,14 +50,30 @@ namespace Umbraco.Cms.Core.Telemetry
telemetryReportData = new TelemetryReportData
{
Id = telemetryId,
Version = _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild(),
Version = GetVersion(),
Packages = GetPackageTelemetry(),
Detailed = _usageInformationService.GetDetailed(),
};
return true;
}
private string GetVersion()
{
if (_metricsConsentService.GetConsentLevel() == TelemetryLevel.Minimal)
{
return null;
}
return _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild();
}
private IEnumerable<PackageTelemetry> GetPackageTelemetry()
{
if (_metricsConsentService.GetConsentLevel() == TelemetryLevel.Minimal)
{
return null;
}
List<PackageTelemetry> packages = new();
IEnumerable<PackageManifest> manifests = _manifestParser.GetManifests();

View File

@@ -55,6 +55,7 @@ using Umbraco.Cms.Infrastructure.Runtime;
using Umbraco.Cms.Infrastructure.Scoping;
using Umbraco.Cms.Infrastructure.Search;
using Umbraco.Cms.Infrastructure.Serialization;
using Umbraco.Cms.Infrastructure.Services.Implement;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.DependencyInjection
@@ -201,6 +202,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddSingleton<PackageDataInstallation>();
builder.Services.AddTransient<INodeCountService, NodeCountService>();
builder.AddInstaller();
// Services required to run background jobs (with out the handler)

View File

@@ -47,6 +47,9 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddSingleton<PackageDataInstallation>();
builder.Services.AddUnique<IPackageInstallation, PackageInstallation>();
builder.Services.AddUnique<IHtmlMacroParameterParser, HtmlMacroParameterParser>();
builder.Services.AddTransient<IExamineIndexCountService, ExamineIndexCountService>();
builder.Services.AddUnique<IUserDataService, SystemInformationTelemetryProvider>();
builder.Services.AddTransient<IUsageInformationService, UsageInformationService>();
return builder;
}

View File

@@ -0,0 +1,25 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Infrastructure.Telemetry.Interfaces;
using Umbraco.Cms.Infrastructure.Telemetry.Providers;
namespace Umbraco.Cms.Infrastructure.DependencyInjection
{
public static class UmbracoBuilder_TelemetryProviders
{
public static IUmbracoBuilder AddTelemetryProviders(this IUmbracoBuilder builder)
{
builder.Services.AddTransient<IDetailedTelemetryProvider, ContentTelemetryProvider>();
builder.Services.AddTransient<IDetailedTelemetryProvider, DomainTelemetryProvider>();
builder.Services.AddTransient<IDetailedTelemetryProvider, ExamineTelemetryProvider>();
builder.Services.AddTransient<IDetailedTelemetryProvider, LanguagesTelemetryProvider>();
builder.Services.AddTransient<IDetailedTelemetryProvider, MacroTelemetryProvider>();
builder.Services.AddTransient<IDetailedTelemetryProvider, MediaTelemetryProvider>();
builder.Services.AddTransient<IDetailedTelemetryProvider, NodeCountTelemetryProvider>();
builder.Services.AddTransient<IDetailedTelemetryProvider, PropertyEditorTelemetryProvider>();
builder.Services.AddTransient<IDetailedTelemetryProvider, UserTelemetryProvider>();
builder.Services.AddTransient<IDetailedTelemetryProvider, SystemInformationTelemetryProvider>();
return builder;
}
}
}

View File

@@ -859,7 +859,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
if (_database.Exists<NodeDto>(1048))
{
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1048, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MemberPicker, DbType = "Ntext" });
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1048, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MediaPicker, DbType = "Ntext" });
}
if (_database.Exists<NodeDto>(1049))

View File

@@ -41,7 +41,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot;
public override bool? IsValue(object value, PropertyValueLevel level) => value?.ToString() != "[]";
public override bool? IsValue(object value, PropertyValueLevel level) => value != null && value.ToString() != "[]";
public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) => source?.ToString();
@@ -51,7 +51,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
{
var maxNumber = propertyType.DataType.ConfigurationAs<MultiUrlPickerConfiguration>().MaxNumber;
if (inter == null)
if (string.IsNullOrWhiteSpace(inter?.ToString()))
{
return maxNumber == 1 ? null : Enumerable.Empty<Link>();
}

View File

@@ -35,6 +35,7 @@ namespace Umbraco.Cms.Core.Security
private readonly GlobalSettings _globalSettings;
private readonly IUmbracoMapper _mapper;
private readonly AppCaches _appCaches;
private readonly ITwoFactorLoginService _twoFactorLoginService;
/// <summary>
/// Initializes a new instance of the <see cref="BackOfficeUserStore"/> class.
@@ -48,7 +49,8 @@ namespace Umbraco.Cms.Core.Security
IOptionsSnapshot<GlobalSettings> globalSettings,
IUmbracoMapper mapper,
BackOfficeErrorDescriber describer,
AppCaches appCaches)
AppCaches appCaches,
ITwoFactorLoginService twoFactorLoginService)
: base(describer)
{
_scopeProvider = scopeProvider;
@@ -58,10 +60,71 @@ namespace Umbraco.Cms.Core.Security
_globalSettings = globalSettings.Value;
_mapper = mapper;
_appCaches = appCaches;
_twoFactorLoginService = twoFactorLoginService;
_userService = userService;
_externalLoginService = externalLoginService;
}
[Obsolete("Use non obsolete ctor")]
public BackOfficeUserStore(
IScopeProvider scopeProvider,
IUserService userService,
IEntityService entityService,
IExternalLoginWithKeyService externalLoginService,
IOptions<GlobalSettings> globalSettings,
IUmbracoMapper mapper,
BackOfficeErrorDescriber describer,
AppCaches appCaches)
: this(
scopeProvider,
userService,
entityService,
externalLoginService,
StaticServiceProvider.Instance.GetRequiredService<IOptionsSnapshot<GlobalSettings>>(),
mapper,
describer,
appCaches,
StaticServiceProvider.Instance.GetRequiredService<ITwoFactorLoginService>())
{
}
[Obsolete("Use non obsolete ctor")]
public BackOfficeUserStore(
IScopeProvider scopeProvider,
IUserService userService,
IEntityService entityService,
IExternalLoginService externalLoginService,
IOptions<GlobalSettings> globalSettings,
IUmbracoMapper mapper,
BackOfficeErrorDescriber describer,
AppCaches appCaches)
: this(
scopeProvider,
userService,
entityService,
StaticServiceProvider.Instance.GetRequiredService<IExternalLoginWithKeyService>(),
StaticServiceProvider.Instance.GetRequiredService<IOptionsSnapshot<GlobalSettings>>(),
mapper,
describer,
appCaches,
StaticServiceProvider.Instance.GetRequiredService<ITwoFactorLoginService>())
{
}
/// <inheritdoc />
public override async Task<bool> GetTwoFactorEnabledAsync(BackOfficeIdentityUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
if (!int.TryParse(user.Id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intUserId))
{
return await base.GetTwoFactorEnabledAsync(user, cancellationToken);
}
return await _twoFactorLoginService.IsTwoFactorEnabledAsync(user.Key);
}
/// <inheritdoc />
public override Task<IdentityResult> CreateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default)
{

View File

@@ -597,7 +597,10 @@ namespace Umbraco.Cms.Core.Security
|| (member.LastLoginDate != default && identityUser.LastLoginDateUtc.HasValue == false)
|| (identityUser.LastLoginDateUtc.HasValue && member.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value))
{
changeType = MemberDataChangeType.LoginOnly;
// If the LastLoginDate is default on the member we have to do a full save.
// This is because the umbraco property data for the member doesn't exist yet in this case
// meaning we can't just update that property data, but have to do a full save to create it
changeType = member.LastLoginDate == default ? MemberDataChangeType.FullSave : MemberDataChangeType.LoginOnly;
// if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime
DateTime dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime();

View File

@@ -49,10 +49,10 @@ namespace Umbraco.Cms.Core.Security
public override bool SupportsQueryableUsers => false; // It would be nice to support this but we don't need to currently and that would require IQueryable support for our user service/repository
/// <summary>
/// Developers will need to override this to support custom 2 factor auth
/// Both users and members supports 2FA
/// </summary>
/// <inheritdoc />
public override bool SupportsUserTwoFactor => false;
public override bool SupportsUserTwoFactor => true;
/// <inheritdoc />
public override bool SupportsUserPhoneNumber => false; // We haven't needed to support this yet, though might be necessary for 2FA

View File

@@ -0,0 +1,21 @@
using System.Linq;
using Examine;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Infrastructure.Services.Implement
{
public class ExamineIndexCountService : IExamineIndexCountService
{
private readonly IExamineManager _examineManager;
public ExamineIndexCountService(IExamineManager examineManager)
{
_examineManager = examineManager;
}
public int GetCount()
{
return _examineManager.Indexes.Count();
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Services.Implement
{
public class NodeCountService : INodeCountService
{
private readonly IScopeProvider _scopeProvider;
public NodeCountService(IScopeProvider scopeProvider) => _scopeProvider = scopeProvider;
public int GetNodeCount(Guid nodeType)
{
int count = 0;
using (IScope scope = _scopeProvider.CreateScope(autoComplete: true))
{
var query = scope.Database.SqlContext.Sql()
.SelectCount()
.From<NodeDto>()
.Where<NodeDto>(x => x.NodeObjectType == nodeType && x.Trashed == false);
count = scope.Database.ExecuteScalar<int>(query);
}
return count;
}
public int GetMediaCount()
{
using (IScope scope = _scopeProvider.CreateScope(autoComplete: true))
{
var query = scope.Database.SqlContext.Sql()
.SelectCount()
.From<NodeDto>()
.InnerJoin<ContentDto>()
.On<NodeDto, ContentDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentTypeDto>()
.On<ContentDto, ContentTypeDto>(left => left.ContentTypeId, right => right.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == Constants.ObjectTypes.Media)
.Where<NodeDto>(x => !x.Trashed)
.Where<ContentTypeDto>(x => x.Alias != Constants.Conventions.MediaTypes.Folder);
return scope.Database.ExecuteScalar<int>(query);
}
}
}
}

View File

@@ -3,22 +3,26 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Web.Common.DependencyInjection;
namespace Umbraco.Cms.Core.Services
{
/// <inheritdoc />
public class TwoFactorLoginService : ITwoFactorLoginService
public class TwoFactorLoginService : ITwoFactorLoginService2
{
private readonly ITwoFactorLoginRepository _twoFactorLoginRepository;
private readonly IScopeProvider _scopeProvider;
private readonly IOptions<IdentityOptions> _identityOptions;
private readonly IOptions<BackOfficeIdentityOptions> _backOfficeIdentityOptions;
private readonly IDictionary<string, ITwoFactorProvider> _twoFactorSetupGenerators;
private readonly ILogger<TwoFactorLoginService> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="TwoFactorLoginService"/> class.
@@ -28,16 +32,34 @@ namespace Umbraco.Cms.Core.Services
IScopeProvider scopeProvider,
IEnumerable<ITwoFactorProvider> twoFactorSetupGenerators,
IOptions<IdentityOptions> identityOptions,
IOptions<BackOfficeIdentityOptions> backOfficeIdentityOptions
)
IOptions<BackOfficeIdentityOptions> backOfficeIdentityOptions,
ILogger<TwoFactorLoginService> logger)
{
_twoFactorLoginRepository = twoFactorLoginRepository;
_scopeProvider = scopeProvider;
_identityOptions = identityOptions;
_backOfficeIdentityOptions = backOfficeIdentityOptions;
_logger = logger;
_twoFactorSetupGenerators = twoFactorSetupGenerators.ToDictionary(x =>x.ProviderName);
}
[Obsolete("Use ctor with all params - This will be removed in v11")]
public TwoFactorLoginService(
ITwoFactorLoginRepository twoFactorLoginRepository,
IScopeProvider scopeProvider,
IEnumerable<ITwoFactorProvider> twoFactorSetupGenerators,
IOptions<IdentityOptions> identityOptions,
IOptions<BackOfficeIdentityOptions> backOfficeIdentityOptions)
: this(twoFactorLoginRepository,
scopeProvider,
twoFactorSetupGenerators,
identityOptions,
backOfficeIdentityOptions,
StaticServiceProvider.Instance.GetRequiredService<ILogger<TwoFactorLoginService>>())
{
}
/// <inheritdoc />
public async Task DeleteUserLoginsAsync(Guid userOrMemberKey)
{
@@ -51,6 +73,56 @@ namespace Umbraco.Cms.Core.Services
return await GetEnabledProviderNamesAsync(userOrMemberKey);
}
public async Task<bool> DisableWithCodeAsync(string providerName, Guid userOrMemberKey, string code)
{
var secret = await GetSecretForUserAndProviderAsync(userOrMemberKey, providerName);
if (!_twoFactorSetupGenerators.TryGetValue(providerName, out ITwoFactorProvider generator))
{
throw new InvalidOperationException($"No ITwoFactorSetupGenerator found for provider: {providerName}");
}
var isValid = generator.ValidateTwoFactorPIN(secret, code);
if (!isValid)
{
return false;
}
return await DisableAsync(userOrMemberKey, providerName);
}
public async Task<bool> ValidateAndSaveAsync(string providerName, Guid userOrMemberKey, string secret, string code)
{
try
{
var isValid = ValidateTwoFactorSetup(providerName, secret, code);
if (isValid == false)
{
return false;
}
var twoFactorLogin = new TwoFactorLogin()
{
Confirmed = true,
Secret = secret,
UserOrMemberKey = userOrMemberKey,
ProviderName = providerName
};
await SaveAsync(twoFactorLogin);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not log in with the provided one-time-password");
}
return false;
}
private async Task<IEnumerable<string>> GetEnabledProviderNamesAsync(Guid userOrMemberKey)
{
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Infrastructure.Telemetry.Interfaces
{
internal interface IDetailedTelemetryProvider
{
IEnumerable<UsageInformation> GetInformation();
}
}

View File

@@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Telemetry.Interfaces;
namespace Umbraco.Cms.Infrastructure.Telemetry.Providers
{
public class ContentTelemetryProvider : IDetailedTelemetryProvider
{
private readonly IContentService _contentService;
public ContentTelemetryProvider(IContentService contentService) => _contentService = contentService;
public IEnumerable<UsageInformation> GetInformation()
{
var rootNodes = _contentService.GetRootContent();
int nodes = rootNodes.Count();
yield return new UsageInformation(Constants.Telemetry.RootCount, nodes);
}
}
}

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Telemetry.Interfaces;
namespace Umbraco.Cms.Infrastructure.Telemetry.Providers
{
public class DomainTelemetryProvider : IDetailedTelemetryProvider
{
private readonly IDomainService _domainService;
public DomainTelemetryProvider(IDomainService domainService) => _domainService = domainService;
public IEnumerable<UsageInformation> GetInformation()
{
var domains = _domainService.GetAll(true).Count();
yield return new UsageInformation(Constants.Telemetry.DomainCount, domains);
}
}
}

View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Telemetry.Interfaces;
namespace Umbraco.Cms.Infrastructure.Telemetry.Providers
{
public class ExamineTelemetryProvider : IDetailedTelemetryProvider
{
private readonly IExamineIndexCountService _examineIndexCountService;
public ExamineTelemetryProvider(IExamineIndexCountService examineIndexCountService) => _examineIndexCountService = examineIndexCountService;
public IEnumerable<UsageInformation> GetInformation()
{
var indexes = _examineIndexCountService.GetCount();
yield return new UsageInformation(Constants.Telemetry.ExamineIndexCount, indexes);
}
}
}

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Telemetry.Interfaces;
namespace Umbraco.Cms.Infrastructure.Telemetry.Providers
{
public class LanguagesTelemetryProvider : IDetailedTelemetryProvider
{
private readonly ILocalizationService _localizationService;
public LanguagesTelemetryProvider(ILocalizationService localizationService)
{
_localizationService = localizationService;
}
public IEnumerable<UsageInformation> GetInformation()
{
int languages = _localizationService.GetAllLanguages().Count();
yield return new UsageInformation(Constants.Telemetry.LanguageCount, languages);
}
}
}

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Telemetry.Interfaces;
namespace Umbraco.Cms.Infrastructure.Telemetry.Providers
{
public class MacroTelemetryProvider : IDetailedTelemetryProvider
{
private readonly IMacroService _macroService;
public MacroTelemetryProvider(IMacroService macroService)
{
_macroService = macroService;
}
public IEnumerable<UsageInformation> GetInformation()
{
var macros = _macroService.GetAll().Count();
yield return new UsageInformation(Constants.Telemetry.MacroCount, macros);
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Collections.Generic;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Telemetry.Interfaces;
namespace Umbraco.Cms.Infrastructure.Telemetry.Providers
{
public class MediaTelemetryProvider : IDetailedTelemetryProvider
{
private readonly INodeCountService _nodeCountService;
public MediaTelemetryProvider(INodeCountService nodeCountService) => _nodeCountService = nodeCountService;
public IEnumerable<UsageInformation> GetInformation()
{
yield return new UsageInformation(Constants.Telemetry.MediaCount, _nodeCountService.GetMediaCount());
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Telemetry.Interfaces;
namespace Umbraco.Cms.Infrastructure.Telemetry.Providers
{
/// <inheritdoc />
public class NodeCountTelemetryProvider : IDetailedTelemetryProvider
{
private readonly INodeCountService _nodeCountService;
public NodeCountTelemetryProvider(INodeCountService nodeCountService) => _nodeCountService = nodeCountService;
public IEnumerable<UsageInformation> GetInformation()
{
yield return new UsageInformation(Constants.Telemetry.MemberCount, _nodeCountService.GetNodeCount(Constants.ObjectTypes.Member));
yield return new UsageInformation(Constants.Telemetry.TemplateCount, _nodeCountService.GetNodeCount(Constants.ObjectTypes.Template));
yield return new UsageInformation(Constants.Telemetry.ContentCount, _nodeCountService.GetNodeCount(Constants.ObjectTypes.Document));
yield return new UsageInformation(Constants.Telemetry.DocumentTypeCount, _nodeCountService.GetNodeCount(Constants.ObjectTypes.DocumentType));
}
}
}

View File

@@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Telemetry.Interfaces;
namespace Umbraco.Cms.Infrastructure.Telemetry.Providers
{
public class PropertyEditorTelemetryProvider : IDetailedTelemetryProvider
{
private readonly IContentTypeService _contentTypeService;
public PropertyEditorTelemetryProvider(IContentTypeService contentTypeService) => _contentTypeService = contentTypeService;
public IEnumerable<UsageInformation> GetInformation()
{
var contentTypes = _contentTypeService.GetAll();
var propertyTypes = new HashSet<string>();
foreach (IContentType contentType in contentTypes)
{
propertyTypes.UnionWith(contentType.PropertyTypes.Select(x => x.PropertyEditorAlias));
}
yield return new UsageInformation(Constants.Telemetry.Properties, propertyTypes);
}
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Telemetry.Interfaces;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Telemetry.Providers
{
internal class SystemInformationTelemetryProvider : IDetailedTelemetryProvider, IUserDataService
{
private readonly IUmbracoVersion _version;
private readonly ILocalizationService _localizationService;
private readonly IHostEnvironment _hostEnvironment;
private readonly Lazy<IUmbracoDatabase> _database;
private readonly GlobalSettings _globalSettings;
private readonly HostingSettings _hostingSettings;
private readonly ModelsBuilderSettings _modelsBuilderSettings;
public SystemInformationTelemetryProvider(
IUmbracoVersion version,
ILocalizationService localizationService,
IOptions<ModelsBuilderSettings> modelsBuilderSettings,
IOptions<HostingSettings> hostingSettings,
IOptions<GlobalSettings> globalSettings,
IHostEnvironment hostEnvironment,
Lazy<IUmbracoDatabase> database)
{
_version = version;
_localizationService = localizationService;
_hostEnvironment = hostEnvironment;
_database = database;
_globalSettings = globalSettings.Value;
_hostingSettings = hostingSettings.Value;
_modelsBuilderSettings = modelsBuilderSettings.Value;
}
private string CurrentWebServer => IsRunningInProcessIIS() ? "IIS" : "Kestrel";
private string ServerFramework => RuntimeInformation.FrameworkDescription;
private string ModelsBuilderMode => _modelsBuilderSettings.ModelsMode.ToString();
private string CurrentCulture => Thread.CurrentThread.CurrentCulture.ToString();
private bool IsDebug => _hostingSettings.Debug;
private bool UmbracoPathCustomized => _globalSettings.UmbracoPath != Constants.System.DefaultUmbracoPath;
private string AspEnvironment => _hostEnvironment.EnvironmentName;
private string ServerOs => RuntimeInformation.OSDescription;
private string DatabaseProvider => _database.Value.DatabaseType.GetProviderName();
public IEnumerable<UsageInformation> GetInformation() =>
new UsageInformation[]
{
new(Constants.Telemetry.ServerOs, ServerOs),
new(Constants.Telemetry.ServerFramework, ServerFramework),
new(Constants.Telemetry.OsLanguage, CurrentCulture),
new(Constants.Telemetry.WebServer, CurrentWebServer),
new(Constants.Telemetry.ModelsBuilderMode, ModelsBuilderMode),
new(Constants.Telemetry.CustomUmbracoPath, UmbracoPathCustomized),
new(Constants.Telemetry.AspEnvironment, AspEnvironment),
new(Constants.Telemetry.IsDebug, IsDebug),
new(Constants.Telemetry.DatabaseProvider, DatabaseProvider),
};
public IEnumerable<UserData> GetUserData() =>
new UserData[]
{
new("Server OS", ServerOs),
new("Server Framework", ServerFramework),
new("Default Language", _localizationService.GetDefaultLanguageIsoCode()),
new("Umbraco Version", _version.SemanticVersion.ToSemanticStringWithoutBuild()),
new("Current Culture", CurrentCulture),
new("Current UI Culture", Thread.CurrentThread.CurrentUICulture.ToString()),
new("Current Webserver", CurrentWebServer),
new("Models Builder Mode", ModelsBuilderMode),
new("Debug Mode", IsDebug.ToString()),
new("Database Provider", DatabaseProvider),
};
private bool IsRunningInProcessIIS()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return false;
}
string processName = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().ProcessName);
return (processName.Contains("w3wp") || processName.Contains("iisexpress"));
}
}
}

View File

@@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Telemetry.Interfaces;
namespace Umbraco.Cms.Infrastructure.Telemetry.Providers
{
public class UserTelemetryProvider : IDetailedTelemetryProvider
{
private readonly IUserService _userService;
public UserTelemetryProvider(IUserService userService)
{
_userService = userService;
}
public IEnumerable<UsageInformation> GetInformation()
{
_userService.GetAll(1, 1, out long total);
int userGroups = _userService.GetAllUserGroups().Count();
yield return new UsageInformation(Constants.Telemetry.UserCount, total);
yield return new UsageInformation(Constants.Telemetry.UserGroupCount, userGroups);
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Infrastructure.Telemetry.Interfaces;
namespace Umbraco.Cms.Core.Services
{
internal class UsageInformationService : IUsageInformationService
{
private readonly IMetricsConsentService _metricsConsentService;
private readonly IEnumerable<IDetailedTelemetryProvider> _providers;
public UsageInformationService(
IMetricsConsentService metricsConsentService,
IEnumerable<IDetailedTelemetryProvider> providers)
{
_metricsConsentService = metricsConsentService;
_providers = providers;
}
public IEnumerable<UsageInformation> GetDetailed()
{
if (_metricsConsentService.GetConsentLevel() != TelemetryLevel.Detailed)
{
return null;
}
var detailedUsageInformation = new List<UsageInformation>();
foreach (IDetailedTelemetryProvider provider in _providers)
{
detailedUsageInformation.AddRange(provider.GetInformation());
}
return detailedUsageInformation;
}
}
}

View File

@@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Infrastructure.PublishedCache.Snap;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.PublishedCache
{
@@ -443,10 +444,18 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
refreshedIdsA.Contains(x.ContentTypeId) &&
BuildKit(x, out _)))
{
// replacing the node: must preserve the parents
// replacing the node: must preserve the relations
var node = GetHead(_contentNodes, kit.Node.Id)?.Value;
if (node != null)
{
// Preserve children
kit.Node.FirstChildContentId = node.FirstChildContentId;
kit.Node.LastChildContentId = node.LastChildContentId;
// Also preserve siblings
kit.Node.NextSiblingContentId = node.NextSiblingContentId;
kit.Node.PreviousSiblingContentId = node.PreviousSiblingContentId;
}
SetValueLocked(_contentNodes, kit.Node.Id, kit.Node);
@@ -688,7 +697,36 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
[Obsolete("Use the overload that takes a 'kitGroupSize' parameter instead")]
public bool SetAllFastSortedLocked(IEnumerable<ContentNodeKit> kits, bool fromDb)
{
return SetAllFastSortedLocked(kits, 1, fromDb);
}
/// <summary>
/// Builds all kits on startup using a fast forward only cursor
/// </summary>
/// <param name="kits">
/// All kits sorted by Level + Parent Id + Sort order
/// </param>
/// <param name="kitGroupSize"></param>
/// <param name="fromDb">True if the data is coming from the database (not the local cache db)</param>
/// <returns></returns>
/// <remarks>
/// <para>
/// This requires that the collection is sorted by Level + ParentId + Sort Order.
/// This should be used only on a site startup as the first generations.
/// This CANNOT be used after startup since it bypasses all checks for Generations.
/// </para>
/// <para>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </para>
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public bool SetAllFastSortedLocked(IEnumerable<ContentNodeKit> kits, int kitGroupSize, bool fromDb)
{
EnsureLocked();
@@ -706,7 +744,19 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
ContentNode previousNode = null;
ContentNode parent = null;
foreach (var kit in kits)
// By using InGroupsOf() here we are forcing the database query result extraction to retrieve items in batches,
// reducing the possibility of a database timeout (ThreadAbortException) on large datasets.
// This in turn reduces the possibility that the NuCache file will remain locked, because an exception
// here results in the calling method to not release the lock.
// However the larger the batck size, the more content loaded into memory. So by default, this is set to 1 and can be increased by setting
// the configuration setting Umbraco:CMS:NuCache:KitPageSize to a higher value.
// If we are not loading from the database, then we can ignore this restriction.
foreach (var kitGroup in kits.InGroupsOf(!fromDb || kitGroupSize < 1 ? 1 : kitGroupSize))
{
foreach (var kit in kitGroup)
{
if (!BuildKit(kit, out var parentLink))
{
@@ -755,6 +805,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
_contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id;
}
}
return ok;
}
@@ -771,7 +822,27 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
[Obsolete("Use the overload that takes the 'kitGroupSize' and 'fromDb' parameters instead")]
public bool SetAllLocked(IEnumerable<ContentNodeKit> kits)
{
return SetAllLocked(kits, 1, false);
}
/// <summary>
/// Set all data for a collection of <see cref="ContentNodeKit"/>
/// </summary>
/// <param name="kits"></param>
/// <param name="kitGroupSize"></param>
/// <param name="fromDb">True if the data is coming from the database (not the local cache db)</param>
/// <returns></returns>
/// <remarks>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public bool SetAllLocked(IEnumerable<ContentNodeKit> kits, int kitGroupSize, bool fromDb)
{
EnsureLocked();
@@ -784,7 +855,18 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
//ClearLocked(_contentTypesById);
//ClearLocked(_contentTypesByAlias);
foreach (var kit in kits)
// By using InGroupsOf() here we are forcing the database query result extraction to retrieve items in batches,
// reducing the possibility of a database timeout (ThreadAbortException) on large datasets.
// This in turn reduces the possibility that the NuCache file will remain locked, because an exception
// here results in the calling method to not release the lock.
// However the larger the batck size, the more content loaded into memory. So by default, this is set to 1 and can be increased by setting
// the configuration setting Umbraco:CMS:NuCache:KitPageSize to a higher value.
// If we are not loading from the database, then we can ignore this restriction.
foreach (var kitGroup in kits.InGroupsOf(!fromDb || kitGroupSize < 1 ? 1 : kitGroupSize))
{
foreach (var kit in kitGroup)
{
if (!BuildKit(kit, out var parent))
{
@@ -804,6 +886,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
_contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id;
}
}
return ok;
}

View File

@@ -337,8 +337,19 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
_localContentDb?.Clear();
// IMPORTANT GetAllContentSources sorts kits by level + parentId + sortOrder
try
{
var kits = _publishedContentService.GetAllContentSources();
return onStartup ? _contentStore.SetAllFastSortedLocked(kits, true) : _contentStore.SetAllLocked(kits);
return onStartup ? _contentStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true) : _contentStore.SetAllLocked(kits, _config.KitBatchSize, true);
}
catch (ThreadAbortException tae)
{
// Caught a ThreadAbortException, most likely from a database timeout.
// If we don't catch it here, the whole local cache can remain locked causing widespread panic (see above comment).
_logger.LogWarning(tae, tae.Message);
}
return false;
}
}
@@ -385,8 +396,20 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
_logger.LogDebug("Loading media from database...");
// IMPORTANT GetAllMediaSources sorts kits by level + parentId + sortOrder
try
{
var kits = _publishedContentService.GetAllMediaSources();
return onStartup ? _mediaStore.SetAllFastSortedLocked(kits, true) : _mediaStore.SetAllLocked(kits);
return onStartup ? _mediaStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true) : _mediaStore.SetAllLocked(kits, _config.KitBatchSize, true);
}
catch (ThreadAbortException tae)
{
// Caught a ThreadAbortException, most likely from a database timeout.
// If we don't catch it here, the whole local cache can remain locked causing widespread panic (see above comment).
_logger.LogWarning(tae, tae.Message);
}
return false;
}
}
@@ -434,7 +457,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
return false;
}
return onStartup ? store.SetAllFastSortedLocked(kits, false) : store.SetAllLocked(kits);
return onStartup ? store.SetAllFastSortedLocked(kits, _config.KitBatchSize, false) : store.SetAllLocked(kits, _config.KitBatchSize, false);
}
private void LockAndLoadDomains()

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Web.BackOffice.Controllers
{
public class AnalyticsController : UmbracoAuthorizedJsonController
{
private readonly IMetricsConsentService _metricsConsentService;
public AnalyticsController(IMetricsConsentService metricsConsentService)
{
_metricsConsentService = metricsConsentService;
}
public TelemetryLevel GetConsentLevel()
{
return _metricsConsentService.GetConsentLevel();
}
[HttpPost]
public IActionResult SetConsentLevel([FromBody]TelemetryResource telemetryResource)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
_metricsConsentService.SetConsentLevel(telemetryResource.TelemetryLevel);
return Ok();
}
public IEnumerable<TelemetryLevel> GetAllLevels() => new[] { TelemetryLevel.Minimal, TelemetryLevel.Basic, TelemetryLevel.Detailed };
}
}

View File

@@ -74,6 +74,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
private readonly IBackOfficeExternalLoginProviders _externalAuthenticationOptions;
private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ITwoFactorLoginService _twoFactorLoginService;
private readonly WebRoutingSettings _webRoutingSettings;
// TODO: We need to review all _userManager.Raise calls since many/most should be on the usermanager or signinmanager, very few should be here
@@ -97,7 +98,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
IBackOfficeExternalLoginProviders externalAuthenticationOptions,
IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions,
IHttpContextAccessor httpContextAccessor,
IOptions<WebRoutingSettings> webRoutingSettings)
IOptions<WebRoutingSettings> webRoutingSettings,
ITwoFactorLoginService twoFactorLoginService)
{
_backofficeSecurityAccessor = backofficeSecurityAccessor;
_userManager = backOfficeUserManager;
@@ -118,6 +120,95 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
_backOfficeTwoFactorOptions = backOfficeTwoFactorOptions;
_httpContextAccessor = httpContextAccessor;
_webRoutingSettings = webRoutingSettings.Value;
_twoFactorLoginService = twoFactorLoginService;
}
[Obsolete("Use constructor that takes all params, scheduled for removal in V11")]
public AuthenticationController(
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IBackOfficeUserManager backOfficeUserManager,
IBackOfficeSignInManager signInManager,
IUserService userService,
ILocalizedTextService textService,
IUmbracoMapper umbracoMapper,
IOptions<GlobalSettings> globalSettings,
IOptions<SecuritySettings> securitySettings,
ILogger<AuthenticationController> logger,
IIpResolver ipResolver,
IOptions<UserPasswordConfigurationSettings> passwordConfiguration,
IEmailSender emailSender,
ISmsSender smsSender,
IHostingEnvironment hostingEnvironment,
LinkGenerator linkGenerator,
IBackOfficeExternalLoginProviders externalAuthenticationOptions,
IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions,
IHttpContextAccessor httpContextAccessor,
IOptions<WebRoutingSettings> webRoutingSettings)
: this(
backofficeSecurityAccessor,
backOfficeUserManager,
signInManager,
userService,
textService,
umbracoMapper,
globalSettings,
securitySettings,
logger,
ipResolver,
passwordConfiguration,
emailSender,
smsSender,
hostingEnvironment,
linkGenerator,
externalAuthenticationOptions,
backOfficeTwoFactorOptions,
StaticServiceProvider.Instance.GetRequiredService<IHttpContextAccessor>(),
StaticServiceProvider.Instance.GetRequiredService<IOptions<WebRoutingSettings>>(),
StaticServiceProvider.Instance.GetRequiredService<ITwoFactorLoginService>())
{
}
[Obsolete("Use constructor that takes all params, scheduled for removal in V11")]
public AuthenticationController(
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IBackOfficeUserManager backOfficeUserManager,
IBackOfficeSignInManager signInManager,
IUserService userService,
ILocalizedTextService textService,
IUmbracoMapper umbracoMapper,
IOptions<GlobalSettings> globalSettings,
IOptions<SecuritySettings> securitySettings,
ILogger<AuthenticationController> logger,
IIpResolver ipResolver,
IOptions<UserPasswordConfigurationSettings> passwordConfiguration,
IEmailSender emailSender,
ISmsSender smsSender,
IHostingEnvironment hostingEnvironment,
LinkGenerator linkGenerator,
IBackOfficeExternalLoginProviders externalAuthenticationOptions,
IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions)
: this(
backofficeSecurityAccessor,
backOfficeUserManager,
signInManager,
userService,
textService,
umbracoMapper,
globalSettings,
securitySettings,
logger,
ipResolver,
passwordConfiguration,
emailSender,
smsSender,
hostingEnvironment,
linkGenerator,
externalAuthenticationOptions,
backOfficeTwoFactorOptions,
StaticServiceProvider.Instance.GetRequiredService<IHttpContextAccessor>(),
StaticServiceProvider.Instance.GetRequiredService<IOptions<WebRoutingSettings>>())
{
}
/// <summary>
@@ -427,7 +518,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
return NotFound();
}
var userFactors = await _userManager.GetValidTwoFactorProvidersAsync(user);
var userFactors = await _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(user.Key);
return new ObjectResult(userFactors);
}

View File

@@ -241,6 +241,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
"authenticationApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<AuthenticationController>(
controller => controller.PostLogin(null))
},
{
"twoFactorLoginApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<TwoFactorLoginController>(
controller => controller.SetupInfo(null))
},
{
"currentUserApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<CurrentUserController>(
controller => controller.PostChangePassword(null))
@@ -387,7 +391,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
"trackedReferencesApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<TrackedReferencesController>(
controller => controller.GetPagedReferences(0, 1, 1, false))
}
},
{
"analyticsApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<AnalyticsController>(
controller => controller.GetConsentLevel())
},
}
},
{

View File

@@ -22,6 +22,7 @@ using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Infrastructure.Packaging;
using Umbraco.Cms.Web.BackOffice.Filters;
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
@@ -452,6 +453,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
/// </summary>
/// <param name="contentId"></param>
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes)]
[OutgoingEditorModelEvent]
public IEnumerable<ContentTypeBasic> GetAllowedChildren(int contentId)
{
if (contentId == Constants.System.RecycleBinContent)

View File

@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Mime;
using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
@@ -191,6 +193,39 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
return _umbracoMapper.Map<IDictionaryItem, DictionaryDisplay>(dictionary);
}
/// <summary>
/// Changes the structure for dictionary items
/// </summary>
/// <param name="move"></param>
/// <returns></returns>
public IActionResult PostMove(MoveOrCopy move)
{
var dictionaryItem = _localizationService.GetDictionaryItemById(move.Id);
if (dictionaryItem == null)
return ValidationProblem(_localizedTextService.Localize("dictionary", "itemDoesNotExists"));
var parent = _localizationService.GetDictionaryItemById(move.ParentId);
if (parent == null)
{
if (move.ParentId == Constants.System.Root)
dictionaryItem.ParentId = null;
else
return ValidationProblem(_localizedTextService.Localize("dictionary", "parentDoesNotExists"));
}
else
{
dictionaryItem.ParentId = parent.Key;
if (dictionaryItem.Key == parent.ParentId)
return ValidationProblem(_localizedTextService.Localize("moveOrCopy", "notAllowedByPath"));
}
_localizationService.Save(dictionaryItem);
var model = _umbracoMapper.Map<IDictionaryItem, DictionaryDisplay>(dictionaryItem);
return Content(model.Path, MediaTypeNames.Text.Plain, Encoding.UTF8);
}
/// <summary>
/// Saves a dictionary item
/// </summary>

View File

@@ -12,6 +12,7 @@ using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Web.BackOffice.Filters;
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
@@ -339,6 +340,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
/// </summary>
/// <param name="contentId"></param>
[Authorize(Policy = AuthorizationPolicies.TreeAccessMediaOrMediaTypes)]
[OutgoingEditorModelEvent]
public IEnumerable<ContentTypeBasic> GetAllowedChildren(int contentId)
{
if (contentId == Constants.System.RecycleBinContent)

View File

@@ -252,5 +252,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
return display;
}
/// <summary>
/// Copy the member type
/// </summary>
/// <param name="copy"></param>
/// <returns></returns>
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
public IActionResult PostCopy(MoveOrCopy copy)
{
return PerformCopy(
copy,
i => _memberTypeService.Get(i),
(type, i) => _memberTypeService.Copy(type, i));
}
}
}

View File

@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.BackOffice.Security;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Web.BackOffice.Controllers
{
public class TwoFactorLoginController : UmbracoAuthorizedJsonController
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly ILogger<TwoFactorLoginController> _logger;
private readonly ITwoFactorLoginService2 _twoFactorLoginService;
private readonly IBackOfficeSignInManager _backOfficeSignInManager;
private readonly IBackOfficeUserManager _backOfficeUserManager;
private readonly IOptionsSnapshot<TwoFactorLoginViewOptions> _twoFactorLoginViewOptions;
public TwoFactorLoginController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
ILogger<TwoFactorLoginController> logger,
ITwoFactorLoginService twoFactorLoginService,
IBackOfficeSignInManager backOfficeSignInManager,
IBackOfficeUserManager backOfficeUserManager,
IOptionsSnapshot<TwoFactorLoginViewOptions> twoFactorLoginViewOptions)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_logger = logger;
if (twoFactorLoginService is not ITwoFactorLoginService2 twoFactorLoginService2)
{
throw new ArgumentException("twoFactorLoginService needs to implement ITwoFactorLoginService2 until the interfaces are merged", nameof(twoFactorLoginService));
}
_twoFactorLoginService = twoFactorLoginService2;
_backOfficeSignInManager = backOfficeSignInManager;
_backOfficeUserManager = backOfficeUserManager;
_twoFactorLoginViewOptions = twoFactorLoginViewOptions;
}
/// <summary>
/// Used to retrieve the 2FA providers for code submission
/// </summary>
/// <returns></returns>
[HttpGet]
[AllowAnonymous]
public async Task<ActionResult<IEnumerable<string>>> GetEnabled2FAProvidersForCurrentUser()
{
var user = await _backOfficeSignInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
_logger.LogWarning("No verified user found, returning 404");
return NotFound();
}
var userFactors = await _backOfficeUserManager.GetValidTwoFactorProvidersAsync(user);
return new ObjectResult(userFactors);
}
[HttpGet]
public async Task<ActionResult<IEnumerable<UserTwoFactorProviderModel>>> Get2FAProvidersForUser(int userId)
{
var user = await _backOfficeUserManager.FindByIdAsync(userId.ToString());
var enabledProviderNameHashSet = new HashSet<string>(await _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(user.Key));
var providerNames = await _backOfficeUserManager.GetValidTwoFactorProvidersAsync(user);
return providerNames.Select(providerName =>
new UserTwoFactorProviderModel(providerName, enabledProviderNameHashSet.Contains(providerName))).ToArray();
}
[HttpGet]
public async Task<ActionResult<object>> SetupInfo(string providerName)
{
var user = _backOfficeSecurityAccessor?.BackOfficeSecurity.CurrentUser;
var setupInfo = await _twoFactorLoginService.GetSetupInfoAsync(user.Key, providerName);
return setupInfo;
}
[HttpPost]
public async Task<ActionResult<bool>> ValidateAndSave(string providerName, string secret, string code)
{
var user = _backOfficeSecurityAccessor?.BackOfficeSecurity.CurrentUser;
return await _twoFactorLoginService.ValidateAndSaveAsync(providerName, user.Key, secret, code);
}
[HttpPost]
[Authorize(Policy = AuthorizationPolicies.SectionAccessUsers)]
public async Task<ActionResult<bool>> Disable(string providerName, Guid userKey)
{
return await _twoFactorLoginService.DisableAsync(userKey, providerName);
}
[HttpPost]
public async Task<ActionResult<bool>> DisableWithCode(string providerName, string code)
{
Guid key = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Key;
return await _twoFactorLoginService.DisableWithCodeAsync(providerName, key, code);
}
[HttpGet]
public ActionResult<string> ViewPathForProviderName(string providerName)
{
var options = _twoFactorLoginViewOptions.Get(providerName);
return options.SetupViewPath;
}
}
}

View File

@@ -45,6 +45,11 @@ namespace Umbraco.Extensions
{
o.Cookie.Name = Constants.Security.BackOfficeTwoFactorAuthenticationType;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
})
.AddCookie(Constants.Security.BackOfficeTwoFactorRememberMeAuthenticationType, o =>
{
o.Cookie.Name = Constants.Security.BackOfficeTwoFactorRememberMeAuthenticationType;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
});
builder.Services.ConfigureOptions<ConfigureBackOfficeCookieOptions>();

View File

@@ -42,7 +42,8 @@ namespace Umbraco.Extensions
factory.GetRequiredService<IOptionsSnapshot<GlobalSettings>>(),
factory.GetRequiredService<IUmbracoMapper>(),
factory.GetRequiredService<BackOfficeErrorDescriber>(),
factory.GetRequiredService<AppCaches>()
factory.GetRequiredService<AppCaches>(),
factory.GetRequiredService<ITwoFactorLoginService>()
))
.AddUserManager<IBackOfficeUserManager, BackOfficeUserManager>()
.AddSignInManager<IBackOfficeSignInManager, BackOfficeSignInManager>()
@@ -64,7 +65,7 @@ namespace Umbraco.Extensions
services.TryAddScoped<IIpResolver, AspNetCoreIpResolver>();
services.TryAddSingleton<IBackOfficeExternalLoginProviders, BackOfficeExternalLoginProviders>();
services.TryAddSingleton<IBackOfficeTwoFactorOptions, NoopBackOfficeTwoFactorOptions>();
services.TryAddSingleton<IBackOfficeTwoFactorOptions, DefaultBackOfficeTwoFactorOptions>();
return new BackOfficeIdentityBuilder(services);
}

View File

@@ -84,6 +84,12 @@ namespace Umbraco.Cms.Web.BackOffice.Filters
case IEnumerable<Tab<IDashboardSlim>> dashboards:
_eventAggregator.Publish(new SendingDashboardsNotification(dashboards, umbracoContext));
break;
case IEnumerable<ContentTypeBasic> allowedChildren:
// Changing the Enumerable will generate a new instance, so we need to update the context result with the new content
var notification = new SendingAllowedChildrenNotification(allowedChildren, umbracoContext);
_eventAggregator.Publish(notification);
context.Result = new ObjectResult(notification.Children);
break;
}
}
}

View File

@@ -6,10 +6,14 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Cms.Web.Common.Security;
using Umbraco.Extensions;
@@ -24,6 +28,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
{
private readonly BackOfficeUserManager _userManager;
private readonly IBackOfficeExternalLoginProviders _externalLogins;
private readonly IEventAggregator _eventAggregator;
private readonly GlobalSettings _globalSettings;
protected override string AuthenticationType => Constants.Security.BackOfficeAuthenticationType;
@@ -43,14 +48,32 @@ namespace Umbraco.Cms.Web.BackOffice.Security
IOptions<GlobalSettings> globalSettings,
ILogger<SignInManager<BackOfficeIdentityUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<BackOfficeIdentityUser> confirmation)
IUserConfirmation<BackOfficeIdentityUser> confirmation,
IEventAggregator eventAggregator)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
_userManager = userManager;
_externalLogins = externalLogins;
_eventAggregator = eventAggregator;
_globalSettings = globalSettings.Value;
}
[Obsolete("Use ctor with all params")]
public BackOfficeSignInManager(
BackOfficeUserManager userManager,
IHttpContextAccessor contextAccessor,
IBackOfficeExternalLoginProviders externalLogins,
IUserClaimsPrincipalFactory<BackOfficeIdentityUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
IOptions<GlobalSettings> globalSettings,
ILogger<SignInManager<BackOfficeIdentityUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<BackOfficeIdentityUser> confirmation)
: this(userManager, contextAccessor, externalLogins, claimsFactory, optionsAccessor, globalSettings, logger, schemes, confirmation, StaticServiceProvider.Instance.GetRequiredService<IEventAggregator>())
{
}
/// <summary>
/// Custom ExternalLoginSignInAsync overload for handling external sign in with auto-linking
/// </summary>
@@ -284,6 +307,31 @@ namespace Umbraco.Cms.Web.BackOffice.Security
}
}
protected override async Task<SignInResult> SignInOrTwoFactorAsync(BackOfficeIdentityUser user, bool isPersistent,
string loginProvider = null, bool bypassTwoFactor = false)
{
var result = await base.SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor);
if (result.RequiresTwoFactor)
{
NotifyRequiresTwoFactor(user);
}
return result;
}
protected void NotifyRequiresTwoFactor(BackOfficeIdentityUser user) => Notify(user,
(currentUser) => new UserTwoFactorRequestedNotification(currentUser.Key)
);
private T Notify<T>(BackOfficeIdentityUser currentUser, Func<BackOfficeIdentityUser, T> createNotification) where T : INotification
{
var notification = createNotification(currentUser);
_eventAggregator.Publish(notification);
return notification;
}
private void LogFailedExternalLogin(ExternalLoginInfo loginInfo, BackOfficeIdentityUser user) =>
Logger.LogWarning("The AutoLinkOptions of the external authentication provider '{LoginProvider}' have refused the login based on the OnExternalLogin method. Affected user id: '{UserId}'", loginInfo.LoginProvider, user.Id);
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Umbraco.Cms.Web.BackOffice.Security
{
public class DefaultBackOfficeTwoFactorOptions : IBackOfficeTwoFactorOptions
{
public string GetTwoFactorView(string username) => "views\\common\\login-2fa.html";
}
}

View File

@@ -1,5 +1,8 @@
namespace Umbraco.Cms.Web.BackOffice.Security
using System;
namespace Umbraco.Cms.Web.BackOffice.Security
{
[Obsolete("Not used anymore")]
public class NoopBackOfficeTwoFactorOptions : IBackOfficeTwoFactorOptions
{
public string GetTwoFactorView(string username) => null;

View File

@@ -0,0 +1,13 @@
namespace Umbraco.Cms.Web.BackOffice.Security
{
/// <summary>
/// Options used as named options for 2fa providers
/// </summary>
public class TwoFactorLoginViewOptions
{
/// <summary>
/// Gets or sets the path of the view to show when setting up this 2fa provider
/// </summary>
public string SetupViewPath { get; set; }
}
}

View File

@@ -126,7 +126,11 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
if (id != Constants.System.RootString)
{
menu.Items.Add<ActionDelete>(LocalizedTextService, true, opensDialog: true);
menu.Items.Add<ActionMove>(LocalizedTextService, true, opensDialog: true);
}
menu.Items.Add(new RefreshNode(LocalizedTextService, true));

View File

@@ -1,14 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Trees;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.Cms.Web.BackOffice.Trees
@@ -21,14 +24,34 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
{
private readonly IMemberGroupService _memberGroupService;
[ActivatorUtilitiesConstructor]
public MemberGroupTreeController(
ILocalizedTextService localizedTextService,
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
IMenuItemCollectionFactory menuItemCollectionFactory,
IMemberGroupService memberGroupService,
IEventAggregator eventAggregator,
IMemberTypeService memberTypeService)
: base(localizedTextService, umbracoApiControllerTypeCollection, menuItemCollectionFactory, eventAggregator, memberTypeService)
=> _memberGroupService = memberGroupService;
[Obsolete("Use ctor with all params")]
public MemberGroupTreeController(
ILocalizedTextService localizedTextService,
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
IMenuItemCollectionFactory menuItemCollectionFactory,
IMemberGroupService memberGroupService,
IEventAggregator eventAggregator)
: base(localizedTextService, umbracoApiControllerTypeCollection, menuItemCollectionFactory, eventAggregator)
=> _memberGroupService = memberGroupService;
: this(localizedTextService,
umbracoApiControllerTypeCollection,
menuItemCollectionFactory,
memberGroupService,
eventAggregator,
StaticServiceProvider.Instance.GetRequiredService<IMemberTypeService>())
{
}
protected override IEnumerable<TreeNode> GetTreeNodesFromService(string id, FormCollection queryStrings)
=> _memberGroupService.GetAll()

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Events;
@@ -8,6 +10,8 @@ using Umbraco.Cms.Core.Models.Trees;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Trees;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.Cms.Web.BackOffice.Trees
@@ -16,21 +20,46 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
[CoreTree]
public abstract class MemberTypeAndGroupTreeControllerBase : TreeController
{
private readonly IMemberTypeService _memberTypeService;
public IMenuItemCollectionFactory MenuItemCollectionFactory { get; }
protected MemberTypeAndGroupTreeControllerBase(
ILocalizedTextService localizedTextService,
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
IMenuItemCollectionFactory menuItemCollectionFactory,
IEventAggregator eventAggregator)
IEventAggregator eventAggregator,
IMemberTypeService memberTypeService)
: base(localizedTextService, umbracoApiControllerTypeCollection, eventAggregator)
{
MenuItemCollectionFactory = menuItemCollectionFactory;
_memberTypeService = memberTypeService;
}
[Obsolete("Use ctor injecting IMemberTypeService")]
protected MemberTypeAndGroupTreeControllerBase(
ILocalizedTextService localizedTextService,
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
IMenuItemCollectionFactory menuItemCollectionFactory,
IEventAggregator eventAggregator)
: this(
localizedTextService,
umbracoApiControllerTypeCollection,
menuItemCollectionFactory,
eventAggregator,
StaticServiceProvider.Instance.GetRequiredService<IMemberTypeService>())
{
}
protected override ActionResult<TreeNodeCollection> GetTreeNodes(string id, FormCollection queryStrings)
{
var nodes = new TreeNodeCollection();
// if the request is for folders only then just return
if (queryStrings["foldersonly"].ToString().IsNullOrWhiteSpace() == false && queryStrings["foldersonly"].ToString() == "1")
return nodes;
nodes.AddRange(GetTreeNodesFromService(id, queryStrings));
return nodes;
}
@@ -48,6 +77,12 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
}
else
{
var memberType = _memberTypeService.Get(int.Parse(id));
if (memberType != null)
{
menu.Items.Add<ActionCopy>(LocalizedTextService, opensDialog: true);
}
// delete member type/group
menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true);
}

View File

@@ -32,13 +32,12 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
UmbracoTreeSearcher treeSearcher,
IMemberTypeService memberTypeService,
IEventAggregator eventAggregator)
: base(localizedTextService, umbracoApiControllerTypeCollection, menuItemCollectionFactory, eventAggregator)
: base(localizedTextService, umbracoApiControllerTypeCollection, menuItemCollectionFactory, eventAggregator, memberTypeService)
{
_treeSearcher = treeSearcher;
_memberTypeService = memberTypeService;
}
protected override ActionResult<TreeNode> CreateRootNode(FormCollection queryStrings)
{
var rootResult = base.CreateRootNode(queryStrings);

View File

@@ -92,8 +92,6 @@ namespace Umbraco.Cms.Web.Common.ApplicationBuilder
{
UseUmbracoCoreMiddleware();
AppBuilder.UseStatusCodePages();
// Important we handle image manipulations before the static files, otherwise the querystring is just ignored.
AppBuilder.UseImageSharp();

View File

@@ -159,6 +159,7 @@ namespace Umbraco.Extensions
));
builder.AddCoreInitialServices();
builder.AddTelemetryProviders();
// aspnet app lifetime mgmt
builder.Services.AddUnique<IUmbracoApplicationLifetime, AspNetCoreUmbracoApplicationLifetime>();

View File

@@ -799,9 +799,13 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder
if (!_disposedValue)
{
if (disposing)
{
if (_watcher != null)
{
_watcher.EnableRaisingEvents = false;
_watcher.Dispose();
}
_locker.Dispose();
}

View File

@@ -74,6 +74,7 @@ namespace Umbraco.Cms.Web.Common.Security
return await base.VerifyPasswordAsync(store, user, password);
}
/// <summary>
/// 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
/// </summary>

View File

@@ -45,9 +45,6 @@ namespace Umbraco.Cms.Web.Common.Security
_httpContextAccessor = httpContextAccessor;
}
/// <inheritdoc />
public override bool SupportsUserTwoFactor => true;
/// <inheritdoc />
public async Task<bool> IsMemberAuthorizedAsync(IEnumerable<string> allowTypes = null, IEnumerable<string> allowGroups = null, IEnumerable<int> allowMembers = null)
{

View File

@@ -1,7 +1,7 @@
(function () {
"use strict";
function AppHeaderDirective(eventsService, appState, userService, focusService, overlayService, $timeout) {
function AppHeaderDirective(eventsService, appState, userService, focusService, $timeout, editorService) {
function link(scope, element) {
@@ -72,17 +72,15 @@
};
scope.avatarClick = function () {
const dialog = {
view: "user",
position: "right",
name: "overlay-user",
const userEditor = {
size: "small",
view: "views/common/infiniteeditors/user/user.html",
close: function() {
overlayService.close();
editorService.close();
}
};
overlayService.open(dialog);
editorService.open(userEditor);
};
scope.logoModal = {

View File

@@ -443,12 +443,16 @@
vm.twoFactor.submitCallback = function submitCallback() {
vm.onLogin();
}
vm.twoFactor.cancelCallback = function cancelCallback() {
vm.showLogin();
}
vm.twoFactor.view = viewPath;
vm.view = "2fa-login";
SetTitle();
}
function resetInputValidation() {
vm.loginStates.submitButton = "init";
vm.confirmPassword = "";
vm.password = "";
vm.login = "";

Some files were not shown because too many files have changed in this diff Show More