Merge remote-tracking branch 'origin/netcore/netcore' into netcore/task/6666-auth-policies

# Conflicts:
#	src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs
#	src/Umbraco.Web.Common/Filters/UmbracoMemberAuthorizeFilter.cs
This commit is contained in:
Shannon
2020-11-24 00:46:38 +11:00
50 changed files with 699 additions and 368 deletions

View File

@@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
namespace Umbraco.Web.Models
namespace Umbraco.Core.Models.Security
{
public class LoginModel : PostRedirectModel
{
@@ -11,7 +11,7 @@ namespace Umbraco.Web.Models
[Required]
[DataMember(Name = "password", IsRequired = true)]
[StringLength(maximumLength:256)]
[StringLength(maximumLength: 256)]
public string Password { get; set; }
}

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Web.Models
namespace Umbraco.Core.Models.Security
{
/// <summary>
/// A base model containing a value to indicate to Umbraco where to redirect to after Posting if

View File

@@ -2,36 +2,15 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Current = Umbraco.Web.Composing.Current;
using Umbraco.Web.Models;
namespace Umbraco.Web.Models
namespace Umbraco.Core.Models.Security
{
/// <summary>
/// A readonly member profile model
/// </summary>
[ModelBinder(typeof(ProfileModelBinder))]
public class ProfileModel : PostRedirectModel
{
public static ProfileModel CreateModel()
{
var model = new ProfileModel(false);
return model;
}
private ProfileModel(bool doLookup)
{
MemberProperties = new List<UmbracoProperty>();
if (doLookup && Current.UmbracoContext != null)
{
var helper = Current.MembershipHelper;
var model = helper.GetCurrentMemberProfileModel();
MemberProperties = model.MemberProperties;
}
}
[Required]
[RegularExpression(@"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?",
ErrorMessage = "Please enter a valid e-mail address")]
@@ -81,18 +60,6 @@ namespace Umbraco.Web.Models
/// <remarks>
/// Adding items to this list on the front-end will not add properties to the member in the database.
/// </remarks>
public List<UmbracoProperty> MemberProperties { get; set; }
/// <summary>
/// A custom model binder for MVC because the default ctor performs a lookup!
/// </summary>
internal class ProfileModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
return ProfileModel.CreateModel();
}
}
public List<UmbracoProperty> MemberProperties { get; set; } = new List<UmbracoProperty>();
}
}

View File

@@ -1,12 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Umbraco.Core;
using Umbraco.Web.Models;
namespace Umbraco.Web.Models
namespace Umbraco.Core.Models.Security
{
[ModelBinder(typeof(RegisterModelBinder))]
public class RegisterModel : PostRedirectModel
{
/// <summary>
@@ -27,7 +24,6 @@ namespace Umbraco.Web.Models
CreatePersistentLoginCookie = true;
}
[Required]
[RegularExpression(@"[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\.)+[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?",
ErrorMessage = "Please enter a valid e-mail address")]
@@ -74,16 +70,5 @@ namespace Umbraco.Web.Models
/// Default is true to create a persistent cookie if LoginOnSuccess is true
/// </summary>
public bool CreatePersistentLoginCookie { get; set; }
/// <summary>
/// A custom model binder for MVC because the default ctor performs a lookup!
/// </summary>
internal class RegisterModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
return RegisterModel.CreateModel();
}
}
}
}

View File

@@ -2,10 +2,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Serialization;
namespace Umbraco.Core
namespace Umbraco.Core.PropertyEditors
{
/// <summary>
/// Represents a data type configuration editor.

View File

@@ -25,7 +25,7 @@ namespace Umbraco.Core.PropertyEditors
//TODO: We will need to change this once we support tracking via variants/segments
// for now, we are tracking values from ALL variants
foreach(var propertyVal in p.Values)
foreach (var propertyVal in p.Values)
{
var val = propertyVal.EditedValue;
@@ -33,9 +33,9 @@ namespace Umbraco.Core.PropertyEditors
if (valueEditor is IDataValueReference reference)
{
var refs = reference.GetReferences(val);
foreach(var r in refs)
foreach (var r in refs)
trackedRelations.Add(r);
}
}
// Loop over collection that may be add to existing property editors
// implementation of GetReferences in IDataValueReference.
@@ -48,14 +48,11 @@ namespace Umbraco.Core.PropertyEditors
// in the dataeditor/property has referecnes to media/content items
if (item.IsForEditor(editor))
{
foreach(var r in item.GetDataValueReference().GetReferences(val))
foreach (var r in item.GetDataValueReference().GetReferences(val))
trackedRelations.Add(r);
}
}
}
}
return trackedRelations;

View File

@@ -1,5 +1,4 @@
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.Validators;
namespace Umbraco.Web.PropertyEditors
@@ -7,7 +6,7 @@ namespace Umbraco.Web.PropertyEditors
/// <summary>
/// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored.
/// </summary>
internal class DecimalConfigurationEditor : ConfigurationEditor
public class DecimalConfigurationEditor : ConfigurationEditor
{
public DecimalConfigurationEditor()
{

View File

@@ -13,6 +13,6 @@ namespace Umbraco.Core.PropertyEditors
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
IEnumerable<UmbracoEntityReference> GetReferences(object value);
IEnumerable<UmbracoEntityReference> GetReferences(object value);
}
}

View File

@@ -5,7 +5,7 @@
/// <summary>
/// Gets a value indicating whether the DataValueReference lookup supports a datatype (data editor).
/// </summary>
/// <param name="dataType">The datatype.</param>
/// <param name="dataEditor"></param>
/// <returns>A value indicating whether the converter supports a datatype.</returns>
bool IsForEditor(IDataEditor dataEditor);

View File

@@ -1,5 +1,4 @@
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.Validators;
namespace Umbraco.Web.PropertyEditors
@@ -7,7 +6,7 @@ namespace Umbraco.Web.PropertyEditors
/// <summary>
/// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored.
/// </summary>
internal class IntegerConfigurationEditor : ConfigurationEditor
public class IntegerConfigurationEditor : ConfigurationEditor
{
public IntegerConfigurationEditor()
{

View File

@@ -1,4 +1,4 @@
using Newtonsoft.Json;
using System.Runtime.Serialization;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
@@ -31,7 +31,7 @@ namespace Umbraco.Web.PropertyEditors
new Layout { Name = "grid", Icon = "icon-thumbnails-small", IsSystem = 1, Selected = true, Path = "views/propertyeditors/listview/layouts/grid/grid.html" }
};
IncludeProperties = new []
IncludeProperties = new[]
{
new Property { Alias = "sortOrder", Header = "Sort order", IsSystem = 1 },
new Property { Alias = "updateDate", Header = "Last edited", IsSystem = 1 },
@@ -41,7 +41,7 @@ namespace Umbraco.Web.PropertyEditors
[ConfigurationField("pageSize", "Page Size", "number", Description = "Number of items per page")]
public int PageSize { get; set; }
[ConfigurationField("orderBy", "Order By", "views/propertyeditors/listview/sortby.prevalues.html",
Description = "The default sort order for the list")]
public string OrderBy { get; set; }
@@ -69,54 +69,57 @@ namespace Umbraco.Web.PropertyEditors
[ConfigurationField("showContentFirst", "Show Content App First", "boolean", Description = "Enable this to show the content app by default instead of the list view app")]
public bool ShowContentFirst { get; set; }
[DataContract]
public class Property
{
[JsonProperty("alias")]
[DataMember(Name = "alias")]
public string Alias { get; set; }
[JsonProperty("header")]
[DataMember(Name = "header")]
public string Header { get; set; }
[JsonProperty("nameTemplate")]
[DataMember(Name = "nameTemplate")]
public string Template { get; set; }
[JsonProperty("isSystem")]
[DataMember(Name = "isSystem")]
public int IsSystem { get; set; } // TODO: bool
}
[DataContract]
public class Layout
{
[JsonProperty("name")]
[DataMember(Name = "name")]
public string Name { get; set; }
[JsonProperty("path")]
[DataMember(Name = "path")]
public string Path { get; set; }
[JsonProperty("icon")]
[DataMember(Name = "icon")]
public string Icon { get; set; }
[JsonProperty("isSystem")]
[DataMember(Name = "isSystem")]
public int IsSystem { get; set; } // TODO: bool
[JsonProperty("selected")]
[DataMember(Name = "selected")]
public bool Selected { get; set; }
}
[DataContract]
public class BulkActionPermissionSettings
{
[JsonProperty("allowBulkPublish")]
[DataMember(Name = "allowBulkPublish")]
public bool AllowBulkPublish { get; set; } = true;
[JsonProperty("allowBulkUnpublish")]
[DataMember(Name = "allowBulkUnpublish")]
public bool AllowBulkUnpublish { get; set; } = true;
[JsonProperty("allowBulkCopy")]
[DataMember(Name = "allowBulkCopy")]
public bool AllowBulkCopy { get; set; } = true;
[JsonProperty("allowBulkMove")]
[DataMember(Name = "allowBulkMove")]
public bool AllowBulkMove { get; set; } = true;
[JsonProperty("allowBulkDelete")]
[DataMember(Name = "allowBulkDelete")]
public bool AllowBulkDelete { get; set; } = true;
}
}

View File

@@ -1,5 +1,5 @@
using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{

View File

@@ -0,0 +1,21 @@
using System.Runtime.Serialization;
using Umbraco.Core;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the 'startNode' value for the <see cref="Umbraco.Web.PropertyEditors.MultiNodePickerConfiguration"/>
/// </summary>
[DataContract]
public class MultiNodePickerConfigurationTreeSource
{
[DataMember(Name = "type")]
public string ObjectType { get; set; }
[DataMember(Name = "query")]
public string StartNodeQuery { get; set; }
[DataMember(Name = "id")]
public Udi StartNodeId { get; set; }
}
}

View File

@@ -1,4 +1,4 @@
using Newtonsoft.Json;
using System.Runtime.Serialization;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
@@ -27,15 +27,17 @@ namespace Umbraco.Web.PropertyEditors
[ConfigurationField("hideLabel", "Hide Label", "boolean", Description = "Hide the property label and let the item list span the full width of the editor window.")]
public bool HideLabel { get; set; }
[DataContract]
public class ContentType
{
[JsonProperty("ncAlias")]
[DataMember(Name = "ncAlias")]
public string Alias { get; set; }
[JsonProperty("ncTabAlias")]
[DataMember(Name = "ncTabAlias")]
public string TabAlias { get; set; }
[JsonProperty("nameTemplate")]
[DataMember(Name = "nameTemplate")]
public string Template { get; set; }
}
}

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors

View File

@@ -27,7 +27,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
=> propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.ContentPicker);
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof (IPublishedContent);
=> typeof(IPublishedContent);
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Elements;

View File

@@ -65,9 +65,9 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
if (source is decimal sourceDecimal) return sourceDecimal;
if (source is string sourceDecimalString)
return decimal.TryParse(sourceDecimalString, NumberStyles.Any, CultureInfo.InvariantCulture, out var d) ? d : 0;
if (source is double sourceDouble)
return Convert.ToDecimal(sourceDouble);
return (decimal) 0;
if (source is double sourceDouble)
return Convert.ToDecimal(sourceDouble);
return (decimal)0;
case ValueTypes.Integer:
if (source is int sourceInt) return sourceInt;
if (source is string sourceIntString)
@@ -76,7 +76,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
case ValueTypes.Bigint:
if (source is string sourceLongString)
return long.TryParse(sourceLongString, out var i) ? i : 0;
return (long) 0;
return (long)0;
default: // everything else is a string
return source?.ToString() ?? string.Empty;
}

View File

@@ -6,6 +6,7 @@ using Umbraco.Core;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web.PublishedCache;
namespace Umbraco.Web.PropertyEditors.ValueConverters
{
/// <summary>
@@ -67,7 +68,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
{
var isMultiple = IsMultipleDataType(propertyType.DataType);
var udis = (Udi[]) source;
var udis = (Udi[])source;
var mediaItems = new List<IPublishedContent>();
if (source == null) return isMultiple ? mediaItems : null;

View File

@@ -14,7 +14,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
=> propertyType.EditorAlias == Constants.PropertyEditors.Aliases.TinyMce;
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof (IHtmlEncodedString);
=> typeof(IHtmlEncodedString);
// PropertyCacheLevel.Content is ok here because that converter does not parse {locallink} nor executes macros
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)

View File

@@ -2,10 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
using Umbraco.Web;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;

View File

@@ -1,15 +1,12 @@
using Umbraco.Core.Cache;
using Umbraco.Core.Security;
using Umbraco.Web;
using Umbraco.Web.Security;
namespace Umbraco.Core
namespace Umbraco.Core.Security
{
public class HybridBackofficeSecurityAccessor : HybridAccessorBase<IBackOfficeSecurity>, IBackOfficeSecurityAccessor
{
/// <summary>
/// Initializes a new instance of the <see cref="HybridUmbracoContextAccessor"/> class.
/// Initializes a new instance of the <see cref="HybridBackofficeSecurityAccessor"/> class.
/// </summary>
public HybridBackofficeSecurityAccessor(IRequestCache requestCache)
: base(requestCache)
@@ -19,7 +16,7 @@ namespace Umbraco.Core
protected override string ItemKey => "Umbraco.Web.HybridBackofficeSecurityAccessor";
/// <summary>
/// Gets or sets the <see cref="BackOfficeSecurity"/> object.
/// Gets or sets the <see cref="IBackOfficeSecurity"/> object.
/// </summary>
public IBackOfficeSecurity BackOfficeSecurity
{

View File

@@ -0,0 +1,28 @@
using Umbraco.Core.Cache;
using Umbraco.Web;
namespace Umbraco.Core.Security
{
public class HybridUmbracoWebsiteSecurityAccessor : HybridAccessorBase<IUmbracoWebsiteSecurity>, IUmbracoWebsiteSecurityAccessor
{
/// <summary>
/// Initializes a new instance of the <see cref="HybridUmbracoWebsiteSecurityAccessor"/> class.
/// </summary>
public HybridUmbracoWebsiteSecurityAccessor(IRequestCache requestCache)
: base(requestCache)
{ }
/// <inheritdoc />
protected override string ItemKey => "Umbraco.Web.HybridUmbracoWebsiteSecurityAccessor";
/// <summary>
/// Gets or sets the <see cref="IUmbracoWebsiteSecurity"/> object.
/// </summary>
public IUmbracoWebsiteSecurity WebsiteSecurity
{
get => Value;
set => Value = value;
}
}
}

View File

@@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Umbraco.Core.Models.Security;
namespace Umbraco.Core.Security
{
public interface IUmbracoWebsiteSecurity
{
/// <summary>
/// Registers a new member.
/// </summary>
/// <param name="model">Register member model.</param>
/// <param name="logMemberIn">Flag for whether to log the member in upon successful registration.</param>
/// <returns>Result of registration operation.</returns>
Task<RegisterMemberStatus> RegisterMemberAsync(RegisterModel model, bool logMemberIn = true);
/// <summary>
/// Updates the currently logged in member's profile.
/// </summary>
/// <param name="model">Update member profile model.</param>
/// <returns>Result of update profile operation.</returns>
Task<UpdateMemberProfileResult> UpdateMemberProfileAsync(ProfileModel model);
/// <summary>
/// A helper method to perform the validation and logging in of a member.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="password">The password.</param>
/// <returns>Result of login operation.</returns>
Task<bool> LoginAsync(string username, string password);
/// <summary>
/// Check if a member is logged in
/// </summary>
/// <returns>True if logged in, false if not.</returns>
bool IsLoggedIn();
/// <summary>
/// Logs out the current member.
/// </summary>
Task LogOutAsync();
/// <summary>
/// Checks if the current member is authorized based on the parameters provided.
/// </summary>
/// <param name="allowTypes">Allowed types.</param>
/// <param name="allowGroups">Allowed groups.</param>
/// <param name="allowMembers">Allowed individual members.</param>
/// <returns>True or false if the currently logged in member is authorized</returns>
bool IsMemberAuthorized(
IEnumerable<string> allowTypes = null,
IEnumerable<string> allowGroups = null,
IEnumerable<int> allowMembers = null);
}
}

View File

@@ -0,0 +1,7 @@
namespace Umbraco.Core.Security
{
public interface IUmbracoWebsiteSecurityAccessor
{
IUmbracoWebsiteSecurity WebsiteSecurity { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
namespace Umbraco.Core.Security
{
public enum RegisterMemberStatus
{
Success,
InvalidUserName,
InvalidPassword,
InvalidEmail,
DuplicateUserName,
DuplicateEmail,
Error,
}
}

View File

@@ -0,0 +1,24 @@
namespace Umbraco.Core.Security
{
public class UpdateMemberProfileResult
{
private UpdateMemberProfileResult()
{
}
public UpdateMemberProfileStatus Status { get; private set; }
public string ErrorMessage { get; private set; }
public static UpdateMemberProfileResult Success()
{
return new UpdateMemberProfileResult { Status = UpdateMemberProfileStatus.Success };
}
public static UpdateMemberProfileResult Error(string message)
{
return new UpdateMemberProfileResult { Status = UpdateMemberProfileStatus.Error, ErrorMessage = message };
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Umbraco.Core.Security
{
public enum UpdateMemberProfileStatus
{
Success,
Error,
}
}

View File

@@ -1,21 +0,0 @@
using Newtonsoft.Json;
using Umbraco.Core;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Represents the 'startNode' value for the <see cref="MultiNodePickerConfiguration"/>
/// </summary>
[JsonObject]
public class MultiNodePickerConfigurationTreeSource
{
[JsonProperty("type")]
public string ObjectType {get;set;}
[JsonProperty("query")]
public string StartNodeQuery {get;set;}
[JsonProperty("id")]
public Udi StartNodeId {get;set;}
}
}

View File

@@ -3,17 +3,19 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.BackOffice;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.Security;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Extensions;
@@ -25,11 +27,10 @@ using Umbraco.Web.Common.Controllers;
using Umbraco.Web.Common.Exceptions;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Common.Security;
using Umbraco.Web.Editors.Filters;
using Umbraco.Web.Models;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Security;
using Constants = Umbraco.Core.Constants;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Authorization;
using Umbraco.Web.Common.Authorization;

View File

@@ -250,7 +250,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// <param name="culture">The culture to fetch the URL for</param>
/// <returns>The URL or path to the item</returns>
[DetermineAmbiguousActionByPassingParameters]
public HttpResponseMessage GetUrl(Udi udi, string culture = "*")
public IActionResult GetUrl(Udi udi, string culture = "*")
{
var intId = _entityService.GetId(udi);
if (!intId.Success)
@@ -284,7 +284,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// We are not restricting this with security because there is no sensitive data
/// </remarks>
[DetermineAmbiguousActionByPassingParameters]
public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type, string culture = null)
public IActionResult GetUrl(int id, UmbracoEntityTypes type, string culture = null)
{
culture = culture ?? ClientCulture();
@@ -297,10 +297,7 @@ namespace Umbraco.Web.BackOffice.Controllers
{
returnUrl = foundUrl;
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(returnUrl)
};
return Ok(returnUrl);
}
}
@@ -314,10 +311,7 @@ namespace Umbraco.Web.BackOffice.Controllers
returnUrl = "/" + string.Join("/", ancestors.Select(x => x.Name));
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(returnUrl)
};
return Ok(returnUrl);
}

View File

@@ -157,7 +157,7 @@ namespace Umbraco.Web.BackOffice.Controllers
}
[HttpPost]
public HttpResponseMessage CreatePartialViewMacroWithFile(CreatePartialViewMacroWithFileModel model)
public IActionResult CreatePartialViewMacroWithFile(CreatePartialViewMacroWithFileModel model)
{
if (model == null) throw new ArgumentNullException("model");
if (string.IsNullOrWhiteSpace(model.Filename)) throw new ArgumentException("Filename cannot be null or whitespace", "model.Filename");
@@ -173,7 +173,7 @@ namespace Umbraco.Web.BackOffice.Controllers
};
_macroService.Save(macro); // may throw
return new HttpResponseMessage(HttpStatusCode.OK);
return Ok();
}
public class CreatePartialViewMacroWithFileModel

View File

@@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Mvc.Filters;
using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Core.Security;
using Umbraco.Extensions;
namespace Umbraco.Web.Common.Filters
@@ -13,6 +14,12 @@ namespace Umbraco.Web.Common.Filters
public class UmbracoMemberAuthorizeFilter : IAuthorizationFilter
{
// TODO: Lets revisit this when we get members done and the front-end working and whether it can be replaced or moved to an authz policy
private readonly IUmbracoWebsiteSecurity _websiteSecurity;
public UmbracoMemberAuthorizeFilter(IUmbracoWebsiteSecurity websiteSecurity)
{
_websiteSecurity = websiteSecurity;
}
/// <summary>
/// Comma delimited list of allowed member types
@@ -29,9 +36,7 @@ namespace Umbraco.Web.Common.Filters
/// </summary>
public string AllowMembers { get; private set; }
private UmbracoMemberAuthorizeFilter(
string allowType, string allowGroup, string allowMembers)
private UmbracoMemberAuthorizeFilter(string allowType, string allowGroup, string allowMembers)
{
AllowType = allowType;
AllowGroup = allowGroup;
@@ -50,11 +55,19 @@ namespace Umbraco.Web.Common.Filters
private bool IsAuthorized()
{
if (AllowMembers.IsNullOrWhiteSpace())
AllowMembers = "";
{
AllowMembers = string.Empty;
}
if (AllowGroup.IsNullOrWhiteSpace())
AllowGroup = "";
{
AllowGroup = string.Empty;
}
if (AllowType.IsNullOrWhiteSpace())
AllowType = "";
{
AllowType = string.Empty;
}
var members = new List<int>();
foreach (var s in AllowMembers.Split(','))
@@ -65,7 +78,7 @@ namespace Umbraco.Web.Common.Filters
}
}
return false;// TODO reintroduce when members are implemented: _memberHelper.IsMemberAuthorized(AllowType.Split(','), AllowGroup.Split(','), members);
return _websiteSecurity.IsMemberAuthorized(AllowType.Split(','), AllowGroup.Split(','), members);
}
}
}

View File

@@ -74,9 +74,12 @@ namespace Umbraco.Web.Common.Runtime
// register the umbraco context factory
composition.Services.AddUnique<IUmbracoContextFactory, UmbracoContextFactory>();
composition.Services.AddUnique<IBackOfficeSecurityFactory, BackOfficeSecurityFactory>();
composition.Services.AddUnique<IBackOfficeSecurityAccessor, HybridBackofficeSecurityAccessor>();
composition.Services.AddUnique<IUmbracoWebsiteSecurityAccessor, HybridUmbracoWebsiteSecurityAccessor>();
//register the install components
composition.ComposeInstaller();

View File

@@ -21,7 +21,13 @@ namespace Umbraco.Web.Website.Controllers
// [MergeParentContextViewData]
public abstract class SurfaceController : PluginController
{
private readonly IPublishedUrlProvider _publishedUrlProvider;
protected SurfaceController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger)
{
PublishedUrlProvider = publishedUrlProvider;
}
protected IPublishedUrlProvider PublishedUrlProvider { get; }
/// <summary>
/// Gets the current page.
@@ -39,12 +45,6 @@ namespace Umbraco.Web.Website.Controllers
}
}
protected SurfaceController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger)
{
_publishedUrlProvider = publishedUrlProvider;
}
/// <summary>
/// Redirects to the Umbraco page with the given id
/// </summary>
@@ -52,7 +52,7 @@ namespace Umbraco.Web.Website.Controllers
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(Guid contentKey)
{
return new RedirectToUmbracoPageResult(contentKey, _publishedUrlProvider, UmbracoContextAccessor);
return new RedirectToUmbracoPageResult(contentKey, PublishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
@@ -63,7 +63,7 @@ namespace Umbraco.Web.Website.Controllers
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(Guid contentKey, QueryString queryString)
{
return new RedirectToUmbracoPageResult(contentKey, queryString, _publishedUrlProvider, UmbracoContextAccessor);
return new RedirectToUmbracoPageResult(contentKey, queryString, PublishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
@@ -73,7 +73,7 @@ namespace Umbraco.Web.Website.Controllers
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent)
{
return new RedirectToUmbracoPageResult(publishedContent, _publishedUrlProvider, UmbracoContextAccessor);
return new RedirectToUmbracoPageResult(publishedContent, PublishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
@@ -84,7 +84,7 @@ namespace Umbraco.Web.Website.Controllers
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent, QueryString queryString)
{
return new RedirectToUmbracoPageResult(publishedContent, queryString, _publishedUrlProvider, UmbracoContextAccessor);
return new RedirectToUmbracoPageResult(publishedContent, queryString, PublishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
@@ -93,7 +93,7 @@ namespace Umbraco.Web.Website.Controllers
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage()
{
return new RedirectToUmbracoPageResult(CurrentPage, _publishedUrlProvider, UmbracoContextAccessor);
return new RedirectToUmbracoPageResult(CurrentPage, PublishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
@@ -103,7 +103,7 @@ namespace Umbraco.Web.Website.Controllers
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage(QueryString queryString)
{
return new RedirectToUmbracoPageResult(CurrentPage, queryString, _publishedUrlProvider, UmbracoContextAccessor);
return new RedirectToUmbracoPageResult(CurrentPage, queryString, PublishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>

View File

@@ -0,0 +1,60 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Security;
using Umbraco.Core.Persistence;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Website.Controllers
{
public class UmbLoginController : SurfaceController
{
private readonly IUmbracoWebsiteSecurityAccessor _websiteSecurityAccessor;
public UmbLoginController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory,
ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider,
IUmbracoWebsiteSecurityAccessor websiteSecurityAccessor)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
{
_websiteSecurityAccessor = websiteSecurityAccessor;
}
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateUmbracoFormRouteString]
public async Task<IActionResult> HandleLogin([Bind(Prefix = "loginModel")]LoginModel model)
{
if (ModelState.IsValid == false)
{
return CurrentUmbracoPage();
}
if (await _websiteSecurityAccessor.WebsiteSecurity.LoginAsync(model.Username, model.Password) == false)
{
// Don't add a field level error, just model level.
ModelState.AddModelError("loginModel", "Invalid username or password");
return CurrentUmbracoPage();
}
TempData["LoginSuccess"] = true;
// If there is a specified path to redirect to then use it.
if (model.RedirectUrl.IsNullOrWhiteSpace() == false)
{
// Validate the redirect url.
// If it's not a local url we'll redirect to the root of the current site.
return Redirect(Url.IsLocalUrl(model.RedirectUrl)
? model.RedirectUrl
: CurrentPage.AncestorOrSelf(1).Url(PublishedUrlProvider));
}
// Redirect to current page by default.
return RedirectToCurrentUmbracoPage();
}
}
}

View File

@@ -1,58 +1,54 @@
using System.Web.Mvc;
using System.Web.Security;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Security;
using Umbraco.Core.Persistence;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Security;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Controllers
namespace Umbraco.Web.Website.Controllers
{
[MemberAuthorize]
[UmbracoMemberAuthorize]
public class UmbLoginStatusController : SurfaceController
{
private readonly MembershipHelper _membershipHelper;
public UmbLoginStatusController()
{
}
private readonly IUmbracoWebsiteSecurityAccessor _websiteSecurityAccessor;
public UmbLoginStatusController(IUmbracoContextAccessor umbracoContextAccessor,
IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches,
IProfilingLogger profilingLogger, MembershipHelper membershipHelper)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger)
IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider, IUmbracoWebsiteSecurityAccessor websiteSecurityAccessor)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
{
_membershipHelper = membershipHelper;
_websiteSecurityAccessor = websiteSecurityAccessor;
}
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateUmbracoFormRouteString]
public ActionResult HandleLogout([Bind(Prefix = "logoutModel")]PostRedirectModel model)
public async Task<IActionResult> HandleLogout([Bind(Prefix = "logoutModel")]PostRedirectModel model)
{
if (ModelState.IsValid == false)
{
return CurrentUmbracoPage();
}
if (_membershipHelper.IsLoggedIn())
if (_websiteSecurityAccessor.WebsiteSecurity.IsLoggedIn())
{
FormsAuthentication.SignOut();
await _websiteSecurityAccessor.WebsiteSecurity.LogOutAsync();
}
TempData["LogoutSuccess"] = true;
//if there is a specified path to redirect to then use it
// If there is a specified path to redirect to then use it.
if (model.RedirectUrl.IsNullOrWhiteSpace() == false)
{
return Redirect(model.RedirectUrl);
}
//redirect to current page by default
// Redirect to current page by default.
return RedirectToCurrentUmbracoPage();
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Security;
using Umbraco.Core.Persistence;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Website.Controllers
{
[UmbracoMemberAuthorize]
public class UmbProfileController : SurfaceController
{
private readonly IUmbracoWebsiteSecurityAccessor _websiteSecurityAccessor;
public UmbProfileController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory,
ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger,
IPublishedUrlProvider publishedUrlProvider, IUmbracoWebsiteSecurityAccessor websiteSecurityAccessor)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
{
_websiteSecurityAccessor = websiteSecurityAccessor;
}
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateUmbracoFormRouteString]
public async Task<IActionResult> HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model)
{
if (ModelState.IsValid == false)
{
return CurrentUmbracoPage();
}
var result = await _websiteSecurityAccessor.WebsiteSecurity.UpdateMemberProfileAsync(model);
switch (result.Status)
{
case UpdateMemberProfileStatus.Success:
break;
case UpdateMemberProfileStatus.Error:
// Don't add a field level error, just model level.
ModelState.AddModelError("profileModel", result.ErrorMessage);
return CurrentUmbracoPage();
default:
throw new ArgumentOutOfRangeException();
}
TempData["ProfileUpdateSuccess"] = true;
// If there is a specified path to redirect to then use it.
if (model.RedirectUrl.IsNullOrWhiteSpace() == false)
{
return Redirect(model.RedirectUrl);
}
// Redirect to current page by default.
return RedirectToCurrentUmbracoPage();
}
}
}

View File

@@ -1,37 +1,34 @@
using System;
using System.Web.Mvc;
using System.Web.Security;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Security;
using Umbraco.Core.Persistence;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
using Umbraco.Web.Security;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Controllers
namespace Umbraco.Web.Website.Controllers
{
public class UmbRegisterController : SurfaceController
{
private readonly MembershipHelper _membershipHelper;
public UmbRegisterController()
{
}
private readonly IUmbracoWebsiteSecurityAccessor _websiteSecurityAccessor;
public UmbRegisterController(IUmbracoContextAccessor umbracoContextAccessor,
IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches,
IProfilingLogger profilingLogger, MembershipHelper membershipHelper)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger)
IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider, IUmbracoWebsiteSecurityAccessor websiteSecurityAccessor)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
{
_membershipHelper = membershipHelper;
_websiteSecurityAccessor = websiteSecurityAccessor;
}
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateUmbracoFormRouteString]
public ActionResult HandleRegisterMember([Bind(Prefix = "registerModel")]RegisterModel model)
public async Task<IActionResult> HandleRegisterMember([Bind(Prefix = "registerModel")]RegisterModel model)
{
if (ModelState.IsValid == false)
{
@@ -39,60 +36,52 @@ namespace Umbraco.Web.Controllers
}
// U4-10762 Server error with "Register Member" snippet (Cannot save member with empty name)
// If name field is empty, add the email address instead
// If name field is empty, add the email address instead.
if (string.IsNullOrEmpty(model.Name) && string.IsNullOrEmpty(model.Email) == false)
{
model.Name = model.Email;
}
MembershipCreateStatus status;
var member = _membershipHelper.RegisterMember(model, out status, model.LoginOnSuccess);
var result = await _websiteSecurityAccessor.WebsiteSecurity.RegisterMemberAsync(model, model.LoginOnSuccess);
switch (status)
switch (result)
{
case MembershipCreateStatus.Success:
case RegisterMemberStatus.Success:
TempData["FormSuccess"] = true;
//if there is a specified path to redirect to then use it
// If there is a specified path to redirect to then use it.
if (model.RedirectUrl.IsNullOrWhiteSpace() == false)
{
return Redirect(model.RedirectUrl);
}
//redirect to current page by default
// Redirect to current page by default.
return RedirectToCurrentUmbracoPage();
case MembershipCreateStatus.InvalidUserName:
case RegisterMemberStatus.InvalidUserName:
ModelState.AddModelError((model.UsernameIsEmail || model.Username == null)
? "registerModel.Email"
: "registerModel.Username",
"Username is not valid");
break;
case MembershipCreateStatus.InvalidPassword:
case RegisterMemberStatus.InvalidPassword:
ModelState.AddModelError("registerModel.Password", "The password is not strong enough");
break;
case MembershipCreateStatus.InvalidQuestion:
case MembershipCreateStatus.InvalidAnswer:
// TODO: Support q/a http://issues.umbraco.org/issue/U4-3213
throw new NotImplementedException(status.ToString());
case MembershipCreateStatus.InvalidEmail:
case RegisterMemberStatus.InvalidEmail:
ModelState.AddModelError("registerModel.Email", "Email is invalid");
break;
case MembershipCreateStatus.DuplicateUserName:
case RegisterMemberStatus.DuplicateUserName:
ModelState.AddModelError((model.UsernameIsEmail || model.Username == null)
? "registerModel.Email"
: "registerModel.Username",
"A member with this username already exists.");
break;
case MembershipCreateStatus.DuplicateEmail:
case RegisterMemberStatus.DuplicateEmail:
ModelState.AddModelError("registerModel.Email", "A member with this e-mail address already exists");
break;
case MembershipCreateStatus.UserRejected:
case MembershipCreateStatus.InvalidProviderUserKey:
case MembershipCreateStatus.DuplicateProviderUserKey:
case MembershipCreateStatus.ProviderError:
//don't add a field level error, just model level
ModelState.AddModelError("registerModel", "An error occurred creating the member: " + status);
case RegisterMemberStatus.Error:
// Don't add a field level error, just model level.
ModelState.AddModelError("registerModel", $"An error occurred creating the member: {result}");
break;
default:
throw new ArgumentOutOfRangeException();
@@ -100,6 +89,5 @@ namespace Umbraco.Web.Controllers
return CurrentUmbracoPage();
}
}
}

View File

@@ -5,7 +5,7 @@ using Umbraco.Web.Website.ViewEngines;
namespace Umbraco.Extensions
{
public static class UmbracoWebstiteServiceCollectionExtensions
public static class UmbracoWebsiteServiceCollectionExtensions
{
public static void AddUmbracoWebsite(this IServiceCollection services)
{

View File

@@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Umbraco.Core.Models.Security;
using Umbraco.Core.Security;
namespace Umbraco.Web.Website.Security
{
public class UmbracoWebsiteSecurity : IUmbracoWebsiteSecurity
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UmbracoWebsiteSecurity(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
/// <inheritdoc/>
public Task<RegisterMemberStatus> RegisterMemberAsync(RegisterModel model, bool logMemberIn = true)
{
throw new System.NotImplementedException();
}
/// <inheritdoc/>
public Task<UpdateMemberProfileResult> UpdateMemberProfileAsync(ProfileModel model)
{
throw new System.NotImplementedException();
}
/// <inheritdoc/>
public bool IsLoggedIn()
{
var httpContext = _httpContextAccessor.HttpContext;
return httpContext?.User != null && httpContext.User.Identity.IsAuthenticated;
}
/// <inheritdoc/>
public Task<bool> LoginAsync(string username, string password)
{
throw new System.NotImplementedException();
}
/// <inheritdoc/>
public async Task LogOutAsync()
{
await _httpContextAccessor.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
/// <inheritdoc/>
public bool IsMemberAuthorized(IEnumerable<string> allowTypes = null, IEnumerable<string> allowGroups = null, IEnumerable<int> allowMembers = null)
{
throw new System.NotImplementedException();
}
}
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>

View File

@@ -1,63 +0,0 @@
using System.Web.Mvc;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Services;
using Umbraco.Web.Security;
namespace Umbraco.Web.Controllers
{
public class UmbLoginController : SurfaceController
{
private readonly MembershipHelper _membershipHelper;
public UmbLoginController()
{
}
public UmbLoginController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory,
ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger,
MembershipHelper membershipHelper)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger)
{
_membershipHelper = membershipHelper;
}
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateUmbracoFormRouteString]
public ActionResult HandleLogin([Bind(Prefix = "loginModel")]LoginModel model)
{
if (ModelState.IsValid == false)
{
return CurrentUmbracoPage();
}
if (_membershipHelper.Login(model.Username, model.Password) == false)
{
//don't add a field level error, just model level
ModelState.AddModelError("loginModel", "Invalid username or password");
return CurrentUmbracoPage();
}
TempData["LoginSuccess"] = true;
//if there is a specified path to redirect to then use it
if (model.RedirectUrl.IsNullOrWhiteSpace() == false)
{
// validate the redirect url
// if it's not a local url we'll redirect to the root of the current site
return Redirect(Url.IsLocalUrl(model.RedirectUrl)
? model.RedirectUrl
: CurrentPage.AncestorOrSelf(1).Url());
}
//redirect to current page by default
return RedirectToCurrentUmbracoPage();
}
}
}

View File

@@ -1,61 +0,0 @@
using System;
using System.Web.Mvc;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
using Umbraco.Core.Security;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Services;
using Umbraco.Web.Security;
namespace Umbraco.Web.Controllers
{
[MemberAuthorize]
public class UmbProfileController : SurfaceController
{
private readonly MembershipHelper _membershipHelper;
public UmbProfileController()
{ }
public UmbProfileController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory,
ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger,
MembershipHelper membershipHelper)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger)
{
_membershipHelper = membershipHelper;
}
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateUmbracoFormRouteString]
public ActionResult HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model)
{
if (ModelState.IsValid == false)
{
return CurrentUmbracoPage();
}
var updateAttempt = _membershipHelper.UpdateMemberProfile(model);
if (updateAttempt.Success == false)
{
//don't add a field level error, just model level
ModelState.AddModelError("profileModel", updateAttempt.Exception.Message);
return CurrentUmbracoPage();
}
TempData["ProfileUpdateSuccess"] = true;
//if there is a specified path to redirect to then use it
if (model.RedirectUrl.IsNullOrWhiteSpace() == false)
{
return Redirect(model.RedirectUrl);
}
//redirect to current page by default
return RedirectToCurrentUmbracoPage();
}
}
}

View File

@@ -1,20 +1,20 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web.Security;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Web.Models;
using Umbraco.Web.PublishedCache;
using Umbraco.Core.Cache;
using Umbraco.Web.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Models.Security;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Web.Editors;
using Umbraco.Web.Models;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Security.Providers;
using System.ComponentModel.DataAnnotations;
namespace Umbraco.Web.Security
{
@@ -443,7 +443,7 @@ namespace Umbraco.Web.Security
return null;
}
var model = ProfileModel.CreateModel();
var model = new ProfileModel();
model.Name = member.Name;
model.MemberTypeAlias = member.ContentTypeAlias;
@@ -458,7 +458,6 @@ namespace Umbraco.Web.Security
model.LastActivityDate = membershipUser.LastActivityDate;
model.LastPasswordChangedDate = membershipUser.LastPasswordChangedDate;
var memberType = _memberTypeService.Get(member.ContentTypeId);
var builtIns = ConventionsHelper.GetStandardPropertyTypeStubs(_shortStringHelper).Select(x => x.Key).ToArray();

View File

@@ -142,6 +142,7 @@
<Compile Include="Macros\MemberUserKeyProvider.cs" />
<Compile Include="Mvc\IRenderController.cs" />
<Compile Include="Mvc\IRenderMvcController.cs" />
<Compile Include="Mvc\MemberAuthorizeAttribute.cs" />
<Compile Include="Mvc\RenderMvcController.cs" />
<Compile Include="Mvc\UmbracoViewPageOfTModel.cs" />
<Compile Include="Security\IdentityFactoryMiddleware.cs" />
@@ -220,8 +221,6 @@
<Compile Include="WebAssets\CDF\UmbracoClientDependencyLoader.cs" />
<Compile Include="UmbracoDefaultOwinStartup.cs" />
<Compile Include="Mvc\ProfilingView.cs" />
<Compile Include="Controllers\UmbProfileController.cs" />
<Compile Include="Controllers\UmbLoginStatusController.cs" />
<Compile Include="GridTemplateExtensions.cs" />
<Compile Include="Mvc\NotFoundHandler.cs" />
<Compile Include="Mvc\RedirectToUmbracoUrlResult.cs" />
@@ -236,13 +235,10 @@
<Compile Include="WebApi\AngularJsonOnlyConfigurationAttribute.cs" />
<Compile Include="WebApi\Filters\AngularAntiForgeryHelper.cs" />
<Compile Include="WebApi\Filters\ValidateAngularAntiForgeryTokenAttribute.cs" />
<Compile Include="Controllers\UmbRegisterController.cs" />
<Compile Include="Models\ProfileModel.cs" />
<Compile Include="Models\LoginStatusModel.cs" />
<Compile Include="PublishedPropertyExtension.cs" />
<Compile Include="Mvc\MergeParentContextViewDataAttribute.cs" />
<Compile Include="Mvc\ViewDataDictionaryExtensions.cs" />
<Compile Include="Models\RegisterModel.cs" />
<Compile Include="Security\MembershipHelper.cs" />
<Compile Include="HttpCookieExtensions.cs" />
<Compile Include="Security\Providers\MembersMembershipProvider.cs" />
@@ -253,10 +249,8 @@
<Compile Include="Macros\PartialViewMacroPage.cs" />
<Compile Include="Mvc\AreaRegistrationExtensions.cs" />
<Compile Include="Mvc\QueryStringFilterAttribute.cs" />
<Compile Include="Mvc\MemberAuthorizeAttribute.cs" />
<Compile Include="Mvc\ControllerFactoryExtensions.cs" />
<Compile Include="Mvc\SurfaceRouteHandler.cs" />
<Compile Include="Controllers\UmbLoginController.cs" />
<Compile Include="UrlHelperExtensions.cs" />
<Compile Include="UrlHelperRenderExtensions.cs" />
<Compile Include="WebApi\IsBackOfficeAttribute.cs" />

View File

@@ -0,0 +1,205 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29209.152
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{2849E9D4-3B4E-40A3-A309-F3CB4F0E125F}"
ProjectSection(SolutionItems) = preProject
..\build\build-bootstrap.ps1 = ..\build\build-bootstrap.ps1
..\build\build.ps1 = ..\build\build.ps1
..\NuGet.Config = ..\NuGet.Config
SolutionInfo.cs = SolutionInfo.cs
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{FD962632-184C-4005-A5F3-E705D92FC645}"
ProjectSection(SolutionItems) = preProject
..\.github\BUILD.md = ..\.github\BUILD.md
..\.github\CLEAR.md = ..\.github\CLEAR.md
..\.github\CODE_OF_CONDUCT.md = ..\.github\CODE_OF_CONDUCT.md
..\.github\CONTRIBUTING.md = ..\.github\CONTRIBUTING.md
..\.github\CONTRIBUTING_DETAILED.md = ..\.github\CONTRIBUTING_DETAILED.md
..\.github\CONTRIBUTION_GUIDELINES.md = ..\.github\CONTRIBUTION_GUIDELINES.md
..\.github\PULL_REQUEST_TEMPLATE.md = ..\.github\PULL_REQUEST_TEMPLATE.md
..\.github\README.md = ..\.github\README.md
..\.github\REVIEW_PROCESS.md = ..\.github\REVIEW_PROCESS.md
..\.github\V8_GETTING_STARTED.md = ..\.github\V8_GETTING_STARTED.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B5BD12C1-A454-435E-8A46-FF4A364C0382}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuSpecs", "NuSpecs", "{227C3B55-80E5-4E7E-A802-BE16C5128B9D}"
ProjectSection(SolutionItems) = preProject
..\build\NuSpecs\UmbracoCms.Core.nuspec = ..\build\NuSpecs\UmbracoCms.Core.nuspec
..\build\NuSpecs\UmbracoCms.nuspec = ..\build\NuSpecs\UmbracoCms.nuspec
..\build\NuSpecs\UmbracoCms.SqlCe.nuspec = ..\build\NuSpecs\UmbracoCms.SqlCe.nuspec
..\build\NuSpecs\UmbracoCms.Web.nuspec = ..\build\NuSpecs\UmbracoCms.Web.nuspec
EndProjectSection
EndProject
Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Web.UI.Client", "http://localhost:3961", "{3819A550-DCEC-4153-91B4-8BA9F7F0B9B4}"
ProjectSection(WebsiteProperties) = preProject
UseIISExpress = "true"
TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5"
Debug.AspNetCompiler.VirtualPath = "/localhost_3961"
Debug.AspNetCompiler.PhysicalPath = "Umbraco.Web.UI.Client\"
Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_3961\"
Debug.AspNetCompiler.Updateable = "true"
Debug.AspNetCompiler.ForceOverwrite = "true"
Debug.AspNetCompiler.FixedNames = "false"
Debug.AspNetCompiler.Debug = "True"
Release.AspNetCompiler.VirtualPath = "/localhost_3961"
Release.AspNetCompiler.PhysicalPath = "Umbraco.Web.UI.Client\"
Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_3961\"
Release.AspNetCompiler.Updateable = "true"
Release.AspNetCompiler.ForceOverwrite = "true"
Release.AspNetCompiler.FixedNames = "false"
Release.AspNetCompiler.Debug = "False"
SlnRelativePath = "Umbraco.Web.UI.Client\"
DefaultWebSiteLanguage = "Visual C#"
StartServerOnDebug = "false"
EndProjectSection
EndProject
Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Tests.AcceptanceTest", "http://localhost:58896", "{9E4C8A12-FBE0-4673-8CE2-DF99D5D57817}"
ProjectSection(WebsiteProperties) = preProject
UseIISExpress = "true"
TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5"
Debug.AspNetCompiler.VirtualPath = "/localhost_62926"
Debug.AspNetCompiler.PhysicalPath = "Umbraco.Tests.AcceptanceTest\"
Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_62926\"
Debug.AspNetCompiler.Updateable = "true"
Debug.AspNetCompiler.ForceOverwrite = "true"
Debug.AspNetCompiler.FixedNames = "false"
Debug.AspNetCompiler.Debug = "True"
Release.AspNetCompiler.VirtualPath = "/localhost_62926"
Release.AspNetCompiler.PhysicalPath = "Umbraco.Tests.AcceptanceTest\"
Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_62926\"
Release.AspNetCompiler.Updateable = "true"
Release.AspNetCompiler.ForceOverwrite = "true"
Release.AspNetCompiler.FixedNames = "false"
Release.AspNetCompiler.Debug = "False"
SlnRelativePath = "Umbraco.Tests.AcceptanceTest\"
DefaultWebSiteLanguage = "Visual C#"
StartServerOnDebug = "false"
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{E3F9F378-AFE1-40A5-90BD-82833375DBFE}"
ProjectSection(SolutionItems) = preProject
..\build\NuSpecs\tools\applications.config.install.xdt = ..\build\NuSpecs\tools\applications.config.install.xdt
..\build\NuSpecs\tools\ClientDependency.config.install.xdt = ..\build\NuSpecs\tools\ClientDependency.config.install.xdt
..\build\NuSpecs\tools\install.ps1 = ..\build\NuSpecs\tools\install.ps1
..\build\NuSpecs\tools\Readme.txt = ..\build\NuSpecs\tools\Readme.txt
..\build\NuSpecs\tools\ReadmeUpgrade.txt = ..\build\NuSpecs\tools\ReadmeUpgrade.txt
..\build\NuSpecs\tools\serilog.config.install.xdt = ..\build\NuSpecs\tools\serilog.config.install.xdt
..\build\NuSpecs\tools\Web.config.install.xdt = ..\build\NuSpecs\tools\Web.config.install.xdt
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{5B03EF4E-E0AC-4905-861B-8C3EC1A0D458}"
ProjectSection(SolutionItems) = preProject
..\build\NuSpecs\build\Umbraco.Cms.props = ..\build\NuSpecs\build\Umbraco.Cms.props
..\build\NuSpecs\build\Umbraco.Cms.targets = ..\build\NuSpecs\build\Umbraco.Cms.targets
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DocTools", "DocTools", "{53594E5B-64A2-4545-8367-E3627D266AE8}"
ProjectSection(SolutionItems) = preProject
ApiDocs\docfx.filter.yml = ApiDocs\docfx.filter.yml
ApiDocs\docfx.json = ApiDocs\docfx.json
ApiDocs\index.md = ApiDocs\index.md
ApiDocs\toc.yml = ApiDocs\toc.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IssueTemplates", "IssueTemplates", "{C7311C00-2184-409B-B506-52A5FAEA8736}"
ProjectSection(SolutionItems) = preProject
..\.github\ISSUE_TEMPLATE\1_Bug.md = ..\.github\ISSUE_TEMPLATE\1_Bug.md
..\.github\ISSUE_TEMPLATE\2_Feature_request.md = ..\.github\ISSUE_TEMPLATE\2_Feature_request.md
..\.github\ISSUE_TEMPLATE\3_Support_question.md = ..\.github\ISSUE_TEMPLATE\3_Support_question.md
..\.github\ISSUE_TEMPLATE\4_Documentation_issue.md = ..\.github\ISSUE_TEMPLATE\4_Documentation_issue.md
..\.github\ISSUE_TEMPLATE\5_Security_issue.md = ..\.github\ISSUE_TEMPLATE\5_Security_issue.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Core", "Umbraco.Core\Umbraco.Core.csproj", "{29AA69D9-B597-4395-8D42-43B1263C240A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.ModelsBuilder.Embedded", "Umbraco.ModelsBuilder.Embedded\Umbraco.ModelsBuilder.Embedded.csproj", "{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Infrastructure", "Umbraco.Infrastructure\Umbraco.Infrastructure.csproj", "{3AE7BF57-966B-45A5-910A-954D7C554441}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.PublishedCache.NuCache", "Umbraco.PublishedCache.NuCache\Umbraco.PublishedCache.NuCache.csproj", "{F6DE8DA0-07CC-4EF2-8A59-2BC81DBB3830}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Web.BackOffice", "Umbraco.Web.BackOffice\Umbraco.Web.BackOffice.csproj", "{9B95EEF7-63FE-4432-8C63-166BE9C1A929}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Web.UI.NetCore", "Umbraco.Web.UI.NetCore\Umbraco.Web.UI.NetCore.csproj", "{DCDFE97C-5630-4F6F-855D-8AEEB96556A5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Web.Website", "Umbraco.Web.Website\Umbraco.Web.Website.csproj", "{5A246D54-3109-4D2B-BE7D-FC0787D126AE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Tests.Common", "Umbraco.Tests.Common\Umbraco.Tests.Common.csproj", "{A499779C-1B3B-48A8-B551-458E582E6E96}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Tests.UnitTests", "Umbraco.Tests.UnitTests\Umbraco.Tests.UnitTests.csproj", "{9102ABDF-E537-4E46-B525-C9ED4833EED0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Web.Common", "Umbraco.Web.Common\Umbraco.Web.Common.csproj", "{79E4293D-C92C-4649-AEC8-F1EFD95BDEB1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3819A550-DCEC-4153-91B4-8BA9F7F0B9B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3819A550-DCEC-4153-91B4-8BA9F7F0B9B4}.Release|Any CPU.ActiveCfg = Debug|Any CPU
{9E4C8A12-FBE0-4673-8CE2-DF99D5D57817}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E4C8A12-FBE0-4673-8CE2-DF99D5D57817}.Release|Any CPU.ActiveCfg = Debug|Any CPU
{29AA69D9-B597-4395-8D42-43B1263C240A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29AA69D9-B597-4395-8D42-43B1263C240A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29AA69D9-B597-4395-8D42-43B1263C240A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29AA69D9-B597-4395-8D42-43B1263C240A}.Release|Any CPU.Build.0 = Release|Any CPU
{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}.Release|Any CPU.Build.0 = Release|Any CPU
{3AE7BF57-966B-45A5-910A-954D7C554441}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3AE7BF57-966B-45A5-910A-954D7C554441}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3AE7BF57-966B-45A5-910A-954D7C554441}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3AE7BF57-966B-45A5-910A-954D7C554441}.Release|Any CPU.Build.0 = Release|Any CPU
{F6DE8DA0-07CC-4EF2-8A59-2BC81DBB3830}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F6DE8DA0-07CC-4EF2-8A59-2BC81DBB3830}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F6DE8DA0-07CC-4EF2-8A59-2BC81DBB3830}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F6DE8DA0-07CC-4EF2-8A59-2BC81DBB3830}.Release|Any CPU.Build.0 = Release|Any CPU
{9B95EEF7-63FE-4432-8C63-166BE9C1A929}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B95EEF7-63FE-4432-8C63-166BE9C1A929}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B95EEF7-63FE-4432-8C63-166BE9C1A929}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B95EEF7-63FE-4432-8C63-166BE9C1A929}.Release|Any CPU.Build.0 = Release|Any CPU
{DCDFE97C-5630-4F6F-855D-8AEEB96556A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DCDFE97C-5630-4F6F-855D-8AEEB96556A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DCDFE97C-5630-4F6F-855D-8AEEB96556A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DCDFE97C-5630-4F6F-855D-8AEEB96556A5}.Release|Any CPU.Build.0 = Release|Any CPU
{5A246D54-3109-4D2B-BE7D-FC0787D126AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A246D54-3109-4D2B-BE7D-FC0787D126AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A246D54-3109-4D2B-BE7D-FC0787D126AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A246D54-3109-4D2B-BE7D-FC0787D126AE}.Release|Any CPU.Build.0 = Release|Any CPU
{A499779C-1B3B-48A8-B551-458E582E6E96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A499779C-1B3B-48A8-B551-458E582E6E96}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A499779C-1B3B-48A8-B551-458E582E6E96}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A499779C-1B3B-48A8-B551-458E582E6E96}.Release|Any CPU.Build.0 = Release|Any CPU
{9102ABDF-E537-4E46-B525-C9ED4833EED0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9102ABDF-E537-4E46-B525-C9ED4833EED0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9102ABDF-E537-4E46-B525-C9ED4833EED0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9102ABDF-E537-4E46-B525-C9ED4833EED0}.Release|Any CPU.Build.0 = Release|Any CPU
{79E4293D-C92C-4649-AEC8-F1EFD95BDEB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{79E4293D-C92C-4649-AEC8-F1EFD95BDEB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{79E4293D-C92C-4649-AEC8-F1EFD95BDEB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{79E4293D-C92C-4649-AEC8-F1EFD95BDEB1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{227C3B55-80E5-4E7E-A802-BE16C5128B9D} = {2849E9D4-3B4E-40A3-A309-F3CB4F0E125F}
{9E4C8A12-FBE0-4673-8CE2-DF99D5D57817} = {B5BD12C1-A454-435E-8A46-FF4A364C0382}
{E3F9F378-AFE1-40A5-90BD-82833375DBFE} = {227C3B55-80E5-4E7E-A802-BE16C5128B9D}
{5B03EF4E-E0AC-4905-861B-8C3EC1A0D458} = {227C3B55-80E5-4E7E-A802-BE16C5128B9D}
{53594E5B-64A2-4545-8367-E3627D266AE8} = {FD962632-184C-4005-A5F3-E705D92FC645}
{C7311C00-2184-409B-B506-52A5FAEA8736} = {FD962632-184C-4005-A5F3-E705D92FC645}
{A499779C-1B3B-48A8-B551-458E582E6E96} = {B5BD12C1-A454-435E-8A46-FF4A364C0382}
{9102ABDF-E537-4E46-B525-C9ED4833EED0} = {B5BD12C1-A454-435E-8A46-FF4A364C0382}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7A0F2E34-D2AF-4DAB-86A0-7D7764B3D0EC}
EndGlobalSection
EndGlobal