diff --git a/build/Modules/Umbraco.Build/Build-UmbracoDocs.ps1 b/build/Modules/Umbraco.Build/Build-UmbracoDocs.ps1
index dc9ad843c7..c47147ac9f 100644
--- a/build/Modules/Umbraco.Build/Build-UmbracoDocs.ps1
+++ b/build/Modules/Umbraco.Build/Build-UmbracoDocs.ps1
@@ -15,13 +15,6 @@ function Build-UmbracoDocs
################ Do the UI docs
- # create Belle build folder, so that we don't cause a Belle rebuild
- $belleBuildDir = "$src\Umbraco.Web.UI.Client\build"
- if (-not (Test-Path $belleBuildDir))
- {
- mkdir $belleBuildDir > $null
- }
-
Write-Host "Build UI documentation"
# get a temp clean node env (will restore)
@@ -29,12 +22,22 @@ function Build-UmbracoDocs
push-location "$src\Umbraco.Web.UI.Client"
write "" > $tmp\belle-docs.log
- &npm cache clean --quiet >> $tmp\belle-docs.log 2>&1
- &npm install --quiet >> $tmp\belle-docs.log 2>&1
- &npm install -g grunt-cli --quiet >> $tmp\belle-docs.log 2>&1
- #&npm install -g bower --quiet >> $tmp\belle.log 2>&1
- #&grunt build --buildversion=$version.Release >> $tmp\belle.log 2>&1
- &grunt --gruntfile "$src/Umbraco.Web.UI.Client/gruntfile.js" docs >> $tmp\belle-docs.log 2>&1
+ write "node version is:" > $tmp\belle.log
+ &node -v >> $tmp\belle.log 2>&1
+ write "npm version is:" >> $tmp\belle.log 2>&1
+ &npm -v >> $tmp\belle.log 2>&1
+ write "clean npm cache" >> $tmp\belle.log 2>&1
+ &npm cache clean >> $tmp\belle.log 2>&1
+ write "npm install" >> $tmp\belle.log 2>&1
+ &npm install >> $tmp\belle.log 2>&1
+ write "installing bower" >> $tmp\belle.log 2>&1
+ &npm install -g bower >> $tmp\belle.log 2>&1
+ write "installing gulp" >> $tmp\belle.log 2>&1
+ &npm install -g gulp >> $tmp\belle.log 2>&1
+ write "installing gulp-cli" >> $tmp\belle.log 2>&1
+ &npm install -g gulp-cli --quiet >> $tmp\belle.log 2>&1
+ write "gulp docs" >> $tmp\belle.log 2>&1
+ &gulp docs >> $tmp\belle.log 2>&1
pop-location
# fixme - should we filter the log to find errors?
diff --git a/build/Modules/Umbraco.Build/Get-UmbracoBuildEnv.ps1 b/build/Modules/Umbraco.Build/Get-UmbracoBuildEnv.ps1
index b2cd63306d..d1aaf7582f 100644
--- a/build/Modules/Umbraco.Build/Get-UmbracoBuildEnv.ps1
+++ b/build/Modules/Umbraco.Build/Get-UmbracoBuildEnv.ps1
@@ -143,7 +143,7 @@ function Get-UmbracoBuildEnv
$vsMajor = [int]::Parse($vsVerParts[0])
$vsMinor = [int]::Parse($vsVerParts[1])
if ($vsMajor -eq 15) {
- $msBuild = "$vsPath\MSBuild\$vsMajor.$vsMinor\Bin"
+ $msBuild = "$vsPath\MSBuild\$vsMajor.0\Bin"
}
elseif ($vsMajor -eq 14) {
$msBuild = "c:\Program Files (x86)\MSBuild\$vsMajor\Bin"
diff --git a/build/Modules/Umbraco.Build/Umbraco.Build.psm1 b/build/Modules/Umbraco.Build/Umbraco.Build.psm1
index b7ca0439f7..c7bb3b824d 100644
--- a/build/Modules/Umbraco.Build/Umbraco.Build.psm1
+++ b/build/Modules/Umbraco.Build/Umbraco.Build.psm1
@@ -46,7 +46,6 @@ function Prepare-Build
# clear
Write-Host "Clear folders and files"
- Remove-Directory "$src\Umbraco.Web.UI.Client\build"
Remove-Directory "$src\Umbraco.Web.UI.Client\bower_components"
if (-not $keep)
@@ -131,11 +130,22 @@ function Compile-Belle
push-location "$($uenv.SolutionRoot)\src\Umbraco.Web.UI.Client"
write "" > $tmp\belle.log
- &npm cache clean --quiet >> $tmp\belle.log 2>&1
- &npm install --quiet >> $tmp\belle.log 2>&1
- &npm install -g grunt-cli --quiet >> $tmp\belle.log 2>&1
- &npm install -g bower --quiet >> $tmp\belle.log 2>&1
- &grunt build --buildversion=$version.Release >> $tmp\belle.log 2>&1
+ write "node version is:" > $tmp\belle.log
+ &node -v >> $tmp\belle.log 2>&1
+ write "npm version is:" >> $tmp\belle.log 2>&1
+ &npm -v >> $tmp\belle.log 2>&1
+ write "clean npm cache" >> $tmp\belle.log 2>&1
+ &npm cache clean >> $tmp\belle.log 2>&1
+ write "npm install" >> $tmp\belle.log 2>&1
+ &npm install >> $tmp\belle.log 2>&1
+ write "install bower" >> $tmp\belle.log 2>&1
+ &npm install -g bower >> $tmp\belle.log 2>&1
+ write "install gulp" >> $tmp\belle.log 2>&1
+ &npm install -g gulp >> $tmp\belle.log 2>&1
+ write "install gulp-cli" >> $tmp\belle.log 2>&1
+ &npm install -g gulp-cli --quiet >> $tmp\belle.log 2>&1
+ write "gulp build for version $version" >> $tmp\belle.log 2>&1
+ &gulp build --buildversion=$version.Release >> $tmp\belle.log 2>&1
pop-location
# fixme - should we filter the log to find errors?
@@ -237,12 +247,19 @@ function Prepare-Tests
# data
Write-Host "Copy data files"
- mkdir "$tmp\tests\Packaging" > $null
+ if (-not (Test-Path -Path "$tmp\tests\Packaging" ))
+ {
+ Write-Host "Create packaging directory"
+ mkdir "$tmp\tests\Packaging" > $null
+ }
Copy-Files "$src\Umbraco.Tests\Packaging\Packages" "*" "$tmp\tests\Packaging\Packages"
# required for package install tests
- Write-Host "Create bin directory"
- mkdir "$tmp\tests\bin" > $null
+ if (-not (Test-Path -Path "$tmp\tests\bin" ))
+ {
+ Write-Host "Create bin directory"
+ mkdir "$tmp\tests\bin" > $null
+ }
}
#
@@ -363,8 +380,11 @@ function Prepare-Packages
# copy Belle
Write-Host "Copy Belle"
- Copy-Files "$src\Umbraco.Web.UI.Client\build\belle" "*" "$tmp\WebApp\umbraco" `
- { -not ($_.RelativeName -eq "index.html") }
+ Copy-Files "$src\Umbraco.Web.UI\umbraco\assets" "*" "$tmp\WebApp\umbraco\assets"
+ Copy-Files "$src\Umbraco.Web.UI\umbraco\js" "*" "$tmp\WebApp\umbraco\js"
+ Copy-Files "$src\Umbraco.Web.UI\umbraco\lib" "*" "$tmp\WebApp\umbraco\lib"
+ Copy-Files "$src\Umbraco.Web.UI\umbraco\views" "*" "$tmp\WebApp\umbraco\views"
+ Copy-Files "$src\Umbraco.Web.UI\umbraco\preview" "*" "$tmp\WebApp\umbraco\preview"
# prepare WebPI
Write-Host "Prepare WebPI"
@@ -399,11 +419,11 @@ function Package-Zip
Write-Host "Zip cms"
&$uenv.Zip a -r "$out\UmbracoCms.$($version.Semver).zip" `
"$tmp\WebApp\*" `
- "-x!dotless.Core.*" "-x!Content_Types.xml" "-x!Umbraco.Compat7.*" `
+ "-x!dotless.Core.*" "-x!Content_Types.xml" "-x!*.pdb" "-x!Umbraco.Compat7.*" `
> $null
Write-Host "Zip WebPI"
- &$uenv.Zip a -r "$out\UmbracoCms.WebPI.$($version.Semver).zip" `
+ &$uenv.Zip a -r "$out\UmbracoCms.WebPI.$($version.Semver).zip" "-x!*.pdb" `
"$tmp\WebPi\*" `
"-x!dotless.Core.*" "-x!Umbraco.Compat7.*" `
> $null
@@ -519,7 +539,7 @@ function Build-Umbraco
if ($target -eq "pre-build")
{
Prepare-Build $uenv
- Compile-Belle $uenv $version
+ #Compile-Belle $uenv $version
# set environment variables
$env:UMBRACO_VERSION=$version.Semver.ToString()
diff --git a/build/NuSpecs/tools/Dashboard.config.install.xdt b/build/NuSpecs/tools/Dashboard.config.install.xdt
index 8368870186..036beeba29 100644
--- a/build/NuSpecs/tools/Dashboard.config.install.xdt
+++ b/build/NuSpecs/tools/Dashboard.config.install.xdt
@@ -41,16 +41,6 @@
views/dashboard/developer/examinemanagement.html
-
-
- views/dashboard/developer/healthcheck.html
-
-
-
-
- views/dashboard/developer/redirecturls.html
-
-
+
+
+
+ content
+
+
+
+ views/dashboard/developer/redirecturls.html
+
+
+
+
+
+
+ developer
+
+
+
+ views/dashboard/developer/healthcheck.html
+
+
+
\ No newline at end of file
diff --git a/build/NuSpecs/tools/ReadmeUpgrade.txt b/build/NuSpecs/tools/ReadmeUpgrade.txt
index e0d660a795..e85b22a902 100644
--- a/build/NuSpecs/tools/ReadmeUpgrade.txt
+++ b/build/NuSpecs/tools/ReadmeUpgrade.txt
@@ -8,8 +8,16 @@
----------------------------------------------------
-Don't forget to build!
+*** IMPORTANT NOTICE FOR 7.7 UPGRADES ***
+Be sure to read the version specific upgrade information before proceeding:
+https://our.umbraco.org/documentation/Getting-Started/Setup/Upgrading/version-specific#version-7-7-0
+
+Depending on the version you are upgrading from, you may need to make some changes to your web.config
+and you will need to be aware of the breaking changes listed there to see if these affect your installation.
+
+
+Don't forget to build!
We've done our best to transform your configuration files but in case something is not quite right: remember we
backed up your files in App_Data\NuGetBackup so you can find the original files before they were transformed.
diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt
index 4851cd75b3..7af4a38c14 100644
--- a/build/NuSpecs/tools/Web.config.install.xdt
+++ b/build/NuSpecs/tools/Web.config.install.xdt
@@ -24,6 +24,7 @@
+
@@ -332,6 +333,8 @@
+
+
@@ -352,7 +355,7 @@
-
+
@@ -386,6 +389,10 @@
+
+
+
+
diff --git a/build/NuSpecs/tools/cache.config.install.xdt b/build/NuSpecs/tools/cache.config.install.xdt
new file mode 100644
index 0000000000..746e3c3298
--- /dev/null
+++ b/build/NuSpecs/tools/cache.config.install.xdt
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build/NuSpecs/tools/install.core.ps1 b/build/NuSpecs/tools/install.core.ps1
index 32dfecbb00..e2230e0c32 100644
--- a/build/NuSpecs/tools/install.core.ps1
+++ b/build/NuSpecs/tools/install.core.ps1
@@ -42,6 +42,7 @@ if ($project) {
if(Test-Path $umbracoBinFolder\SQLCE4Umbraco.dll) { Remove-Item $umbracoBinFolder\SQLCE4Umbraco.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\TidyNet.dll) { Remove-Item $umbracoBinFolder\TidyNet.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\umbraco.dll) { Remove-Item $umbracoBinFolder\umbraco.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.Core.dll) { Remove-Item $umbracoBinFolder\Umbraco.Core.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\umbraco.DataLayer.dll) { Remove-Item $umbracoBinFolder\umbraco.DataLayer.dll -Force -Confirm:$false }
@@ -70,6 +71,7 @@ if ($project) {
if(Test-Path $umbracoBinFolder\ImageProcessor.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\ImageProcessor.Web.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.Web.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Lucene.Net.dll) { Remove-Item $umbracoBinFolder\Lucene.Net.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll) { Remove-Item $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll -Force -Confirm:$false }
diff --git a/build/NuSpecs/tools/processing.config.install.xdt b/build/NuSpecs/tools/processing.config.install.xdt
new file mode 100644
index 0000000000..0bef321533
--- /dev/null
+++ b/build/NuSpecs/tools/processing.config.install.xdt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/build/NuSpecs/tools/trees.config.install.xdt b/build/NuSpecs/tools/trees.config.install.xdt
index 4067318be7..65aa9c2b53 100644
--- a/build/NuSpecs/tools/trees.config.install.xdt
+++ b/build/NuSpecs/tools/trees.config.install.xdt
@@ -34,14 +34,19 @@
xdt:Transform="SetAttributes()" />
-
+ xdt:Transform="SetAttributes()" />
+
+
+
+
-
-
+
+
+
+
diff --git a/build/build.md b/build/build.md
deleted file mode 100644
index bc8ab500b1..0000000000
--- a/build/build.md
+++ /dev/null
@@ -1,149 +0,0 @@
-Umbraco Cms Build
---
-----
-
-# Quick!
-
-To build Umbraco, fire PowerShell and move to Umbraco's repository root (the directory that contains `src`, `build`, `README.md`...). There, trigger the build with the following command:
-
- build\build.ps1
-
-By default, this builds the current version. It is possible to specify a different version as a parameter to the build script:
-
- build\build.ps1 7.6.44
-
-Valid version strings are defined in the `Set-UmbracoVersion` documentation below.
-
-## Notes
-
-Git might have issues dealing with long file paths during build. You may want/need to enable `core.longpaths` support (see [this page](https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path) for details).
-
-# Build
-
-The Umbraco Build solution relies on a PowerShell module. The module needs to be imported into PowerShell. From within Umbraco's repository root:
-
- build\build.ps1 -ModuleOnly
-
-Or the abbreviated form:
-
- build\build.ps1 -mo
-
-Once the module has been imported, a set of commands are added to PowerShell.
-
-## Get-UmbracoBuildEnv
-
-Gets the Umbraco build environment ie NuGet, Semver, Visual Studio, etc. Downloads things that can be downloaded such as NuGet. Examples:
-
- $uenv = Get-UmbracoBuildEnv
- Write-Host $uenv.SolutionRoot
- &$uenv.NuGet help
-
-The object exposes the following properties:
-
-* `SolutionRoot`: the absolute path to the solution root
-* `VisualStudio`: a Visual Studio object (see below)
-* `NuGet`: the absolute path to the NuGet executable
-* `Zip`: the absolute path to the 7Zip executable
-* `VsWhere`: the absolute path to the VsWhere executable
-* `NodePath`: the absolute path to the Node install
-* `NpmPath`: the absolute path to the Npm install
-
-The Visual Studio object is `null` when Visual Studio has not been detected (eg on VSTS). When not null, the object exposes the following properties:
-
-* `Path`: Visual Studio installation path (eg some place under `Program Files`)
-* `Major`: Visual Studio major version (eg `15` for VS 2017)
-* `Minor`: Visual Studio minor version
-* `MsBUild`: the absolute path to the MsBuild executable
-
-## Get-UmbracoVersion
-
-Gets an object representing the current Umbraco version. Example:
-
- $v = Get-UmbracoVersion
- Write-Host $v.Semver
-
-The object exposes the following properties:
-
-* `Semver`: the semver object representing the version
-* `Release`: the main part of the version (eg `7.6.33`)
-* `Comment`: the pre release part of the version (eg `alpha02`)
-* `Build`: the build number part of the version (eg `1234`)
-
-## Set-UmbracoVersion
-
-Modifies Umbraco files with the new version.
-
->This entirely replaces the legacy `UmbracoVersion.txt` file.
-
-The version must be a valid semver version. It can include a *pre release* part (eg `alpha02`) and/or a *build number* (eg `1234`). Examples:
-
- Set-UmbracoVersion 7.6.33
- Set-UmbracoVersion 7.6.33-alpha02
- Set-UmbracoVersion 7.6.33+1234
- Set-UmbracoVersion 7.6.33-beta05+5678
-
-Note that `Set-UmbracoVersion` enforces a slightly more restrictive naming scheme than what semver would tolerate. The pre release part can only be composed of a-z and 0-9, therefore `alpha033` is considered valid but not `alpha.033` nor `alpha033-preview` nor `RC2` (would need to be lowercased `rc2`).
-
->It is considered best to add trailing zeroes to pre releases, else NuGet gets the order of versions wrong. So if you plan to have more than 10, but no more that 100 alpha versions, number the versions `alpha00`, `alpha01`, etc.
-
-## Build-Umbraco
-
-Builds Umbraco. Temporary files are generated in `build.tmp` while the actual artifacts (zip files, NuGet packages...) are produced in `build.out`. Example:
-
- Build-Umbraco
-
-Some log files, such as MsBuild logs, are produced in `build.tmp` too. The `build` directory should remain clean during a build.
-
-### web.config
-
-Building Umbraco requires a clean `web.config` file in the `Umbraco.Web.UI` project. If a `web.config` file already exists, the `pre-build` task (see below) will save it as `web.config.temp-build` and replace it with a clean copy of `web.Template.config`. The original file is replaced once it is safe to do so, by the `pre-packages` task.
-
-## Build-UmbracoDocs
-
-Builds umbraco documentation. Temporary files are generated in `build.tmp` while the actual artifacts (docs...) are produced in `build.out`. Example:
-
- Build-UmbracoDocs
-
-Some log files, such as MsBuild logs, are produced in `build.tmp` too. The `build` directory should remain clean during a build.
-
-## Verify-NuGet
-
-Verifies that projects all require the same version of their dependencies, and that NuSpec files require versions that are consistent with projects. Example:
-
- Verify-NuGet
-
-# VSTS
-
-Continuous integration, nightly builds and release builds run on VSTS.
-
-VSTS uses the `Build-Umbraco` command several times, each time passing a different *target* parameter. The supported targets are:
-
-* `pre-build`: prepares the build
-* `compile-belle`: compiles Belle
-* `compile-umbraco`: compiles Umbraco
-* `pre-tests`: prepares the tests
-* `compile-tests`: compiles the tests
-* `pre-packages`: prepares the packages
-* `pkg-zip`: creates the zip files
-* `pre-nuget`: prepares NuGet packages
-* `pkg-nuget`: creates NuGet packages
-
-All these targets are executed when `Build-Umbraco` is invoked without a parameter (or with the `all` parameter). On VSTS, compilations (of Umbraco and tests) are performed by dedicated VSTS tasks. Similarly, creating the NuGet packages is also performed by dedicated VSTS tasks.
-
-Finally, the produced artifacts are published in two containers that can be downloaded from VSTS: `zips` contains the zip files while `nuget` contains the NuGet packages.
-
->During a VSTS build, some environment `UMBRACO_*` variables are exported by the `pre-build` target and can be reused in other targets *and* in VSTS tasks. The `UMBRACO_TMP` environment variable is used in `Umbraco.Tests` to disable some tests that have issues with VSTS at the moment.
-
-# Notes
-
-*This part needs to be cleaned up*
-
-Nightlies should use some sort of build number.
-
-We should increment versions as soon as a version is released. Ie, as soon as `7.6.33` is released, we should `Set-UmbracoVersion 7.6.34-alpha` and push.
-
-NuGet / NuSpec consistency checks are performed in tests. We should move it so it is done as part of the PowerShell script even before we try to compile and run the tests.
-
-There are still a few commands in `build` (to build docs, install Git or cleanup the install) that will need to be migrated to PowerShell.
-
-/eof
\ No newline at end of file
diff --git a/build/setversion.ps1 b/build/setversion.ps1
new file mode 100644
index 0000000000..b41cb94864
--- /dev/null
+++ b/build/setversion.ps1
@@ -0,0 +1,17 @@
+# Usage: powershell .\setversion.ps1 7.6.8
+# Or: powershell .\setversion 7.6.8-beta001
+
+param (
+ [Parameter(Mandatory=$true)]
+ [string]
+ $version
+)
+
+# report
+Write-Host "Setting Umbraco version to $version"
+
+# import Umbraco Build PowerShell module - $pwd is ./build
+./build.ps1 -mo
+
+# run commands
+$version = Set-UmbracoVersion -Version $version
diff --git a/src/Umbraco.Core/Auditing/IdentityAuditEventArgs.cs b/src/Umbraco.Core/Auditing/IdentityAuditEventArgs.cs
new file mode 100644
index 0000000000..14445d461f
--- /dev/null
+++ b/src/Umbraco.Core/Auditing/IdentityAuditEventArgs.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Threading;
+using System.Web;
+using Umbraco.Core.Security;
+
+namespace Umbraco.Core.Auditing
+{
+ ///
+ /// This class is used by events raised from hthe BackofficeUserManager
+ ///
+ public class IdentityAuditEventArgs : EventArgs
+ {
+ ///
+ /// The action that got triggered from the audit event
+ ///
+ public AuditEvent Action { get; private set; }
+
+ ///
+ /// Current date/time in UTC format
+ ///
+ public DateTime DateTimeUtc { get; private set; }
+
+ ///
+ /// The source IP address of the user performing the action
+ ///
+ public string IpAddress { get; private set; }
+
+ ///
+ /// The user affected by the event raised
+ ///
+ public int AffectedUser { get; private set; }
+
+ ///
+ /// If a user is perfoming an action on a different user, then this will be set. Otherwise it will be -1
+ ///
+ public int PerformingUser { get; private set; }
+
+ ///
+ /// An optional comment about the action being logged
+ ///
+ public string Comment { get; private set; }
+
+ ///
+ /// This property is always empty except in the LoginFailed event for an unknown user trying to login
+ ///
+ public string Username { get; private set; }
+
+ ///
+ /// Sets the properties on the event being raised, all parameters are optional except for the action being performed
+ ///
+ /// An action based on the AuditEvent enum
+ /// The client's IP address. This is usually automatically set but could be overridden if necessary
+ /// The Id of the user performing the action (if different from the user affected by the action)
+ public IdentityAuditEventArgs(AuditEvent action, string ipAddress, int performingUser = -1)
+ {
+ DateTimeUtc = DateTime.UtcNow;
+ Action = action;
+
+ IpAddress = ipAddress;
+
+ PerformingUser = performingUser == -1
+ ? GetCurrentRequestBackofficeUserId()
+ : performingUser;
+ }
+
+ public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string username, string comment)
+ {
+ DateTimeUtc = DateTime.UtcNow;
+ Action = action;
+
+ IpAddress = ipAddress;
+ Username = username;
+ Comment = comment;
+ }
+
+ ///
+ /// Returns the current logged in backoffice user's Id logging if there is one
+ ///
+ ///
+ protected int GetCurrentRequestBackofficeUserId()
+ {
+ var userId = -1;
+ var backOfficeIdentity = Thread.CurrentPrincipal.GetUmbracoIdentity();
+ if (backOfficeIdentity != null)
+ int.TryParse(backOfficeIdentity.Id.ToString(), out userId);
+ return userId;
+ }
+ }
+
+ public enum AuditEvent
+ {
+ AccountLocked,
+ AccountUnlocked,
+ ForgotPasswordRequested,
+ ForgotPasswordChangedSuccess,
+ LoginFailed,
+ LoginRequiresVerification,
+ LoginSucces,
+ LogoutSuccess,
+ PasswordChanged,
+ PasswordReset,
+ ResetAccessFailedCount
+ }
+}
diff --git a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs
index 3606b2fbf7..79d6d637c9 100644
--- a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs
+++ b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs
@@ -122,15 +122,34 @@ namespace Umbraco.Core.Security
return await SignInOrTwoFactor(user, isPersistent);
}
+ var requestContext = _request.Context;
+
if (user.HasIdentity && shouldLockout)
{
// If lockout is requested, increment access failed count which might lock out the user
await UserManager.AccessFailedAsync(user.Id);
if (await UserManager.IsLockedOutAsync(user.Id))
{
+ //at this point we've just locked the user out after too many failed login attempts
+
+ if (requestContext != null)
+ {
+ var backofficeUserManager = requestContext.GetBackOfficeUserManager();
+ if (backofficeUserManager != null)
+ backofficeUserManager.RaiseAccountLockedEvent(user.Id);
+ }
+
return SignInStatus.LockedOut;
}
}
+
+ if (requestContext != null)
+ {
+ var backofficeUserManager = requestContext.GetBackOfficeUserManager();
+ if (backofficeUserManager != null)
+ backofficeUserManager.RaiseInvalidLoginAttemptEvent(userName);
+ }
+
return SignInStatus.Failure;
}
@@ -198,7 +217,9 @@ namespace Umbraco.Core.Security
//track the last login date
user.LastLoginDateUtc = DateTime.UtcNow;
- user.AccessFailedCount = 0;
+ if (user.AccessFailedCount > 0)
+ //we have successfully logged in, reset the AccessFailedCount
+ user.AccessFailedCount = 0;
await UserManager.UpdateAsync(user);
_logger.WriteCore(TraceEventType.Information, 0,
diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Core/Security/BackOfficeUserManager.cs
index 1f12500f52..bcfcbb49d8 100644
--- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs
+++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs
@@ -1,16 +1,20 @@
using System;
using System.ComponentModel;
+using System.Configuration.Provider;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Security;
+using System.Web;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security.DataProtection;
using Umbraco.Core.Composing;
+using Umbraco.Core.Auditing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Models.Identity;
+using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
namespace Umbraco.Core.Security
@@ -44,7 +48,7 @@ namespace Umbraco.Core.Security
IContentSection contentSectionConfig)
: base(store)
{
- if (options == null) throw new ArgumentNullException("options"); ;
+ if (options == null) throw new ArgumentNullException("options");
InitUserManager(this, membershipProvider, contentSectionConfig, options);
}
@@ -391,7 +395,10 @@ namespace Umbraco.Core.Security
public override Task ChangePasswordAsync(int userId, string currentPassword, string newPassword)
{
- return base.ChangePasswordAsync(userId, currentPassword, newPassword);
+ var result = base.ChangePasswordAsync(userId, currentPassword, newPassword);
+ if (result.Result.Succeeded)
+ RaisePasswordChangedEvent(userId);
+ return result;
}
///
@@ -472,5 +479,187 @@ namespace Umbraco.Core.Security
}
#endregion
+
+ public override Task SetLockoutEndDateAsync(int userId, DateTimeOffset lockoutEnd)
+ {
+ var result = base.SetLockoutEndDateAsync(userId, lockoutEnd);
+
+ // The way we unlock is by setting the lockoutEnd date to the current datetime
+ if (result.Result.Succeeded && lockoutEnd >= DateTimeOffset.UtcNow)
+ RaiseAccountLockedEvent(userId);
+ else
+ RaiseAccountUnlockedEvent(userId);
+
+ return result;
+ }
+
+ public override async Task ResetAccessFailedCountAsync(int userId)
+ {
+ var lockoutStore = (IUserLockoutStore)Store;
+ var user = await FindByIdAsync(userId);
+ if (user == null)
+ throw new InvalidOperationException("No user found by user id " + userId);
+
+ var accessFailedCount = await GetAccessFailedCountAsync(user.Id);
+
+ if (accessFailedCount == 0)
+ return IdentityResult.Success;
+
+ await lockoutStore.ResetAccessFailedCountAsync(user);
+ //raise the event now that it's reset
+ RaiseResetAccessFailedCountEvent(userId);
+ return await UpdateAsync(user);
+ }
+
+
+
+
+ public override Task AccessFailedAsync(int userId)
+ {
+ var result = base.AccessFailedAsync(userId);
+
+ //Slightly confusing: this will return a Success if we successfully update the AccessFailed count
+ if (result.Result.Succeeded)
+ RaiseLoginFailedEvent(userId);
+
+ return result;
+ }
+
+ internal void RaiseAccountLockedEvent(int userId)
+ {
+ OnAccountLocked(new IdentityAuditEventArgs(AuditEvent.AccountLocked, GetCurrentRequestIpAddress(), userId));
+ }
+
+ internal void RaiseAccountUnlockedEvent(int userId)
+ {
+ OnAccountUnlocked(new IdentityAuditEventArgs(AuditEvent.AccountUnlocked, GetCurrentRequestIpAddress(), userId));
+ }
+
+ internal void RaiseForgotPasswordRequestedEvent(int userId)
+ {
+ OnForgotPasswordRequested(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordRequested, GetCurrentRequestIpAddress(), userId));
+ }
+
+ internal void RaiseForgotPasswordChangedSuccessEvent(int userId)
+ {
+ OnForgotPasswordChangedSuccess(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordChangedSuccess, GetCurrentRequestIpAddress(), userId));
+ }
+
+ internal void RaiseLoginFailedEvent(int userId)
+ {
+ OnLoginFailed(new IdentityAuditEventArgs(AuditEvent.LoginFailed, GetCurrentRequestIpAddress(), userId));
+ }
+
+ internal void RaiseInvalidLoginAttemptEvent(string username)
+ {
+ OnLoginFailed(new IdentityAuditEventArgs(AuditEvent.LoginFailed, GetCurrentRequestIpAddress(), username, string.Format("Attempted login for username '{0}' failed", username)));
+ }
+
+ internal void RaiseLoginRequiresVerificationEvent(int userId)
+ {
+ OnLoginRequiresVerification(new IdentityAuditEventArgs(AuditEvent.LoginRequiresVerification, GetCurrentRequestIpAddress(), userId));
+ }
+
+ internal void RaiseLoginSuccessEvent(int userId)
+ {
+ OnLoginSuccess(new IdentityAuditEventArgs(AuditEvent.LoginSucces, GetCurrentRequestIpAddress(), userId));
+ }
+
+ internal void RaiseLogoutSuccessEvent(int userId)
+ {
+ OnLogoutSuccess(new IdentityAuditEventArgs(AuditEvent.LogoutSuccess, GetCurrentRequestIpAddress(), userId));
+ }
+
+ internal void RaisePasswordChangedEvent(int userId)
+ {
+ OnPasswordChanged(new IdentityAuditEventArgs(AuditEvent.PasswordChanged, GetCurrentRequestIpAddress(), userId));
+ }
+
+ internal void RaisePasswordResetEvent(int userId)
+ {
+ OnPasswordReset(new IdentityAuditEventArgs(AuditEvent.PasswordReset, GetCurrentRequestIpAddress(), userId));
+ }
+ internal void RaiseResetAccessFailedCountEvent(int userId)
+ {
+ OnResetAccessFailedCount(new IdentityAuditEventArgs(AuditEvent.ResetAccessFailedCount, GetCurrentRequestIpAddress(), userId));
+ }
+
+ public static event EventHandler AccountLocked;
+ public static event EventHandler AccountUnlocked;
+ public static event EventHandler ForgotPasswordRequested;
+ public static event EventHandler ForgotPasswordChangedSuccess;
+ public static event EventHandler LoginFailed;
+ public static event EventHandler LoginRequiresVerification;
+ public static event EventHandler LoginSuccess;
+ public static event EventHandler LogoutSuccess;
+ public static event EventHandler PasswordChanged;
+ public static event EventHandler PasswordReset;
+ public static event EventHandler ResetAccessFailedCount;
+
+ protected virtual void OnAccountLocked(IdentityAuditEventArgs e)
+ {
+ if (AccountLocked != null) AccountLocked(this, e);
+ }
+
+ protected virtual void OnAccountUnlocked(IdentityAuditEventArgs e)
+ {
+ if (AccountUnlocked != null) AccountUnlocked(this, e);
+ }
+
+ protected virtual void OnForgotPasswordRequested(IdentityAuditEventArgs e)
+ {
+ if (ForgotPasswordRequested != null) ForgotPasswordRequested(this, e);
+ }
+
+ protected virtual void OnForgotPasswordChangedSuccess(IdentityAuditEventArgs e)
+ {
+ if (ForgotPasswordChangedSuccess != null) ForgotPasswordChangedSuccess(this, e);
+ }
+
+ protected virtual void OnLoginFailed(IdentityAuditEventArgs e)
+ {
+ if (LoginFailed != null) LoginFailed(this, e);
+ }
+
+ protected virtual void OnLoginRequiresVerification(IdentityAuditEventArgs e)
+ {
+ if (LoginRequiresVerification != null) LoginRequiresVerification(this, e);
+ }
+
+ protected virtual void OnLoginSuccess(IdentityAuditEventArgs e)
+ {
+ if (LoginSuccess != null) LoginSuccess(this, e);
+ }
+
+ protected virtual void OnLogoutSuccess(IdentityAuditEventArgs e)
+ {
+ if (LogoutSuccess != null) LogoutSuccess(this, e);
+ }
+
+ protected virtual void OnPasswordChanged(IdentityAuditEventArgs e)
+ {
+ if (PasswordChanged != null) PasswordChanged(this, e);
+ }
+
+ protected virtual void OnPasswordReset(IdentityAuditEventArgs e)
+ {
+ if (PasswordReset != null) PasswordReset(this, e);
+ }
+
+ protected virtual void OnResetAccessFailedCount(IdentityAuditEventArgs e)
+ {
+ if (ResetAccessFailedCount != null) ResetAccessFailedCount(this, e);
+ }
+
+ ///
+ /// Returns the current request IP address for logging if there is one
+ ///
+ ///
+ protected virtual string GetCurrentRequestIpAddress()
+ {
+ //TODO: inject a service to get this value, we should not be relying on the old HttpContext.Current especially in the ASP.NET Identity world.
+ var httpContext = HttpContext.Current == null ? (HttpContextBase)null : new HttpContextWrapper(HttpContext.Current);
+ return httpContext.GetCurrentRequestIpAddress();
+ }
}
}
diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js
index 089f55f9bf..841c0476e2 100644
--- a/src/Umbraco.Web.UI.Client/gulpfile.js
+++ b/src/Umbraco.Web.UI.Client/gulpfile.js
@@ -217,6 +217,16 @@ gulp.task('dependencies', function () {
gulp.src("src/canvasdesigner/editors/*.less")
.pipe(gulp.dest(root + targets.assets + "/less"))
);
+
+ // Todo: check if we need these fileSize
+ stream.add(
+ gulp.src("src/views/propertyeditors/grid/config/*.*")
+ .pipe(gulp.dest(root + targets.views + "/propertyeditors/grid/config"))
+ );
+ stream.add(
+ gulp.src("src/views/dashboard/default/*.jpg")
+ .pipe(gulp.dest(root + targets.views + "/dashboard/default"))
+ );
return stream;
});
diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js
index 10afba2d76..f0fa81faee 100644
--- a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js
+++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js
@@ -1,22 +1,23 @@
LazyLoad.js([
- '../lib/jquery/jquery.min.js',
- //'../lib/jquery-ui/jquery-ui.min.js',
- '/Scripts/jquery-1.6.4.min.js',
- '../lib/angular/1.1.5/angular.min.js',
- '../lib/underscore/underscore-min.js',
- '../lib/umbraco/Extensions.js',
- '../js/app.js',
- '../js/umbraco.resources.js',
- '../js/umbraco.services.js',
- '../js/umbraco.security.js',
- '../ServerVariables',
- '../lib/spectrum/spectrum.js',
- '../lib/signalr/jquery.signalR.js',
- '/umbraco/signalr/hubs',
- '../js/umbraco.canvasdesigner.js'
+ '../lib/jquery/jquery.min.js',
+ //'../lib/jquery-ui/jquery-ui.min.js',
+ '/Scripts/jquery-1.6.4.min.js',
+ '../lib/angular/1.1.5/angular.min.js',
+ '../lib/underscore/underscore-min.js',
+ '../lib/umbraco/Extensions.js',
+ '../js/app.js',
+ '../js/umbraco.resources.js',
+ '../js/umbraco.services.js',
+ '../js/umbraco.security.js',
+ '../ServerVariables',
+ '../lib/spectrum/spectrum.js',
+ '../lib/signalr/jquery.signalR.js',
+ '/umbraco/signalr/hubs',
+ '../js/umbraco.canvasdesigner.js'
+ '../js/canvasdesigner.panel.js'
], function () {
- jQuery(document).ready(function () {
- angular.bootstrap(document, ['Umbraco.canvasdesigner']);
- });
+ jQuery(document).ready(function () {
+ angular.bootstrap(document, ['Umbraco.canvasdesigner']);
+ });
});
diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less
index ede8245008..2388aae2b2 100644
--- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less
+++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less
@@ -304,7 +304,7 @@ ul.color-picker li a {
top: 0;
left: 0;
cursor: move;
- z-index: 499;
+ z-index: 6001;
position: absolute;
}
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml
index 4845862765..bd4c909f5b 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml
@@ -15,6 +15,7 @@
Slet
Deaktivér
Tøm papirkurv
+ Aktivér
Eksportér dokumenttype
Eksportér til .NET
Eksportér til .NET
@@ -185,8 +186,10 @@
"dokument typer".]]>
"media typer".]]>
Dokumenttype uden skabelon
+ Ny tom partial view
Ny mappe
Ny datatype
+ Ny partial view fra snippet
Til dit website
@@ -460,12 +463,14 @@
Papirkurv
Din papirkurv er tom
Mangler
+ Fjern
Omdøb
Forny
Påkrævet
Prøv igen
Rettigheder
Søg
+ Beklager, vi kan ikke finde det, du leder efter.
Server
Vis
Hvilken side skal vises efter at formularen er sendt
@@ -1114,6 +1119,7 @@ Mange hilsner fra Umbraco robotten
Der kunne ikke tjekkes for ny opdatering. Se trace for mere info.
+ Adgang
Administrator
Kategorifelt
Skift dit kodeord
@@ -1121,18 +1127,22 @@ Mange hilsner fra Umbraco robotten
Gentag dit nye kodeord
Du kan ændre dit kodeord, som giver dig adgang til Umbraco Back Office ved at udfylde formularen og klikke på knappen 'Skift dit kodeord'
Indholdskanal
+ Skift billede
Beskrivelsesfelt
Deaktivér bruger
Dokumenttype
Redaktør
Uddragsfelt
Sprog
+ Sidste login
Brugernavn
Startnode i mediearkivet
Moduler
Deaktivér adgang til Umbraco
+ har endnu ikke logget ind
Gammelt kodeord
Adgangskode
+ Fjern billede
Nulstil kodeord
Dit kodeord er blevet ændret!
Bekræft venligst dit nye kodeord
@@ -1145,6 +1155,7 @@ Mange hilsner fra Umbraco robotten
Erstat underelement-rettigheder
Du ændrer i øjeblikket rettigheder for siderne:
Vælg sider for at ændre deres rettigheder
+ Profil
Søg alle 'børn'
Start node
Navn
diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs
index 7ce63e902c..4417d03d8b 100644
--- a/src/Umbraco.Web/Editors/AuthenticationController.cs
+++ b/src/Umbraco.Web/Editors/AuthenticationController.cs
@@ -174,7 +174,7 @@ namespace Umbraco.Web.Editors
///
/// We cannot user GetCurrentUser since that requires they are approved, this is the same as GetCurrentUser but doesn't require them to be approved
///
- [WebApi.UmbracoAuthorize(requireApproval:false)]
+ [WebApi.UmbracoAuthorize(requireApproval: false)]
[SetAngularAntiForgeryTokens]
public UserDetail GetCurrentInvitedUser()
{
@@ -200,7 +200,7 @@ namespace Umbraco.Web.Editors
//TODO: This should be on the CurrentUserController?
[WebApi.UmbracoAuthorize]
[ValidateAngularAntiForgeryToken]
- public async Task> GetCurrentUserLinkedLogins()
+ public async Task> GetCurrentUserLinkedLogins()
{
var identityUser = await UserManager.FindByIdAsync(UmbracoContext.Security.GetUserId());
return identityUser.Logins.ToDictionary(x => x.LoginProvider, x => x.ProviderKey);
@@ -223,9 +223,11 @@ namespace Umbraco.Web.Editors
switch (result)
{
case SignInStatus.Success:
-
+
//get the user
var user = Services.UserService.GetByUsername(loginModel.Username);
+ UserManager.RaiseLoginSuccessEvent(user.Id);
+
return SetPrincipalAndReturnUserDetail(user);
case SignInStatus.RequiresVerification:
@@ -260,6 +262,8 @@ namespace Umbraco.Web.Editors
userId = attemptedUser.Id
});
+ UserManager.RaiseLoginRequiresVerificationEvent(attemptedUser.Id);
+
return verifyResponse;
case SignInStatus.LockedOut:
@@ -299,13 +303,15 @@ namespace Umbraco.Web.Editors
var message = Services.TextService.Localize("resetPasswordEmailCopyFormat",
//Ensure the culture of the found user is used for the email!
UserExtensions.GetUserCulture(identityUser.Culture, Services.TextService),
- new[] {identityUser.UserName, callbackUrl});
+ new[] { identityUser.UserName, callbackUrl });
await UserManager.SendEmailAsync(identityUser.Id,
Services.TextService.Localize("login/resetPasswordEmailCopySubject",
//Ensure the culture of the found user is used for the email!
UserExtensions.GetUserCulture(identityUser.Culture, Services.TextService)),
message);
+
+ UserManager.RaiseForgotPasswordRequestedEvent(user.Id);
}
}
@@ -366,20 +372,22 @@ namespace Umbraco.Web.Editors
}
var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: true, rememberBrowser: false);
+
+ var user = Services.UserService.GetByUsername(userName);
switch (result)
{
case SignInStatus.Success:
- //get the user
- var user = Services.UserService.GetByUsername(userName);
+ UserManager.RaiseLoginSuccessEvent(user.Id);
return SetPrincipalAndReturnUserDetail(user);
case SignInStatus.LockedOut:
+ UserManager.RaiseAccountLockedEvent(user.Id);
return Request.CreateValidationErrorResponse("User is locked out");
case SignInStatus.Failure:
default:
return Request.CreateValidationErrorResponse("Invalid code");
}
}
-
+
///
/// Processes a set password request. Validates the request and sets a new password.
///
@@ -399,7 +407,7 @@ namespace Umbraco.Web.Editors
//var user = await UserManager.FindByIdAsync(model.UserId);
var unlockResult = await UserManager.SetLockoutEndDateAsync(model.UserId, DateTimeOffset.Now);
- if(unlockResult.Succeeded == false)
+ if (unlockResult.Succeeded == false)
{
Logger.Warn("Could not unlock for user {0} - error {1}",
() => model.UserId, () => unlockResult.Errors.First());
@@ -413,6 +421,7 @@ namespace Umbraco.Web.Editors
}
}
+ UserManager.RaiseForgotPasswordChangedSuccessEvent(model.UserId);
return Request.CreateResponse(HttpStatusCode.OK);
}
return Request.CreateValidationErrorResponse(
@@ -436,10 +445,16 @@ namespace Umbraco.Web.Editors
() => User.Identity == null ? "UNKNOWN" : User.Identity.Name,
() => TryGetOwinContext().Result.Request.RemoteIpAddress);
+ if (UserManager != null)
+ {
+ var userId = -1;
+ int.TryParse(User.Identity.GetUserId(), out userId);
+ UserManager.RaiseLogoutSuccessEvent(userId);
+ }
+
return Request.CreateResponse(HttpStatusCode.OK);
}
-
///
/// This is used when the user is auth'd successfully and we need to return an OK with user details along with setting the current Principal in the request
///
diff --git a/src/Umbraco.Web/Editors/CurrentUserController.cs b/src/Umbraco.Web/Editors/CurrentUserController.cs
index 6c34a198d3..c59aac8280 100644
--- a/src/Umbraco.Web/Editors/CurrentUserController.cs
+++ b/src/Umbraco.Web/Editors/CurrentUserController.cs
@@ -79,11 +79,23 @@ namespace Umbraco.Web.Editors
///
public async Task> PostChangePassword(ChangingPasswordModel data)
{
- var passwordChanger = new PasswordChanger(Logger, Services.UserService);
+ var passwordChanger = new PasswordChanger(Logger, Services.UserService, UmbracoContext.HttpContext);
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, Security.CurrentUser, data, UserManager);
if (passwordChangeResult.Success)
{
+ var userMgr = this.TryGetOwinContext().Result.GetBackOfficeUserManager();
+
+ //raise the appropriate event
+ if (data.Reset.HasValue && data.Reset.Value)
+ {
+ userMgr.RaisePasswordResetEvent(Security.CurrentUser.Id);
+ }
+ else
+ {
+ userMgr.RaisePasswordChangedEvent(Security.CurrentUser.Id);
+ }
+
//even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword
var result = new ModelWithNotifications(passwordChangeResult.Result.ResetPassword);
result.AddSuccessNotification(Services.TextService.Localize("user/password"), Services.TextService.Localize("user/passwordChanged"));
diff --git a/src/Umbraco.Web/Editors/PasswordChanger.cs b/src/Umbraco.Web/Editors/PasswordChanger.cs
index cc4a7d8c1f..d52ebe10d0 100644
--- a/src/Umbraco.Web/Editors/PasswordChanger.cs
+++ b/src/Umbraco.Web/Editors/PasswordChanger.cs
@@ -1,6 +1,7 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
+using System.Web;
using System.Web.Http.ModelBinding;
using System.Web.Security;
using Umbraco.Core;
@@ -18,11 +19,13 @@ namespace Umbraco.Web.Editors
{
private readonly ILogger _logger;
private readonly IUserService _userService;
+ private readonly HttpContextBase _httpContext;
- public PasswordChanger(ILogger logger, IUserService userService)
+ public PasswordChanger(ILogger logger, IUserService userService, HttpContextBase httpContext)
{
_logger = logger;
_userService = userService;
+ _httpContext = httpContext;
}
///
@@ -143,6 +146,20 @@ namespace Umbraco.Web.Editors
if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel));
if (membershipProvider == null) throw new ArgumentNullException(nameof(membershipProvider));
+ BackOfficeUserManager backofficeUserManager = null;
+ var userId = -1;
+
+ if (membershipProvider.IsUmbracoUsersProvider())
+ {
+ backofficeUserManager = _httpContext.GetOwinContext().GetBackOfficeUserManager();
+ if (backofficeUserManager != null)
+ {
+ var profile = _userService.GetProfileByUserName(username);
+ if (profile != null)
+ int.TryParse(profile.Id.ToString(), out userId);
+ }
+ }
+
//Are we resetting the password??
if (passwordModel.Reset.HasValue && passwordModel.Reset.Value)
{
@@ -162,6 +179,9 @@ namespace Umbraco.Web.Editors
username,
membershipProvider.RequiresQuestionAndAnswer ? passwordModel.Answer : null);
+ if (membershipProvider.IsUmbracoUsersProvider() && backofficeUserManager != null && userId >= 0)
+ backofficeUserManager.RaisePasswordResetEvent(userId);
+
//return the generated pword
return Attempt.Succeed(new PasswordChangedModel { ResetPassword = newPass });
}
@@ -211,6 +231,10 @@ namespace Umbraco.Web.Editors
try
{
var result = membershipProvider.ChangePassword(username, passwordModel.OldPassword, passwordModel.NewPassword);
+
+ if (result && backofficeUserManager != null && userId >= 0)
+ backofficeUserManager.RaisePasswordChangedEvent(userId);
+
return result == false
? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "oldPassword" }) })
: Attempt.Succeed(new PasswordChangedModel());
@@ -260,6 +284,5 @@ namespace Umbraco.Web.Editors
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex2.Message + " (see log for full details)", new[] { "value" }) });
}
}
-
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs
index 785710fc61..3437406262 100644
--- a/src/Umbraco.Web/Editors/UsersController.cs
+++ b/src/Umbraco.Web/Editors/UsersController.cs
@@ -531,11 +531,20 @@ namespace Umbraco.Web.Editors
if (userSave.ChangePassword != null)
{
- var passwordChanger = new PasswordChanger(Logger, Services.UserService);
+ var passwordChanger = new PasswordChanger(Logger, Services.UserService, UmbracoContext.HttpContext);
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, found, userSave.ChangePassword, UserManager);
if (passwordChangeResult.Success)
{
+ var userMgr = this.TryGetOwinContext().Result.GetBackOfficeUserManager();
+
+ //raise the event - NOTE that the ChangePassword.Reset value here doesn't mean it's been 'reset', it means
+ //it's been changed by a back office user
+ if (userSave.ChangePassword.Reset.HasValue && userSave.ChangePassword.Reset.Value)
+ {
+ userMgr.RaisePasswordChangedEvent(intId.Result);
+ }
+
//need to re-get the user
found = Services.UserService.GetUserById(intId.Result);
}
diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs
index d1b47f9804..176eb61486 100644
--- a/src/Umbraco.Web/Security/MembershipHelper.cs
+++ b/src/Umbraco.Web/Security/MembershipHelper.cs
@@ -156,11 +156,11 @@ namespace Umbraco.Web.Security
///
private bool HasAccess(string path, RoleProvider roleProvider)
{
- return _umbracoContext.PublishedContentRequest == null
+ return _umbracoContext.PublishedContentRequest == null
? PublicAccessService.HasAccess(path, CurrentUserName, roleProvider.GetRolesForUser)
: PublicAccessService.HasAccess(path, CurrentUserName, Router.GetRolesForLogin);
}
-
+
///
/// Returns true if the current membership provider is the Umbraco built-in one.
///
@@ -712,6 +712,7 @@ namespace Umbraco.Web.Security
{
throw new InvalidOperationException("Could not find provider with name " + membershipProviderName);
}
+
return ChangePassword(username, passwordModel, provider);
}
@@ -724,10 +725,10 @@ namespace Umbraco.Web.Security
///
public virtual Attempt ChangePassword(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider)
{
- var passwordChanger = new PasswordChanger(Logger, UserService);
- return passwordChanger.ChangePasswordWithMembershipProvider(username, passwordModel, membershipProvider);
+ var passwordChanger = new PasswordChanger(Logger, UserService, Current.UmbracoContext.HttpContext));
+ return passwordChanger.ChangePasswordWithMembershipProvider(username, passwordModel, membershipProvider);
}
-
+
///
/// Updates a membership user with all of it's writable properties
///
diff --git a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs
index ac494349a5..20526a6208 100644
--- a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs
+++ b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs
@@ -3,6 +3,7 @@ using System.Collections.Specialized;
using System.Configuration.Provider;
using System.Linq;
using System.Text;
+using System.Web;
using System.Web.Configuration;
using System.Web.Security;
using Umbraco.Core;
@@ -13,6 +14,7 @@ using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
+using Umbraco.Core.Models.Identity;
namespace Umbraco.Web.Security.Providers
{
@@ -59,7 +61,7 @@ namespace Umbraco.Web.Security.Providers
/// The name of the provider has a length of zero.
public override void Initialize(string name, NameValueCollection config)
{
- if (config == null) {throw new ArgumentNullException("config");}
+ if (config == null) { throw new ArgumentNullException("config"); }
if (string.IsNullOrEmpty(name)) name = ProviderName;
@@ -447,6 +449,25 @@ namespace Umbraco.Web.Security.Providers
return generatedPassword;
}
+ internal virtual bool PerformUnlockUser(string username, out TEntity member)
+ {
+ member = MemberService.GetByUsername(username);
+ if (member == null)
+ {
+ throw new ProviderException(string.Format("No member with the username '{0}' found", username));
+ }
+
+ // Non need to update
+ if (member.IsLockedOut == false) return true;
+
+ member.IsLockedOut = false;
+ member.FailedPasswordAttempts = 0;
+
+ MemberService.Save(member);
+
+ return true;
+ }
+
///
/// Clears a lock so that the membership user can be validated.
///
@@ -456,22 +477,9 @@ namespace Umbraco.Web.Security.Providers
///
public override bool UnlockUser(string username)
{
- var member = MemberService.GetByUsername(username);
-
- if (member == null)
- {
- throw new ProviderException(string.Format("No member with the username '{0}' found", username));
- }
-
- // Non need to update
- if (member.IsLockedOut == false) return true;
-
- member.IsLockedOut = false;
- member.FailedPasswordAttempts = 0;
-
- MemberService.Save(member);
-
- return true;
+ TEntity member;
+ var result = PerformUnlockUser(username, out member);
+ return result;
}
///
@@ -509,15 +517,9 @@ namespace Umbraco.Web.Security.Providers
MemberService.Save(m);
}
- ///
- /// Verifies that the specified user name and password exist in the data source.
- ///
- /// The name of the user to validate.
- /// The password for the specified user.
- ///
- /// true if the specified username and password are valid; otherwise, false.
- ///
- public override bool ValidateUser(string username, string password)
+
+
+ internal virtual ValidateUserResult PerformValidateUser(string username, string password)
{
var member = MemberService.GetByUsername(username);
@@ -529,7 +531,10 @@ namespace Umbraco.Web.Security.Providers
username,
GetCurrentRequestIpAddress()));
- return false;
+ return new ValidateUserResult
+ {
+ Authenticated = false
+ };
}
if (member.IsApproved == false)
@@ -540,7 +545,11 @@ namespace Umbraco.Web.Security.Providers
username,
GetCurrentRequestIpAddress()));
- return false;
+ return new ValidateUserResult
+ {
+ Member = member,
+ Authenticated = false
+ };
}
if (member.IsLockedOut)
{
@@ -550,7 +559,11 @@ namespace Umbraco.Web.Security.Providers
username,
GetCurrentRequestIpAddress()));
- return false;
+ return new ValidateUserResult
+ {
+ Member = member,
+ Authenticated = false
+ };
}
var authenticated = CheckPassword(password, member.RawPasswordValue);
@@ -572,7 +585,7 @@ namespace Umbraco.Web.Security.Providers
string.Format(
"Login attempt failed for username {0} from IP address {1}, the user is now locked out, max invalid password attempts exceeded",
username,
- GetCurrentRequestIpAddress()));
+ GetCurrentRequestIpAddress()));
}
else
{
@@ -585,14 +598,19 @@ namespace Umbraco.Web.Security.Providers
}
else
{
- member.FailedPasswordAttempts = 0;
+ if (member.FailedPasswordAttempts > 0)
+ {
+ //we have successfully logged in, reset the AccessFailedCount
+ member.FailedPasswordAttempts = 0;
+ }
+
member.LastLoginDate = DateTime.Now;
Current.Logger.Info(
string.Format(
"Login attempt succeeded for username {0} from IP address {1}",
username,
- GetCurrentRequestIpAddress()));
+ GetCurrentRequestIpAddress()));
}
//don't raise events for this! It just sets the member dates, if we do raise events this will
@@ -605,12 +623,31 @@ namespace Umbraco.Web.Security.Providers
if (UmbracoVersion.Current >= new Version(7, 3, 0, 0))
MemberService.Save(member, false);
- return authenticated;
+ return new ValidateUserResult
+ {
+ Authenticated = authenticated,
+ Member = member
+ };
}
+ ///
+ /// Verifies that the specified user name and password exist in the data source.
+ ///
+ /// The name of the user to validate.
+ /// The password for the specified user.
+ ///
+ /// true if the specified username and password are valid; otherwise, false.
+ ///
+ public override bool ValidateUser(string username, string password)
+ {
+ var result = PerformValidateUser(username, password);
+ return result.Authenticated;
+ }
-
-
-
+ internal class ValidateUserResult
+ {
+ public TEntity Member { get; set; }
+ public bool Authenticated { get; set; }
+ }
}
}
diff --git a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs
index 440caaab9c..51aec7d729 100644
--- a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs
+++ b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs
@@ -2,9 +2,11 @@
using System.Configuration.Provider;
using System.Security.Cryptography;
using System.Text;
+using System.Web;
using System.Web.Security;
using Umbraco.Core;
using Umbraco.Core.Configuration;
+using Umbraco.Core.Models.Identity;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
@@ -56,7 +58,7 @@ namespace Umbraco.Web.Security.Providers
public override bool EnablePasswordReset
{
get { return _enablePasswordReset; }
- }
+ }
///
/// For backwards compatibility, this provider supports this option by default it is FALSE for users
@@ -64,7 +66,7 @@ namespace Umbraco.Web.Security.Providers
public override bool AllowManuallyChangingPassword
{
get { return _allowManuallyChangingPassword; }
- }
+ }
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
@@ -73,7 +75,7 @@ namespace Umbraco.Web.Security.Providers
if (config == null) { throw new ArgumentNullException("config"); }
_allowManuallyChangingPassword = config.GetValue("allowManuallyChangingPassword", false);
- _enablePasswordReset = config.GetValue("enablePasswordReset", false);
+ _enablePasswordReset = config.GetValue("enablePasswordReset", false);
// test for membertype (if not specified, choose the first member type available)
// We'll support both names for legacy reasons: defaultUserTypeAlias & defaultUserGroupAlias
@@ -118,5 +120,69 @@ namespace Umbraco.Web.Security.Providers
return _defaultMemberTypeAlias;
}
}
+
+ ///
+ /// Overridden in order to call the BackOfficeUserManager.UnlockUser method in order to raise the user audit events
+ ///
+ ///
+ ///
+ ///
+ internal override bool PerformUnlockUser(string username, out IUser member)
+ {
+ var result = base.PerformUnlockUser(username, out member);
+ if (result)
+ {
+ var userManager = GetBackofficeUserManager();
+ if (userManager != null)
+ {
+ userManager.RaiseAccountUnlockedEvent(member.Id);
+ }
+ }
+ return result;
+ }
+
+ ///
+ /// Override in order to raise appropriate events via the
+ ///
+ ///
+ ///
+ ///
+ internal override ValidateUserResult PerformValidateUser(string username, string password)
+ {
+ var result = base.PerformValidateUser(username, password);
+
+ var userManager = GetBackofficeUserManager();
+
+ if (userManager == null) return result;
+
+ if (result.Authenticated == false)
+ {
+ var count = result.Member.FailedPasswordAttempts;
+ if (count >= MaxInvalidPasswordAttempts)
+ {
+ userManager.RaiseAccountLockedEvent(result.Member.Id);
+ }
+ }
+ else
+ {
+ if (result.Member.FailedPasswordAttempts > 0)
+ {
+ //we have successfully logged in, if the failed password attempts was modified it means it was reset
+ if (result.Member.WasPropertyDirty("FailedPasswordAttempts"))
+ {
+ userManager.RaiseResetAccessFailedCountEvent(result.Member.Id);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ internal BackOfficeUserManager GetBackofficeUserManager()
+ {
+ return HttpContext.Current == null
+ ? null
+ : HttpContext.Current.GetOwinContext().GetBackOfficeUserManager();
+ }
}
-}
+}
\ No newline at end of file