Implements more BackOfficeController and AuthenticationController, some web security and more, gets the back office UI almost rendering

This commit is contained in:
Shannon
2020-05-25 23:15:32 +10:00
parent 0730867d74
commit 9dcad544a9
16 changed files with 611 additions and 222 deletions

View File

@@ -13,7 +13,6 @@ using Umbraco.Net;
namespace Umbraco.Core.BackOffice
{
public class BackOfficeUserManager : BackOfficeUserManager<BackOfficeIdentityUser>
{
public BackOfficeUserManager(
@@ -399,59 +398,26 @@ namespace Umbraco.Core.BackOffice
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 OnAccountLocked(IdentityAuditEventArgs e) => AccountLocked?.Invoke(this, e);
protected virtual void OnAccountUnlocked(IdentityAuditEventArgs e)
{
if (AccountUnlocked != null) AccountUnlocked(this, e);
}
protected virtual void OnAccountUnlocked(IdentityAuditEventArgs e) => AccountUnlocked?.Invoke(this, e);
protected virtual void OnForgotPasswordRequested(IdentityAuditEventArgs e)
{
if (ForgotPasswordRequested != null) ForgotPasswordRequested(this, e);
}
protected virtual void OnForgotPasswordRequested(IdentityAuditEventArgs e) => ForgotPasswordRequested?.Invoke(this, e);
protected virtual void OnForgotPasswordChangedSuccess(IdentityAuditEventArgs e)
{
if (ForgotPasswordChangedSuccess != null) ForgotPasswordChangedSuccess(this, e);
}
protected virtual void OnForgotPasswordChangedSuccess(IdentityAuditEventArgs e) => ForgotPasswordChangedSuccess?.Invoke(this, e);
protected virtual void OnLoginFailed(IdentityAuditEventArgs e)
{
if (LoginFailed != null) LoginFailed(this, e);
}
protected virtual void OnLoginFailed(IdentityAuditEventArgs e) => LoginFailed?.Invoke(this, e);
protected virtual void OnLoginRequiresVerification(IdentityAuditEventArgs e)
{
if (LoginRequiresVerification != null) LoginRequiresVerification(this, e);
}
protected virtual void OnLoginRequiresVerification(IdentityAuditEventArgs e) => LoginRequiresVerification?.Invoke(this, e);
protected virtual void OnLoginSuccess(IdentityAuditEventArgs e)
{
if (LoginSuccess != null) LoginSuccess(this, e);
}
protected virtual void OnLoginSuccess(IdentityAuditEventArgs e) => LoginSuccess?.Invoke(this, e);
protected virtual void OnLogoutSuccess(IdentityAuditEventArgs e)
{
if (LogoutSuccess != null) LogoutSuccess(this, e);
}
protected virtual void OnLogoutSuccess(IdentityAuditEventArgs e) => LogoutSuccess?.Invoke(this, e);
protected virtual void OnPasswordChanged(IdentityAuditEventArgs e)
{
if (PasswordChanged != null) PasswordChanged(this, e);
}
protected virtual void OnPasswordChanged(IdentityAuditEventArgs e) => PasswordChanged?.Invoke(this, e);
protected virtual void OnPasswordReset(IdentityAuditEventArgs e)
{
if (PasswordReset != null) PasswordReset(this, e);
}
protected virtual void OnPasswordReset(IdentityAuditEventArgs e) => PasswordReset?.Invoke(this, e);
protected virtual void OnResetAccessFailedCount(IdentityAuditEventArgs e)
{
if (ResetAccessFailedCount != null) ResetAccessFailedCount(this, e);
}
protected virtual void OnResetAccessFailedCount(IdentityAuditEventArgs e) => ResetAccessFailedCount?.Invoke(this, e);
}
}

View File

@@ -1,8 +1,23 @@
using Microsoft.AspNetCore.Mvc;
using System;
using System.Net;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.BackOffice;
using Umbraco.Core.Configuration;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Extensions;
using Umbraco.Web.BackOffice.Security;
using Umbraco.Web.Common.Attributes;
using Umbraco.Web.Common.Controllers;
using Umbraco.Web.Common.Exceptions;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Models;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Security;
using Constants = Umbraco.Core.Constants;
@@ -12,14 +27,31 @@ namespace Umbraco.Web.BackOffice.Controllers
//[ValidationFilter] // TODO: I don't actually think this is required with our custom Application Model conventions applied
[TypeFilter(typeof(AngularJsonOnlyConfigurationAttribute))] // TODO: This could be applied with our Application Model conventions
[IsBackOffice] // TODO: This could be applied with our Application Model conventions
public class AuthenticationController : ControllerBase
public class AuthenticationController : UmbracoApiControllerBase
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
// TODO: We need to import the logic from Umbraco.Web.Editors.AuthenticationController and it should not be an auto-routed api controller
private readonly BackOfficeUserManager _userManager;
private readonly BackOfficeSignInManager _signInManager;
private readonly IUserService _userService;
private readonly UmbracoMapper _umbracoMapper;
private readonly IGlobalSettings _globalSettings;
public AuthenticationController(IUmbracoContextAccessor umbracoContextAccessor)
// TODO: We need to import the logic from Umbraco.Web.Editors.AuthenticationController
public AuthenticationController(
IUmbracoContextAccessor umbracoContextAccessor,
BackOfficeUserManager backOfficeUserManager,
BackOfficeSignInManager signInManager,
IUserService userService,
UmbracoMapper umbracoMapper,
IGlobalSettings globalSettings)
{
_umbracoContextAccessor = umbracoContextAccessor;
_userManager = backOfficeUserManager;
_signInManager = signInManager;
_userService = userService;
_umbracoMapper = umbracoMapper;
_globalSettings = globalSettings;
}
/// <summary>
@@ -37,5 +69,95 @@ namespace Umbraco.Web.BackOffice.Controllers
}
return false;
}
/// <summary>
/// Logs a user in
/// </summary>
/// <returns></returns>
[TypeFilter(typeof(SetAngularAntiForgeryTokens))]
public async Task<UserDetail> PostLogin(LoginModel loginModel)
{
// Sign the user in with username/password, this also gives a chance for developers to
// custom verify the credentials and auto-link user accounts with a custom IBackOfficePasswordChecker
var result = await _signInManager.PasswordSignInAsync(
loginModel.Username, loginModel.Password, isPersistent: true, lockoutOnFailure: true);
if (result.Succeeded)
{
// get the user
var user = _userService.GetByUsername(loginModel.Username);
_userManager.RaiseLoginSuccessEvent(User, user.Id);
// TODO: This is not correct, no user will be set :/
return SetPrincipalAndReturnUserDetail(user, HttpContext.User);
}
if (result.RequiresTwoFactor)
{
throw new NotImplementedException("Implement MFA/2FA, we need to have some IOptions or similar to configure this");
//var twofactorOptions = UserManager as IUmbracoBackOfficeTwoFactorOptions;
//if (twofactorOptions == null)
//{
// throw new HttpResponseException(
// Request.CreateErrorResponse(
// HttpStatusCode.BadRequest,
// "UserManager does not implement " + typeof(IUmbracoBackOfficeTwoFactorOptions)));
//}
//var twofactorView = twofactorOptions.GetTwoFactorView(
// owinContext,
// UmbracoContext,
// loginModel.Username);
//if (twofactorView.IsNullOrWhiteSpace())
//{
// throw new HttpResponseException(
// Request.CreateErrorResponse(
// HttpStatusCode.BadRequest,
// typeof(IUmbracoBackOfficeTwoFactorOptions) + ".GetTwoFactorView returned an empty string"));
//}
//var attemptedUser = Services.UserService.GetByUsername(loginModel.Username);
//// create a with information to display a custom two factor send code view
//var verifyResponse = Request.CreateResponse(HttpStatusCode.PaymentRequired, new
//{
// twoFactorView = twofactorView,
// userId = attemptedUser.Id
//});
//_userManager.RaiseLoginRequiresVerificationEvent(User, attemptedUser.Id);
//return verifyResponse;
}
// return BadRequest (400), we don't want to return a 401 because that get's intercepted
// by our angular helper because it thinks that we need to re-perform the request once we are
// authorized and we don't want to return a 403 because angular will show a warning message indicating
// that the user doesn't have access to perform this function, we just want to return a normal invalid message.
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
/// <summary>
/// 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
/// </summary>
/// <param name="user"></param>
/// <param name="principal"></param>
/// <returns></returns>
private UserDetail SetPrincipalAndReturnUserDetail(IUser user, ClaimsPrincipal principal)
{
if (user == null) throw new ArgumentNullException("user");
if (principal == null) throw new ArgumentNullException(nameof(principal));
var userDetail = _umbracoMapper.Map<UserDetail>(user);
// update the userDetail and set their remaining seconds
userDetail.SecondsUntilTimeout = TimeSpan.FromMinutes(_globalSettings.TimeOutInMinutes).TotalSeconds;
// ensure the user is set for the current request
HttpContext.SetPrincipalForRequest(principal);
return userDetail;
}
}
}

View File

@@ -16,6 +16,7 @@ using Umbraco.Core.Services;
using Umbraco.Core.WebAssets;
using Umbraco.Extensions;
using Umbraco.Web.BackOffice.Filters;
using Umbraco.Web.BackOffice.Security;
using Umbraco.Web.Common.ActionResults;
using Umbraco.Web.Models;
using Umbraco.Web.WebAssets;
@@ -36,20 +37,19 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly IGridConfig _gridConfig;
private readonly BackOfficeServerVariables _backOfficeServerVariables;
private readonly AppCaches _appCaches;
private readonly SignInManager<BackOfficeIdentityUser> _signInManager;
private readonly BackOfficeSignInManager _signInManager;
public BackOfficeController(
BackOfficeUserManager userManager,
IRuntimeMinifier runtimeMinifier,
IGlobalSettings globalSettings,
IHostingEnvironment hostingEnvironment,
IUmbracoContextAccessor umbracoContextAccessor,
ILocalizedTextService textService,
IGridConfig gridConfig,
BackOfficeServerVariables backOfficeServerVariables,
AppCaches appCaches,
SignInManager<BackOfficeIdentityUser> signInManager // TODO: Review this, do we want it/need it or create our own?
BackOfficeSignInManager signInManager // TODO: Review this, do we want it/need it or create our own?
)
{
_userManager = userManager;

View File

@@ -15,6 +15,7 @@ using Umbraco.Core.Hosting;
using Umbraco.Core.WebAssets;
using Umbraco.Extensions;
using Umbraco.Web.Common.Attributes;
using Umbraco.Web.Editors;
using Umbraco.Web.Features;
using Umbraco.Web.Trees;
using Umbraco.Web.WebApi;
@@ -39,7 +40,6 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly ISecuritySettings _securitySettings;
private readonly IRuntimeMinifier _runtimeMinifier;
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
private readonly UmbracoApiControllerTypeCollection _apiControllers;
public BackOfficeServerVariables(
LinkGenerator linkGenerator,
@@ -54,8 +54,7 @@ namespace Umbraco.Web.BackOffice.Controllers
IRuntimeSettings settings,
ISecuritySettings securitySettings,
IRuntimeMinifier runtimeMinifier,
IAuthenticationSchemeProvider authenticationSchemeProvider,
UmbracoApiControllerTypeCollection apiControllers)
IAuthenticationSchemeProvider authenticationSchemeProvider)
{
_linkGenerator = linkGenerator;
_runtimeState = runtimeState;
@@ -70,7 +69,6 @@ namespace Umbraco.Web.BackOffice.Controllers
_securitySettings = securitySettings;
_runtimeMinifier = runtimeMinifier;
_authenticationSchemeProvider = authenticationSchemeProvider;
_apiControllers = apiControllers;
}
/// <summary>
@@ -114,7 +112,11 @@ namespace Umbraco.Web.BackOffice.Controllers
// TODO: This is ultra confusing! this same key is used for different things, when returning the full app when authenticated it is this URL but when not auth'd it's actually the ServerVariables address
// so based on compat and how things are currently working we need to replace the serverVarsJs one
((Dictionary<string, object>)defaults["umbracoUrls"])["serverVarsJs"] = _linkGenerator.GetPathByAction("ServerVariables", "BackOffice");
((Dictionary<string, object>)defaults["umbracoUrls"])["serverVarsJs"]
= _linkGenerator.GetPathByAction(
nameof(BackOfficeController.ServerVariables),
ControllerExtensions.GetControllerName<BackOfficeController>(),
new { area = Constants.Web.Mvc.BackOfficeArea });
return defaults;
}
@@ -126,6 +128,7 @@ namespace Umbraco.Web.BackOffice.Controllers
internal async Task<Dictionary<string, object>> GetServerVariablesAsync()
{
var globalSettings = _globalSettings;
var backOfficeControllerName = ControllerExtensions.GetControllerName<BackOfficeController>();
var defaultVals = new Dictionary<string, object>
{
{
@@ -136,211 +139,211 @@ namespace Umbraco.Web.BackOffice.Controllers
// having each url defined here explicitly - we can do that in v8! for now
// for umbraco services we'll stick to explicitly defining the endpoints.
{"externalLoginsUrl", _linkGenerator.GetPathByAction("ExternalLogin", "BackOffice")},
{"externalLinkLoginsUrl", _linkGenerator.GetPathByAction("LinkLogin", "BackOffice")},
{"gridConfig", _linkGenerator.GetPathByAction("GetGridConfig", "BackOffice")},
//{"externalLoginsUrl", _linkGenerator.GetPathByAction(nameof(BackOfficeController.ExternalLogin), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })},
//{"externalLinkLoginsUrl", _linkGenerator.GetPathByAction(nameof(BackOfficeController.LinkLogin), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })},
{"gridConfig", _linkGenerator.GetPathByAction(nameof(BackOfficeController.GetGridConfig), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })},
// TODO: This is ultra confusing! this same key is used for different things, when returning the full app when authenticated it is this URL but when not auth'd it's actually the ServerVariables address
{"serverVarsJs", _linkGenerator.GetPathByAction("Application", "BackOffice")},
{"serverVarsJs", _linkGenerator.GetPathByAction(nameof(BackOfficeController.Application), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })},
//API URLs
{
"packagesRestApiBaseUrl", Constants.PackageRepository.RestApiBaseUrl
},
//{
// "redirectUrlManagementApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<RedirectUrlManagementController>(
// _apiControllers, controller => controller.GetEnableState())
//},
//{
// "tourApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TourController>(
// _apiControllers, controller => controller.GetTours())
// controller => controller.GetEnableState())
//},
{
"tourApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<TourController>(
controller => controller.GetTours())
},
//{
// "embedApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<RteEmbedController>(
// _apiControllers, controller => controller.GetEmbed("", 0, 0))
// controller => controller.GetEmbed("", 0, 0))
//},
//{
// "userApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<UsersController>(
// _apiControllers, controller => controller.PostSaveUser(null))
// controller => controller.PostSaveUser(null))
//},
//{
// "userGroupsApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<UserGroupsController>(
// _apiControllers, controller => controller.PostSaveUserGroup(null))
// controller => controller.PostSaveUserGroup(null))
//},
//{
// "contentApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ContentController>(
// _apiControllers, controller => controller.PostSave(null))
// controller => controller.PostSave(null))
//},
//{
// "mediaApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MediaController>(
// _apiControllers, controller => controller.GetRootMedia())
//},
//{
// "imagesApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ImagesController>(
// _apiControllers, controller => controller.GetBigThumbnail(""))
// controller => controller.GetRootMedia())
//},
{
"imagesApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<ImagesController>(
controller => controller.GetBigThumbnail(""))
},
//{
// "sectionApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<SectionController>(
// _apiControllers, controller => controller.GetSections())
// controller => controller.GetSections())
//},
//{
// "treeApplicationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ApplicationTreeController>(
// _apiControllers, controller => controller.GetApplicationTrees(null, null, null, TreeUse.None))
// controller => controller.GetApplicationTrees(null, null, null, TreeUse.None))
//},
//{
// "contentTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ContentTypeController>(
// _apiControllers, controller => controller.GetAllowedChildren(0))
// controller => controller.GetAllowedChildren(0))
//},
//{
// "mediaTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MediaTypeController>(
// _apiControllers, controller => controller.GetAllowedChildren(0))
// controller => controller.GetAllowedChildren(0))
//},
//{
// "macroRenderingApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MacroRenderingController>(
// _apiControllers, controller => controller.GetMacroParameters(0))
// controller => controller.GetMacroParameters(0))
//},
//{
// "macroApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MacrosController>(
// _apiControllers, controller => controller.Create(null))
//},
//{
// "authenticationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<AuthenticationController>(
// _apiControllers, controller => controller.PostLogin(null))
// controller => controller.Create(null))
//},
{
"authenticationApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<AuthenticationController>(
controller => controller.PostLogin(null))
},
//{
// "currentUserApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<CurrentUserController>(
// _apiControllers, controller => controller.PostChangePassword(null))
// controller => controller.PostChangePassword(null))
//},
//{
// "entityApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<EntityController>(
// _apiControllers, controller => controller.GetById(0, UmbracoEntityTypes.Media))
// controller => controller.GetById(0, UmbracoEntityTypes.Media))
//},
//{
// "dataTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<DataTypeController>(
// _apiControllers, controller => controller.GetById(0))
//},
//{
// "dashboardApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<DashboardController>(
// _apiControllers, controller => controller.GetDashboard(null))
// controller => controller.GetById(0))
//},
{
"dashboardApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<DashboardController>(
controller => controller.GetDashboard(null))
},
//{
// "logApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<LogController>(
// _apiControllers, controller => controller.GetPagedEntityLog(0, 0, 0, Direction.Ascending, null))
// controller => controller.GetPagedEntityLog(0, 0, 0, Direction.Ascending, null))
//},
//{
// "memberApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MemberController>(
// _apiControllers, controller => controller.GetByKey(Guid.Empty))
// controller => controller.GetByKey(Guid.Empty))
//},
//{
// "packageInstallApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<PackageInstallController>(
// _apiControllers, controller => controller.Fetch(string.Empty))
// controller => controller.Fetch(string.Empty))
//},
//{
// "packageApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<PackageController>(
// _apiControllers, controller => controller.GetCreatedPackages())
// controller => controller.GetCreatedPackages())
//},
//{
// "relationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<RelationController>(
// _apiControllers, controller => controller.GetById(0))
// controller => controller.GetById(0))
//},
//{
// "rteApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<RichTextPreValueController>(
// _apiControllers, controller => controller.GetConfiguration())
// controller => controller.GetConfiguration())
//},
//{
// "stylesheetApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<StylesheetController>(
// _apiControllers, controller => controller.GetAll())
// controller => controller.GetAll())
//},
//{
// "memberTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MemberTypeController>(
// _apiControllers, controller => controller.GetAllTypes())
// controller => controller.GetAllTypes())
//},
//{
// "memberGroupApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MemberGroupController>(
// _apiControllers, controller => controller.GetAllGroups())
// controller => controller.GetAllGroups())
//},
//{
// "updateCheckApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<UpdateCheckController>(
// _apiControllers, controller => controller.GetCheck())
// controller => controller.GetCheck())
//},
//{
// "templateApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TemplateController>(
// _apiControllers, controller => controller.GetById(0))
// controller => controller.GetById(0))
//},
//{
// "memberTreeBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MemberTreeController>(
// _apiControllers, controller => controller.GetNodes("-1", null))
// controller => controller.GetNodes("-1", null))
//},
//{
// "mediaTreeBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MediaTreeController>(
// _apiControllers, controller => controller.GetNodes("-1", null))
// controller => controller.GetNodes("-1", null))
//},
//{
// "contentTreeBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ContentTreeController>(
// _apiControllers, controller => controller.GetNodes("-1", null))
// controller => controller.GetNodes("-1", null))
//},
//{
// "tagsDataBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TagsDataController>(
// _apiControllers, controller => controller.GetTags("", "", null))
//},
//{
// "examineMgmtBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ExamineManagementController>(
// _apiControllers, controller => controller.GetIndexerDetails())
// controller => controller.GetTags("", "", null))
//},
{
"examineMgmtBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<ExamineManagementController>(
controller => controller.GetIndexerDetails())
},
//{
// "healthCheckBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<HealthCheckController>(
// _apiControllers, controller => controller.GetAllHealthChecks())
// controller => controller.GetAllHealthChecks())
//},
//{
// "templateQueryApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TemplateQueryController>(
// _apiControllers, controller => controller.PostTemplateQuery(null))
// controller => controller.PostTemplateQuery(null))
//},
//{
// "codeFileApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<CodeFileController>(
// _apiControllers, controller => controller.GetByPath("", ""))
// controller => controller.GetByPath("", ""))
//},
//{
// "publishedStatusBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<PublishedStatusController>(
// _apiControllers, controller => controller.GetPublishedStatusUrl())
// controller => controller.GetPublishedStatusUrl())
//},
//{
// "dictionaryApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<DictionaryController>(
// _apiControllers, controller => controller.DeleteById(int.MaxValue))
// controller => controller.DeleteById(int.MaxValue))
//},
//{
// "publishedSnapshotCacheStatusBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<PublishedSnapshotCacheStatusController>(
// _apiControllers, controller => controller.GetStatus())
//},
//{
// "helpApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<HelpController>(
// _apiControllers, controller => controller.GetContextHelpForPage("","",""))
// controller => controller.GetStatus())
//},
{
"helpApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<HelpController>(
controller => controller.GetContextHelpForPage("","",""))
},
//{
// "backOfficeAssetsApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<BackOfficeAssetsController>(
// _apiControllers, controller => controller.GetSupportedLocales())
// controller => controller.GetSupportedLocales())
//},
//{
// "languageApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<LanguageController>(
// _apiControllers, controller => controller.GetAllLanguages())
// controller => controller.GetAllLanguages())
//},
//{
// "relationTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<RelationTypeController>(
// _apiControllers, controller => controller.GetById(1))
// controller => controller.GetById(1))
//},
//{
// "logViewerApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<LogViewerController>(
// _apiControllers, controller => controller.GetNumberOfErrors(null, null))
// controller => controller.GetNumberOfErrors(null, null))
//},
//{
// "webProfilingBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<WebProfilingController>(
// _apiControllers, controller => controller.GetStatus())
// controller => controller.GetStatus())
//},
//{
// "tinyMceApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TinyMceController>(
// _apiControllers, controller => controller.UploadImage())
//},
//{
// "imageUrlGeneratorApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ImageUrlGeneratorController>(
// _apiControllers, controller => controller.GetCropUrl(null, null, null, null, null))
// controller => controller.UploadImage())
//},
{
"imageUrlGeneratorApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<ImageUrlGeneratorController>(
controller => controller.GetCropUrl(null, null, null, null, null))
},
}
},
{
@@ -394,6 +397,8 @@ namespace Umbraco.Web.BackOffice.Controllers
"externalLogins", new Dictionary<string, object>
{
{
//TODO: I think this should be: _signInManager.GetExternalAuthenticationSchemesAsync() or however that works
"providers", (await _authenticationSchemeProvider.GetAllSchemesAsync())
// TODO: We need to filter only back office enabled schemes.
// Before we used to have a property bag to check, now we don't so need to investigate the easiest/best

View File

@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using Umbraco.Core;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http;
namespace Umbraco.Extensions
{
/// <summary>
/// A helper class to deal with csrf prevention with angularjs and webapi
/// </summary>
public static class AngularAntiForgeryExtensions
{
/// <summary>
/// Returns 2 tokens - one for the cookie value and one that angular should set as the header value
/// </summary>
/// <param name="cookieToken"></param>
/// <param name="headerToken"></param>
/// <remarks>
/// .Net provides us a way to validate one token with another for added security. With the way angular works, this
/// means that we need to set 2 cookies since angular uses one cookie value to create the header value, then we want to validate
/// this header value against our original cookie value.
/// </remarks>
public static void GetTokens(this IAntiforgery antiforgery, HttpContext httpContext, out string cookieToken, out string headerToken)
{
var result = antiforgery.GetTokens(httpContext);
cookieToken = result.CookieToken;
headerToken = result.RequestToken;
}
///// <summary>
///// Validates the header token against the validation cookie value
///// </summary>
///// <param name="cookieToken"></param>
///// <param name="headerToken"></param>
///// <returns></returns>
//public static bool ValidateTokens(this IAntiforgery antiforgery, HttpContext httpContext, string cookieToken, string headerToken)
//{
// // ensure that the cookie matches the header and then ensure it matches the correct value!
// try
// {
// antiforgery.Va .Validate(cookieToken, headerToken);
// }
// catch (Exception ex)
// {
// Current.Logger.Error(typeof(AngularAntiForgeryHelper), ex, "Could not validate XSRF token");
// return false;
// }
// return true;
//}
//internal static bool ValidateHeaders(
// KeyValuePair<string, IEnumerable<string>>[] requestHeaders,
// string cookieToken,
// out string failedReason)
//{
// failedReason = "";
// if (requestHeaders.Any(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername)) == false)
// {
// failedReason = "Missing token";
// return false;
// }
// var headerToken = requestHeaders
// .Where(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername))
// .Select(z => z.Value)
// .SelectMany(z => z)
// .FirstOrDefault();
// // both header and cookie must be there
// if (cookieToken == null || headerToken == null)
// {
// failedReason = "Missing token null";
// return false;
// }
// if (ValidateTokens(cookieToken, headerToken) == false)
// {
// failedReason = "Invalid token";
// return false;
// }
// return true;
//}
///// <summary>
///// Validates the headers/cookies passed in for the request
///// </summary>
///// <param name="requestHeaders"></param>
///// <param name="failedReason"></param>
///// <returns></returns>
//public static bool ValidateHeaders(HttpRequestHeaders requestHeaders, out string failedReason)
//{
// var cookieToken = requestHeaders.GetCookieValue(Constants.Web.CsrfValidationCookieName);
// return ValidateHeaders(
// requestHeaders.ToDictionary(x => x.Key, x => x.Value).ToArray(),
// cookieToken == null ? null : cookieToken,
// out failedReason);
//}
}
}

View File

@@ -65,6 +65,7 @@ namespace Umbraco.Extensions
IAuthenticationSchemeProvider authenticationSchemeProvider,
IEnumerable<string> externalLoginErrors)
{
//TODO: I think this should be: _signInManager.GetExternalAuthenticationSchemesAsync() or however that works
var providers = await authenticationSchemeProvider.GetAllSchemesAsync();
var loginProviders = providers

View File

@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using SixLabors.ImageSharp.Web.DependencyInjection;
using Umbraco.Web.BackOffice.Routing;
using Microsoft.AspNetCore.Builder;
namespace Umbraco.Extensions
{
@@ -20,6 +21,8 @@ namespace Umbraco.Extensions
backOfficeRoutes.CreateRoutes(endpoints);
});
app.UseAuthentication();
app.UseUmbracoRuntimeMinification();
// Important we handle image manipulations before the static files, otherwise the querystring is just ignored.

View File

@@ -0,0 +1,76 @@
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Configuration;
namespace Umbraco.Extensions
{
/// <summary>
/// A filter to set the csrf cookie token based on angular conventions
/// </summary>
public sealed class SetAngularAntiForgeryTokens : IAsyncActionFilter
{
private readonly IAntiforgery _antiforgery;
private readonly IGlobalSettings _globalSettings;
public SetAngularAntiForgeryTokens(IAntiforgery antiforgery, IGlobalSettings globalSettings)
{
_antiforgery = antiforgery;
_globalSettings = globalSettings;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (context.HttpContext.Response != null)
{
//DO not set the token cookies if the request has failed!!
if (context.HttpContext.Response.StatusCode == (int)HttpStatusCode.OK)
{
//don't need to set the cookie if they already exist and they are valid
if (context.HttpContext.Request.Cookies.TryGetValue(Constants.Web.AngularCookieName, out var angularCookieVal)
&& context.HttpContext.Request.Cookies.TryGetValue(Constants.Web.CsrfValidationCookieName, out var csrfCookieVal))
{
//if they are not valid for some strange reason - we need to continue setting valid ones
if (await _antiforgery.IsRequestValidAsync(context.HttpContext))
{
await next();
return;
}
}
string cookieToken, headerToken;
_antiforgery.GetTokens(context.HttpContext, out cookieToken, out headerToken);
//We need to set 2 cookies: one is the cookie value that angular will use to set a header value on each request,
// the 2nd is the validation value generated by the anti-forgery helper that we use to validate the header token against.
context.HttpContext.Response.Cookies.Append(
Constants.Web.AngularCookieName, headerToken,
new Microsoft.AspNetCore.Http.CookieOptions
{
Path = "/",
//must be js readable
HttpOnly = false,
Secure = _globalSettings.UseHttps
});
context.HttpContext.Response.Cookies.Append(
Constants.Web.CsrfValidationCookieName, cookieToken,
new Microsoft.AspNetCore.Http.CookieOptions
{
Path = "/",
HttpOnly = true,
Secure = _globalSettings.UseHttps
});
}
}
await next();
}
}
}

View File

@@ -6,6 +6,7 @@ using Umbraco.Core.Composing;
using Umbraco.Extensions;
using Umbraco.Web.BackOffice.Controllers;
using Umbraco.Web.BackOffice.Routing;
using Umbraco.Web.BackOffice.Security;
namespace Umbraco.Web.BackOffice.Runtime
{
@@ -15,7 +16,7 @@ namespace Umbraco.Web.BackOffice.Runtime
{
composition.RegisterUnique<BackOfficeAreaRoutes>();
composition.RegisterUnique<BackOfficeServerVariables>();
composition.Register<SignInManager<BackOfficeIdentityUser>>(Lifetime.Scope);
composition.Register<BackOfficeSignInManager>(Lifetime.Scope);
}
}
}

View File

@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core.BackOffice;
namespace Umbraco.Web.BackOffice.Security
{
public class BackOfficeSignInManager : SignInManager<BackOfficeIdentityUser>
{
public BackOfficeSignInManager(
UserManager<BackOfficeIdentityUser> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<BackOfficeIdentityUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<BackOfficeIdentityUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<BackOfficeIdentityUser> confirmation)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
}
}
}

View File

@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using Umbraco.Core.BackOffice;
namespace Umbraco.Extensions
{
public static class HttpContextExtensions
{
public static void SetPrincipalForRequest(this HttpContext context, ClaimsPrincipal principal)
{
context.User = principal;
}
/// <summary>
/// This will return the current back office identity.
/// </summary>
/// <param name="http"></param>
/// <returns>
/// Returns the current back office identity if an admin is authenticated otherwise null
/// </returns>
public static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContext http)
{
if (http == null) throw new ArgumentNullException(nameof(http));
if (http.User == null) return null; //there's no user at all so no identity
// If it's already a UmbracoBackOfficeIdentity
var backOfficeIdentity = http.User.GetUmbracoIdentity();
if (backOfficeIdentity != null) return backOfficeIdentity;
return null;
}
}
}

View File

@@ -4,6 +4,8 @@ using Microsoft.AspNetCore.Routing;
using System.Reflection;
using Umbraco.Web.Common.Install;
using Umbraco.Core.Hosting;
using System.Linq.Expressions;
using Umbraco.Web.Common.Controllers;
namespace Umbraco.Extensions
{
@@ -40,5 +42,98 @@ namespace Umbraco.Extensions
{
return linkGenerator.GetPathByAction(nameof(InstallController.Index), ControllerExtensions.GetControllerName<InstallController>(), new { area = Constants.Web.Mvc.InstallArea });
}
/// <summary>
/// Return the Url for a Web Api service
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="actionName"></param>
/// <param name="id"></param>
/// <returns></returns>
public static string GetUmbracoApiService<T>(this LinkGenerator linkGenerator, string actionName, object id = null)
where T : UmbracoApiControllerBase
{
return linkGenerator.GetUmbracoApiService(actionName, typeof(T), id);
}
public static string GetUmbracoApiServiceBaseUrl<T>(this LinkGenerator linkGenerator, Expression<Func<T, object>> methodSelector)
where T : UmbracoApiControllerBase
{
var method = ExpressionHelper.GetMethodInfo(methodSelector);
if (method == null)
{
throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) + " or the result ");
}
return linkGenerator.GetUmbracoApiService<T>(method.Name).TrimEnd(method.Name);
}
/// <summary>
/// Return the Url for a Web Api service
/// </summary>
/// <param name="url"></param>
/// <param name="actionName"></param>
/// <param name="controllerName"></param>
/// <param name="area"></param>
/// <param name="id"></param>
/// <returns></returns>
public static string GetUmbracoApiService(this LinkGenerator linkGenerator, string actionName, string controllerName, string area, object id = null)
{
if (actionName == null) throw new ArgumentNullException(nameof(actionName));
if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName));
if (controllerName == null) throw new ArgumentNullException(nameof(controllerName));
if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(controllerName));
if (area.IsNullOrWhiteSpace())
{
if (id == null)
{
return linkGenerator.GetPathByAction(actionName, controllerName);
}
else
{
return linkGenerator.GetPathByAction(actionName, controllerName, new { id = id });
}
}
else
{
if (id == null)
{
return linkGenerator.GetPathByAction(actionName, controllerName, new { area = area });
}
else
{
return linkGenerator.GetPathByAction(actionName, controllerName, new { area = area, id = id });
}
}
}
/// <summary>
/// Return the Url for a Web Api service
/// </summary>
/// <param name="url"></param>
/// <param name="actionName"></param>
/// <param name="apiControllerType"></param>
/// <param name="id"></param>
/// <returns></returns>
public static string GetUmbracoApiService(this LinkGenerator linkGenerator, string actionName, Type apiControllerType, object id = null)
{
if (actionName == null) throw new ArgumentNullException(nameof(actionName));
if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName));
if (apiControllerType == null) throw new ArgumentNullException(nameof(apiControllerType));
var area = "";
if (!typeof(UmbracoApiControllerBase).IsAssignableFrom(apiControllerType))
throw new InvalidOperationException($"The controller {apiControllerType} is of type {typeof(UmbracoApiControllerBase)}");
var metaData = PluginController.GetMetadata(apiControllerType);
if (metaData.AreaName.IsNullOrWhiteSpace() == false)
{
//set the area to the plugin area
area = metaData.AreaName;
}
return linkGenerator.GetUmbracoApiService(actionName, ControllerExtensions.GetControllerName(apiControllerType), area, id);
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Security;
using System.Text;
using Microsoft.AspNetCore.Http;
using Umbraco.Composing;
@@ -8,6 +9,7 @@ using Umbraco.Core.Configuration;
using Umbraco.Core.Hosting;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Extensions;
using Umbraco.Web.Security;
namespace Umbraco.Web.Common.Security
@@ -17,10 +19,16 @@ namespace Umbraco.Web.Common.Security
public class WebSecurity : IWebSecurity
{
private readonly IUserService _userService;
private readonly IGlobalSettings _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IHttpContextAccessor _httpContextAccessor;
public WebSecurity(IUserService userService)
public WebSecurity(IUserService userService, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IHttpContextAccessor httpContextAccessor)
{
_userService = userService;
_globalSettings = globalSettings;
_hostingEnvironment = hostingEnvironment;
_httpContextAccessor = httpContextAccessor;
}
private IUser _currentUser;
@@ -46,7 +54,13 @@ namespace Umbraco.Web.Common.Security
public ValidateRequestAttempt AuthorizeRequest(bool throwExceptions = false)
{
return ValidateRequestAttempt.Success;
// check for secure connection
if (_globalSettings.UseHttps && !_httpContextAccessor.GetRequiredHttpContext().Request.IsHttps)
{
if (throwExceptions) throw new SecurityException("This installation requires a secure connection (via SSL). Please update the URL to include https://");
return ValidateRequestAttempt.FailedNoSsl;
}
return ValidateCurrentUser(throwExceptions);
}
public void ClearCurrentLogin()
@@ -61,7 +75,8 @@ namespace Umbraco.Web.Common.Security
public bool IsAuthenticated()
{
return true;
var httpContext = _httpContextAccessor.HttpContext;
return httpContext?.User != null && httpContext.User.Identity.IsAuthenticated && httpContext.GetCurrentIdentity() != null;
}
public double PerformLogin(int userId)
@@ -81,7 +96,31 @@ namespace Umbraco.Web.Common.Security
public ValidateRequestAttempt ValidateCurrentUser(bool throwExceptions, bool requiresApproval = true)
{
//This will first check if the current user is already authenticated - which should be the case in nearly all circumstances
// since the authentication happens in the Module, that authentication also checks the ticket expiry. We don't
// need to check it a second time because that requires another decryption phase and nothing can tamper with it during the request.
if (IsAuthenticated() == false)
{
//There is no user
if (throwExceptions) throw new InvalidOperationException("The user has no umbraco contextid - try logging in");
return ValidateRequestAttempt.FailedNoContextId;
}
var user = CurrentUser;
// Check for console access
if (user == null || (requiresApproval && user.IsApproved == false) || (user.IsLockedOut && RequestIsInUmbracoApplication(_httpContextAccessor, _globalSettings, _hostingEnvironment)))
{
if (throwExceptions) throw new ArgumentException("You have no privileges to the umbraco console. Please contact your administrator");
return ValidateRequestAttempt.FailedNoPrivileges;
}
return ValidateRequestAttempt.Success;
}
private static bool RequestIsInUmbracoApplication(IHttpContextAccessor httpContextAccessor, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment)
{
return httpContextAccessor.GetRequiredHttpContext().Request.Path.ToString().IndexOf(hostingEnvironment.ToAbsolute(globalSettings.UmbracoPath), StringComparison.InvariantCultureIgnoreCase) > -1;
}
}
}

View File

@@ -72,7 +72,7 @@ namespace Umbraco.Web
_variationContextAccessor.VariationContext = new VariationContext(_defaultCultureAccessor.DefaultCulture);
}
IWebSecurity webSecurity = new WebSecurity(_userService);
IWebSecurity webSecurity = new WebSecurity(_userService, _globalSettings, _hostingEnvironment, _httpContextAccessor);
return new UmbracoContext(
_publishedSnapshotService,

View File

@@ -156,20 +156,6 @@ namespace Umbraco.Web.Editors
}
}
/// <summary>
/// Checks if the current user's cookie is valid and if so returns OK or a 400 (BadRequest)
/// </summary>
/// <returns></returns>
[System.Web.Http.HttpGet]
public bool IsAuthenticated()
{
var attempt = UmbracoContext.Security.AuthorizeRequest();
if (attempt == ValidateRequestAttempt.Success)
{
return true;
}
return false;
}
/// <summary>
/// Returns the currently logged in Umbraco user
@@ -237,74 +223,6 @@ namespace Umbraco.Web.Editors
return identityUser.Logins.ToDictionary(x => x.LoginProvider, x => x.ProviderKey);
}
/// <summary>
/// Logs a user in
/// </summary>
/// <returns></returns>
[SetAngularAntiForgeryTokens]
public async Task<HttpResponseMessage> PostLogin(LoginModel loginModel)
{
var http = EnsureHttpContext();
var owinContext = TryGetOwinContext().Result;
// Sign the user in with username/password, this also gives a chance for developers to
// custom verify the credentials and auto-link user accounts with a custom IBackOfficePasswordChecker
var result = await SignInManager.PasswordSignInAsync(
loginModel.Username, loginModel.Password, isPersistent: true, shouldLockout: true);
if (result.Succeeded)
{
// get the user
var user = Services.UserService.GetByUsername(loginModel.Username);
UserManager.RaiseLoginSuccessEvent(User, user.Id);
return SetPrincipalAndReturnUserDetail(user, owinContext.Request.User);
}
if (result.RequiresTwoFactor)
{
var twofactorOptions = UserManager as IUmbracoBackOfficeTwoFactorOptions;
if (twofactorOptions == null)
{
throw new HttpResponseException(
Request.CreateErrorResponse(
HttpStatusCode.BadRequest,
"UserManager does not implement " + typeof(IUmbracoBackOfficeTwoFactorOptions)));
}
var twofactorView = twofactorOptions.GetTwoFactorView(
owinContext,
UmbracoContext,
loginModel.Username);
if (twofactorView.IsNullOrWhiteSpace())
{
throw new HttpResponseException(
Request.CreateErrorResponse(
HttpStatusCode.BadRequest,
typeof(IUmbracoBackOfficeTwoFactorOptions) + ".GetTwoFactorView returned an empty string"));
}
var attemptedUser = Services.UserService.GetByUsername(loginModel.Username);
// create a with information to display a custom two factor send code view
var verifyResponse = Request.CreateResponse(HttpStatusCode.PaymentRequired, new
{
twoFactorView = twofactorView,
userId = attemptedUser.Id
});
UserManager.RaiseLoginRequiresVerificationEvent(User, attemptedUser.Id);
return verifyResponse;
}
// return BadRequest (400), we don't want to return a 401 because that get's intercepted
// by our angular helper because it thinks that we need to re-perform the request once we are
// authorized and we don't want to return a 403 because angular will show a warning message indicating
// that the user doesn't have access to perform this function, we just want to return a normal invalid message.
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
/// <summary>
/// Processes a password reset request. Looks for a match on the provided email address
@@ -534,7 +452,7 @@ namespace Umbraco.Web.Editors
var userDetail = Mapper.Map<UserDetail>(user);
// update the userDetail and set their remaining seconds
userDetail.SecondsUntilTimeout = TimeSpan.FromMinutes(GlobalSettings.TimeOutInMinutes).TotalSeconds;
// create a response with the userDetail object
var response = Request.CreateResponse(HttpStatusCode.OK, userDetail);

View File

@@ -197,10 +197,7 @@ namespace Umbraco.Web.Editors
"macroApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MacrosController>(
controller => controller.Create(null))
},
{
"authenticationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<AuthenticationController>(
controller => controller.PostLogin(null))
},
{
"currentUserApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<CurrentUserController>(
controller => controller.PostChangePassword(null))