2013-11-07 17:16:22 +01:00
using System ;
using System.Collections.Generic ;
using System.ComponentModel.DataAnnotations ;
using System.Linq ;
using System.Net ;
using System.Net.Http ;
using System.Text ;
using System.Threading.Tasks ;
2015-10-29 12:52:36 +00:00
using System.Web ;
2013-11-07 17:16:22 +01:00
using System.Web.Http ;
using System.Web.Http.ModelBinding ;
using System.Web.Security ;
using AutoMapper ;
using Examine.LuceneEngine.SearchCriteria ;
using Examine.SearchCriteria ;
using Umbraco.Core ;
using Umbraco.Core.Logging ;
using Umbraco.Core.Models ;
using Umbraco.Core.Models.EntityBase ;
2013-11-18 14:25:08 +11:00
using Umbraco.Core.Models.Membership ;
2014-02-17 12:56:37 +11:00
using Umbraco.Core.Persistence ;
2014-09-19 09:47:42 +10:00
using Umbraco.Core.Persistence.DatabaseModelDefinitions ;
2014-02-17 14:15:40 +11:00
using Umbraco.Core.Security ;
2013-11-17 13:43:04 +11:00
using Umbraco.Core.Services ;
2014-09-19 09:47:42 +10:00
using Umbraco.Web.Models.Mapping ;
2013-11-07 17:16:22 +01:00
using Umbraco.Web.WebApi ;
using Umbraco.Web.Models.ContentEditing ;
using Umbraco.Web.Mvc ;
2015-01-06 11:22:40 +11:00
using Umbraco.Web.WebApi.Binders ;
2013-11-07 17:16:22 +01:00
using Umbraco.Web.WebApi.Filters ;
using umbraco ;
using Constants = Umbraco . Core . Constants ;
using Examine ;
namespace Umbraco.Web.Editors
{
/// <remarks>
/// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting
/// access to ALL of the methods on this controller will need access to the member application.
/// </remarks>
[PluginController("UmbracoApi")]
[UmbracoApplicationAuthorizeAttribute(Constants.Applications.Members)]
2014-05-09 15:50:07 +10:00
[OutgoingNoHyphenGuidFormat]
2013-11-07 17:16:22 +01:00
public class MemberController : ContentControllerBase
{
/// <summary>
/// Constructor
/// </summary>
public MemberController ( )
: this ( UmbracoContext . Current )
{
2014-03-18 20:36:02 +11:00
_provider = Core . Security . MembershipProviderExtensions . GetMembersMembershipProvider ( ) ;
2013-11-07 17:16:22 +01:00
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="umbracoContext"></param>
public MemberController ( UmbracoContext umbracoContext )
: base ( umbracoContext )
{
2014-03-18 20:36:02 +11:00
_provider = Core . Security . MembershipProviderExtensions . GetMembersMembershipProvider ( ) ;
2013-11-07 17:16:22 +01:00
}
2014-05-09 15:50:07 +10:00
private readonly MembershipProvider _provider ;
2013-11-18 15:58:53 +11:00
/// <summary>
2013-11-18 14:25:08 +11:00
/// Returns the currently configured membership scenario for members in umbraco
/// </summary>
/// <value></value>
protected MembershipScenario MembershipScenario
{
get { return Services . MemberService . GetMembershipScenario ( ) ; }
}
2016-02-26 14:30:32 +00:00
public PagedResult < MemberBasic > GetPagedResults (
2014-09-19 09:47:42 +10:00
int pageNumber = 1 ,
int pageSize = 100 ,
2016-08-10 12:58:41 +02:00
string orderBy = "username" ,
2014-09-19 09:47:42 +10:00
Direction orderDirection = Direction . Ascending ,
2016-02-26 14:30:32 +00:00
bool orderBySystemField = true ,
2014-09-19 09:47:42 +10:00
string filter = "" ,
string memberTypeAlias = null )
{
2016-02-26 14:30:32 +00:00
2014-09-19 15:34:26 +10:00
if ( pageNumber < = 0 | | pageSize < = 0 )
2014-09-19 09:47:42 +10:00
{
throw new NotSupportedException ( "Both pageNumber and pageSize must be greater than zero" ) ;
}
2014-09-19 15:34:26 +10:00
if ( MembershipScenario = = MembershipScenario . NativeUmbraco )
2014-09-19 09:47:42 +10:00
{
2015-07-29 15:07:32 +02:00
long totalRecords ;
2016-02-26 14:30:32 +00:00
var members = Services . MemberService
2017-02-09 10:34:40 +11:00
. GetAll ( ( pageNumber - 1 ) , pageSize , out totalRecords , orderBy , orderDirection , orderBySystemField , memberTypeAlias , filter ) . ToArray ( ) ;
2014-09-19 15:34:26 +10:00
if ( totalRecords = = 0 )
{
return new PagedResult < MemberBasic > ( 0 , 0 , 0 ) ;
}
var pagedResult = new PagedResult < MemberBasic > ( totalRecords , pageNumber , pageSize ) ;
pagedResult . Items = members
. Select ( Mapper . Map < IMember , MemberBasic > ) ;
return pagedResult ;
2014-09-19 09:47:42 +10:00
}
2014-09-19 15:34:26 +10:00
else
{
2015-07-29 15:07:32 +02:00
int totalRecords ;
2016-04-07 18:16:03 +02:00
MembershipUserCollection members ;
if ( filter . IsNullOrWhiteSpace ( ) )
{
members = _provider . GetAllUsers ( ( pageNumber - 1 ) , pageSize , out totalRecords ) ;
}
else
{
//we need to search!
//try by name first
members = _provider . FindUsersByName ( filter , ( pageNumber - 1 ) , pageSize , out totalRecords ) ;
if ( totalRecords = = 0 )
{
//try by email then
members = _provider . FindUsersByEmail ( filter , ( pageNumber - 1 ) , pageSize , out totalRecords ) ;
}
}
2014-09-19 15:34:26 +10:00
if ( totalRecords = = 0 )
{
return new PagedResult < MemberBasic > ( 0 , 0 , 0 ) ;
}
var pagedResult = new PagedResult < MemberBasic > ( totalRecords , pageNumber , pageSize ) ;
pagedResult . Items = members
. Cast < MembershipUser > ( )
. Select ( Mapper . Map < MembershipUser , MemberBasic > ) ;
return pagedResult ;
}
2016-02-26 14:30:32 +00:00
2014-09-19 09:47:42 +10:00
}
/// <summary>
/// Returns a display node with a list view to render members
/// </summary>
/// <param name="listName"></param>
/// <returns></returns>
public MemberListDisplay GetListNodeDisplay ( string listName )
{
var display = new MemberListDisplay
{
ContentTypeAlias = listName ,
ContentTypeName = listName ,
Id = listName ,
IsContainer = true ,
2014-10-09 11:08:09 +11:00
Name = listName = = Constants . Conventions . MemberTypes . AllMembersListId ? "All Members" : listName ,
2014-09-19 09:47:42 +10:00
Path = "-1," + listName ,
ParentId = - 1
} ;
2016-01-06 11:22:15 +01:00
TabsAndPropertiesResolver . AddListView ( display , "member" , Services . DataTypeService , Services . TextService ) ;
2014-09-19 09:47:42 +10:00
return display ;
}
2013-11-07 17:16:22 +01:00
/// <summary>
/// Gets the content json for the member
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
2016-01-27 13:20:13 +01:00
[OutgoingEditorModelEvent]
2013-11-07 17:16:22 +01:00
public MemberDisplay GetByKey ( Guid key )
{
2013-11-18 14:25:08 +11:00
MembershipUser foundMembershipMember ;
MemberDisplay display ;
IMember foundMember ;
switch ( MembershipScenario )
2013-11-07 17:16:22 +01:00
{
2013-11-18 14:25:08 +11:00
case MembershipScenario . NativeUmbraco :
foundMember = Services . MemberService . GetByKey ( key ) ;
if ( foundMember = = null )
{
HandleContentNotFound ( key ) ;
}
return Mapper . Map < IMember , MemberDisplay > ( foundMember ) ;
case MembershipScenario . CustomProviderWithUmbracoLink :
2016-02-26 14:30:32 +00:00
//TODO: Support editing custom properties for members with a custom membership provider here.
2013-11-18 15:29:53 +11:00
2016-02-26 14:30:32 +00:00
//foundMember = Services.MemberService.GetByKey(key);
//if (foundMember == null)
//{
// HandleContentNotFound(key);
//}
//foundMembershipMember = Membership.GetUser(key, false);
//if (foundMembershipMember == null)
//{
// HandleContentNotFound(key);
//}
2013-11-18 15:29:53 +11:00
2016-02-26 14:30:32 +00:00
//display = Mapper.Map<MembershipUser, MemberDisplay>(foundMembershipMember);
////map the name over
//display.Name = foundMember.Name;
//return display;
2013-11-18 14:25:08 +11:00
case MembershipScenario . StandaloneCustomProvider :
default :
2014-03-18 20:36:02 +11:00
foundMembershipMember = _provider . GetUser ( key , false ) ;
2013-11-18 14:25:08 +11:00
if ( foundMembershipMember = = null )
{
HandleContentNotFound ( key ) ;
}
display = Mapper . Map < MembershipUser , MemberDisplay > ( foundMembershipMember ) ;
return display ;
2013-11-07 17:16:22 +01:00
}
}
/// <summary>
/// Gets an empty content item for the
/// </summary>
/// <param name="contentTypeAlias"></param>
/// <returns></returns>
2016-01-27 13:20:13 +01:00
[OutgoingEditorModelEvent]
2013-11-17 13:43:04 +11:00
public MemberDisplay GetEmpty ( string contentTypeAlias = null )
2013-11-07 17:16:22 +01:00
{
2013-11-18 14:25:08 +11:00
IMember emptyContent ;
switch ( MembershipScenario )
2013-11-07 17:16:22 +01:00
{
2013-11-18 14:25:08 +11:00
case MembershipScenario . NativeUmbraco :
if ( contentTypeAlias = = null )
{
throw new HttpResponseException ( HttpStatusCode . NotFound ) ;
}
2013-11-07 17:16:22 +01:00
2014-02-13 16:46:52 +11:00
var contentType = Services . MemberTypeService . Get ( contentTypeAlias ) ;
2013-11-18 14:25:08 +11:00
if ( contentType = = null )
{
throw new HttpResponseException ( HttpStatusCode . NotFound ) ;
}
2013-11-17 13:43:04 +11:00
2017-06-27 15:30:32 +10:00
var provider = Core . Security . MembershipProviderExtensions . GetMembersMembershipProvider ( ) ;
2014-03-18 19:40:41 +11:00
emptyContent = new Member ( contentType ) ;
2017-06-27 15:30:32 +10:00
emptyContent . AdditionalData [ "NewPassword" ] = Membership . GeneratePassword ( provider . MinRequiredPasswordLength , provider . MinRequiredNonAlphanumericCharacters ) ;
2013-11-18 14:25:08 +11:00
return Mapper . Map < IMember , MemberDisplay > ( emptyContent ) ;
case MembershipScenario . CustomProviderWithUmbracoLink :
2016-02-26 14:30:32 +00:00
//TODO: Support editing custom properties for members with a custom membership provider here.
2013-11-18 15:29:53 +11:00
2013-11-18 14:25:08 +11:00
case MembershipScenario . StandaloneCustomProvider :
default :
//we need to return a scaffold of a 'simple' member - basically just what a membership provider can edit
emptyContent = MemberService . CreateGenericMembershipProviderMember ( "" , "" , "" , "" ) ;
emptyContent . AdditionalData [ "NewPassword" ] = Membership . GeneratePassword ( Membership . MinRequiredPasswordLength , Membership . MinRequiredNonAlphanumericCharacters ) ;
return Mapper . Map < IMember , MemberDisplay > ( emptyContent ) ;
2013-11-17 13:43:04 +11:00
}
2013-11-07 17:16:22 +01:00
}
/// <summary>
/// Saves member
/// </summary>
/// <returns></returns>
[FileUploadCleanupFilter]
2015-01-06 11:22:40 +11:00
public MemberDisplay PostSave (
[ModelBinder(typeof(MemberBinder))]
MemberSave contentItem )
2013-11-07 17:16:22 +01:00
{
//If we've reached here it means:
// * Our model has been bound
// * and validated
// * any file attachments have been saved to their temporary location for us to use
// * we have a reference to the DTO object and the persisted object
// * Permissions are valid
2013-11-18 14:25:08 +11:00
//This is a special case for when we're not using the umbraco membership provider - when this is the case
// we will not have a ContentTypeAlias set which means the model state will be invalid but we don't care about that
// so we'll remove that model state value
if ( MembershipScenario ! = MembershipScenario . NativeUmbraco )
{
ModelState . Remove ( "ContentTypeAlias" ) ;
//TODO: We're removing this because we are not displaying it but when we support the CustomProviderWithUmbracoLink scenario
// we will be able to have a real name associated so do not remove this state once that is implemented!
ModelState . Remove ( "Name" ) ;
}
2013-11-07 17:16:22 +01:00
//map the properties to the persisted entity
MapPropertyValues ( contentItem ) ;
//Unlike content/media - if there are errors for a member, we do NOT proceed to save them, we cannot so return the errors
if ( ModelState . IsValid = = false )
{
var forDisplay = Mapper . Map < IMember , MemberDisplay > ( contentItem . PersistedContent ) ;
forDisplay . Errors = ModelState . ToErrorDictionary ( ) ;
throw new HttpResponseException ( Request . CreateValidationErrorResponse ( forDisplay ) ) ;
}
//TODO: WE need to support this! - requires UI updates, etc...
2014-03-18 20:36:02 +11:00
if ( _provider . RequiresQuestionAndAnswer )
2013-11-07 17:16:22 +01:00
{
throw new NotSupportedException ( "Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified" ) ;
}
2016-02-26 14:30:32 +00:00
2016-03-10 15:52:34 +01:00
//We're gonna look up the current roles now because the below code can cause
// events to be raised and developers could be manually adding roles to members in
// their handlers. If we don't look this up now there's a chance we'll just end up
// removing the roles they've assigned.
var currRoles = Roles . GetRolesForUser ( contentItem . PersistedContent . Username ) ;
//find the ones to remove and remove them
var rolesToRemove = currRoles . Except ( contentItem . Groups ) . ToArray ( ) ;
2013-11-07 17:16:22 +01:00
string generatedPassword = null ;
//Depending on the action we need to first do a create or update using the membership provider
// this ensures that passwords are formatted correclty and also performs the validation on the provider itself.
switch ( contentItem . Action )
{
case ContentSaveAction . Save :
generatedPassword = UpdateWithMembershipProvider ( contentItem ) ;
break ;
case ContentSaveAction . SaveNew :
MembershipCreateStatus status ;
2013-11-18 14:25:08 +11:00
CreateWithMembershipProvider ( contentItem , out status ) ;
2015-10-29 12:52:36 +00:00
// save the ID of the creator
contentItem . PersistedContent . CreatorId = Security . CurrentUser . Id ;
2013-11-07 17:16:22 +01:00
break ;
default :
//we don't support anything else for members
throw new HttpResponseException ( HttpStatusCode . NotFound ) ;
}
//If we've had problems creating/updating the user with the provider then return the error
if ( ModelState . IsValid = = false )
{
var forDisplay = Mapper . Map < IMember , MemberDisplay > ( contentItem . PersistedContent ) ;
forDisplay . Errors = ModelState . ToErrorDictionary ( ) ;
throw new HttpResponseException ( Request . CreateValidationErrorResponse ( forDisplay ) ) ;
}
2013-11-18 14:25:08 +11:00
//save the IMember -
//TODO: When we support the CustomProviderWithUmbracoLink scenario, we'll need to save the custom properties for that here too
if ( MembershipScenario = = MembershipScenario . NativeUmbraco )
{
//save the item
//NOTE: We are setting the password to NULL - this indicates to the system to not actually save the password
// so it will not get overwritten!
2014-03-18 19:05:07 +11:00
contentItem . PersistedContent . RawPasswordValue = null ;
2013-11-18 14:25:08 +11:00
//create/save the IMember
2013-11-18 15:29:53 +11:00
Services . MemberService . Save ( contentItem . PersistedContent ) ;
2013-11-18 14:25:08 +11:00
}
2016-02-26 14:30:32 +00:00
2013-11-07 17:16:22 +01:00
//Now let's do the role provider stuff - now that we've saved the content item (that is important since
// if we are changing the username, it must be persisted before looking up the member roles).
2016-03-10 15:52:34 +01:00
if ( rolesToRemove . Any ( ) )
2013-11-07 17:16:22 +01:00
{
2016-03-10 15:52:34 +01:00
Roles . RemoveUserFromRoles ( contentItem . PersistedContent . Username , rolesToRemove ) ;
2013-11-07 17:16:22 +01:00
}
//find the ones to add and add them
2016-03-10 15:52:34 +01:00
var toAdd = contentItem . Groups . Except ( currRoles ) . ToArray ( ) ;
2013-11-07 17:16:22 +01:00
if ( toAdd . Any ( ) )
{
//add the ones submitted
Roles . AddUserToRoles ( contentItem . PersistedContent . Username , toAdd ) ;
}
//set the generated password (if there was one) - in order to do this we'll chuck the gen'd password into the
2014-03-25 20:44:08 -07:00
// additional data of the IUmbracoEntity of the persisted item - then we can retrieve this in the model mapper and set
2013-11-07 17:16:22 +01:00
// the value to be given to the UI. Hooray for AdditionalData :)
contentItem . PersistedContent . AdditionalData [ "GeneratedPassword" ] = generatedPassword ;
//return the updated model
var display = Mapper . Map < IMember , MemberDisplay > ( contentItem . PersistedContent ) ;
2013-11-18 14:25:08 +11:00
2013-11-07 17:16:22 +01:00
//lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403
HandleInvalidModelState ( display ) ;
2016-01-06 11:22:15 +01:00
var localizedTextService = Services . TextService ;
2013-11-07 17:16:22 +01:00
//put the correct msgs in
switch ( contentItem . Action )
{
case ContentSaveAction . Save :
case ContentSaveAction . SaveNew :
2016-01-06 11:22:15 +01:00
display . AddSuccessNotification ( localizedTextService . Localize ( "speechBubbles/editMemberSaved" ) , localizedTextService . Localize ( "speechBubbles/editMemberSaved" ) ) ;
2013-11-07 17:16:22 +01:00
break ;
}
return display ;
}
/// <summary>
/// Maps the property values to the persisted entity
/// </summary>
/// <param name="contentItem"></param>
private void MapPropertyValues ( MemberSave contentItem )
{
UpdateName ( contentItem ) ;
//map the custom properties - this will already be set for new entities in our member binder
contentItem . PersistedContent . Email = contentItem . Email ;
contentItem . PersistedContent . Username = contentItem . Username ;
//use the base method to map the rest of the properties
base . MapPropertyValues ( contentItem ) ;
}
/// <summary>
/// Update the membership user using the membership provider (for things like email, etc...)
/// If a password change is detected then we'll try that too.
/// </summary>
/// <param name="contentItem"></param>
/// <returns>
/// If the password has been reset then this method will return the reset/generated password, otherwise will return null.
/// </returns>
private string UpdateWithMembershipProvider ( MemberSave contentItem )
{
//Get the member from the provider
2014-03-18 20:36:02 +11:00
var membershipUser = _provider . GetUser ( contentItem . PersistedContent . Key , false ) ;
2013-11-07 17:16:22 +01:00
if ( membershipUser = = null )
{
//This should never happen! so we'll let it YSOD if it does.
2014-03-18 20:36:02 +11:00
throw new InvalidOperationException ( "Could not get member from membership provider " + _provider . Name + " with key " + contentItem . PersistedContent . Key ) ;
2013-11-07 17:16:22 +01:00
}
var shouldReFetchMember = false ;
2015-07-27 10:27:33 +02:00
var providedUserName = contentItem . PersistedContent . Username ;
2013-11-07 17:16:22 +01:00
//Update the membership user if it has changed
2014-02-20 13:40:17 +11:00
try
2013-11-07 17:16:22 +01:00
{
2014-03-18 20:36:02 +11:00
var requiredUpdating = Members . UpdateMember ( membershipUser , _provider ,
2014-02-20 13:40:17 +11:00
contentItem . Email . Trim ( ) ,
contentItem . IsApproved ,
comment : contentItem . Comments ) ;
if ( requiredUpdating . Success )
2013-11-07 17:16:22 +01:00
{
2013-11-18 14:25:08 +11:00
//re-map these values
2013-11-07 17:16:22 +01:00
shouldReFetchMember = true ;
}
2014-02-20 13:40:17 +11:00
}
catch ( Exception ex )
{
LogHelper . WarnWithException < MemberController > ( "Could not update member, the provider returned an error" , ex ) ;
ModelState . AddPropertyError (
//specify 'default' just so that it shows up as a notification - is not assigned to a property
new ValidationResult ( "Could not update member, the provider returned an error: " + ex . Message + " (see log for full details)" ) , "default" ) ;
2013-11-07 17:16:22 +01:00
}
//if they were locked but now they are trying to be unlocked
if ( membershipUser . IsLockedOut & & contentItem . IsLockedOut = = false )
{
try
{
2014-03-18 20:36:02 +11:00
var result = _provider . UnlockUser ( membershipUser . UserName ) ;
2013-11-07 17:16:22 +01:00
if ( result = = false )
{
//it wasn't successful - but it won't really tell us why.
ModelState . AddModelError ( "custom" , "Could not unlock the user" ) ;
}
else
{
shouldReFetchMember = true ;
}
}
catch ( Exception ex )
{
ModelState . AddModelError ( "custom" , ex ) ;
}
}
else if ( membershipUser . IsLockedOut = = false & & contentItem . IsLockedOut )
{
//NOTE: This should not ever happen unless someone is mucking around with the request data.
//An admin cannot simply lock a user, they get locked out by password attempts, but an admin can un-approve them
ModelState . AddModelError ( "custom" , "An admin cannot lock a user" ) ;
}
2013-11-18 14:25:08 +11:00
2013-11-07 17:16:22 +01:00
//password changes ?
2013-11-18 14:25:08 +11:00
if ( contentItem . Password = = null )
{
//If the provider has changed some values, these values need to be reflected in the member object
//that will get mapped to the display object
if ( shouldReFetchMember )
{
2014-03-17 16:01:09 +11:00
RefetchMemberData ( contentItem , LookupType . ByKey ) ;
2015-07-27 10:27:33 +02:00
RestoreProvidedUserName ( contentItem , providedUserName ) ;
2013-11-18 14:25:08 +11:00
}
2015-07-27 10:27:33 +02:00
2013-11-18 14:25:08 +11:00
return null ;
}
2013-11-07 17:16:22 +01:00
2014-03-18 20:36:02 +11:00
var passwordChangeResult = Members . ChangePassword ( membershipUser . UserName , contentItem . Password , _provider ) ;
2013-11-07 17:16:22 +01:00
if ( passwordChangeResult . Success )
{
//If the provider has changed some values, these values need to be reflected in the member object
//that will get mapped to the display object
if ( shouldReFetchMember )
{
2014-03-17 16:01:09 +11:00
RefetchMemberData ( contentItem , LookupType . ByKey ) ;
2015-07-27 10:27:33 +02:00
RestoreProvidedUserName ( contentItem , providedUserName ) ;
2013-11-07 17:16:22 +01:00
}
//even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword
return passwordChangeResult . Result . ResetPassword ;
}
//it wasn't successful, so add the change error to the model state
ModelState . AddPropertyError (
passwordChangeResult . Result . ChangeError ,
string . Format ( "{0}password" , Constants . PropertyEditors . InternalGenericPropertiesPrefix ) ) ;
return null ;
}
2014-03-17 16:01:09 +11:00
private enum LookupType
{
ByKey ,
ByUserName
}
2013-11-18 14:25:08 +11:00
/// <summary>
2014-03-17 16:01:09 +11:00
/// Re-fetches the database data to map to the PersistedContent object and re-assigns the already mapped the posted properties so that the display object is up-to-date
2013-11-18 14:25:08 +11:00
/// </summary>
/// <param name="contentItem"></param>
/// <remarks>
/// This is done during an update if the membership provider has changed some underlying data - we need to ensure that our model is consistent with that data
/// </remarks>
2014-03-17 16:01:09 +11:00
private void RefetchMemberData ( MemberSave contentItem , LookupType lookup )
2013-11-18 14:25:08 +11:00
{
2014-03-17 16:01:09 +11:00
var currProps = contentItem . PersistedContent . Properties . ToArray ( ) ;
2016-02-26 14:30:32 +00:00
2013-11-18 14:25:08 +11:00
switch ( MembershipScenario )
{
case MembershipScenario . NativeUmbraco :
2014-03-17 16:01:09 +11:00
switch ( lookup )
{
case LookupType . ByKey :
//Go and re-fetch the persisted item
2016-02-26 14:30:32 +00:00
contentItem . PersistedContent = Services . MemberService . GetByKey ( contentItem . Key ) ;
2014-03-17 16:01:09 +11:00
break ;
case LookupType . ByUserName :
contentItem . PersistedContent = Services . MemberService . GetByUsername ( contentItem . Username . Trim ( ) ) ;
2016-02-26 14:30:32 +00:00
break ;
2014-03-17 16:01:09 +11:00
}
2013-11-18 14:25:08 +11:00
break ;
case MembershipScenario . CustomProviderWithUmbracoLink :
case MembershipScenario . StandaloneCustomProvider :
default :
2014-03-18 20:36:02 +11:00
var membershipUser = _provider . GetUser ( contentItem . Key , false ) ;
2013-11-18 14:25:08 +11:00
//Go and re-fetch the persisted item
2016-02-26 14:30:32 +00:00
contentItem . PersistedContent = Mapper . Map < MembershipUser , IMember > ( membershipUser ) ;
2013-11-18 14:25:08 +11:00
break ;
2013-11-18 15:29:53 +11:00
}
2014-03-17 16:01:09 +11:00
2014-03-18 20:36:02 +11:00
UpdateName ( contentItem ) ;
2014-03-17 16:01:09 +11:00
//re-assign the mapped values that are not part of the membership provider properties.
2016-02-26 14:30:32 +00:00
var builtInAliases = Constants . Conventions . Member . GetStandardPropertyTypeStubs ( ) . Select ( x = > x . Key ) . ToArray ( ) ;
2014-03-17 16:01:09 +11:00
foreach ( var p in contentItem . PersistedContent . Properties )
{
var valueMapped = currProps . SingleOrDefault ( x = > x . Alias = = p . Alias ) ;
if ( builtInAliases . Contains ( p . Alias ) = = false & & valueMapped ! = null )
{
p . Value = valueMapped . Value ;
p . TagSupport . Behavior = valueMapped . TagSupport . Behavior ;
p . TagSupport . Enable = valueMapped . TagSupport . Enable ;
p . TagSupport . Tags = valueMapped . TagSupport . Tags ;
}
2016-02-26 14:30:32 +00:00
}
2015-07-27 10:27:33 +02:00
}
/// <summary>
/// Following a refresh of member data called during an update if the membership provider has changed some underlying data,
/// we don't want to lose the provided, and potentiallly changed, username
/// </summary>
/// <param name="contentItem"></param>
/// <param name="providedUserName"></param>
private static void RestoreProvidedUserName ( MemberSave contentItem , string providedUserName )
{
contentItem . PersistedContent . Username = providedUserName ;
2013-11-18 14:25:08 +11:00
}
2013-11-07 17:16:22 +01:00
/// <summary>
/// This is going to create the user with the membership provider and check for validation
/// </summary>
/// <param name="contentItem"></param>
/// <param name="status"></param>
/// <returns></returns>
/// <remarks>
2013-11-18 14:25:08 +11:00
/// Depending on if the Umbraco membership provider is active or not, the process differs slightly:
///
/// * If the umbraco membership provider is used - we create the membership user first with the membership provider, since
/// it's the umbraco membership provider, this writes to the umbraco tables. When that is complete we re-fetch the IMember
/// model data from the db. In this case we don't care what the provider user key is.
/// * If we're using a non-umbraco membership provider - we check if there is a 'Member' member type - if so
/// we create an empty IMember instance first (of type 'Member'), this gives us a unique ID (GUID)
/// that we then use to create the member in the custom membership provider. This acts as the link between Umbraco data and
/// the custom membership provider data. This gives us the ability to eventually have custom membership properties but still use
/// a custom memberhip provider. If there is no 'Member' member type, then we will simply just create the membership provider member
/// with no link to our data.
///
2013-11-07 17:16:22 +01:00
/// If this is successful, it will go and re-fetch the IMember from the db because it will now have an ID because the Umbraco provider
/// uses the umbraco data store - then of course we need to re-map it to the saved property values.
/// </remarks>
2013-11-18 14:25:08 +11:00
private MembershipUser CreateWithMembershipProvider ( MemberSave contentItem , out MembershipCreateStatus status )
2013-11-07 17:16:22 +01:00
{
2013-11-18 14:25:08 +11:00
MembershipUser membershipUser ;
2013-11-07 17:16:22 +01:00
2013-11-18 14:25:08 +11:00
switch ( MembershipScenario )
{
case MembershipScenario . NativeUmbraco :
//We are using the umbraco membership provider, create the member using the membership provider first.
2014-03-18 20:36:02 +11:00
var umbracoMembershipProvider = ( UmbracoMembershipProviderBase ) _provider ;
2013-11-18 14:25:08 +11:00
//TODO: We are not supporting q/a - passing in empty here
membershipUser = umbracoMembershipProvider . CreateUser (
contentItem . ContentTypeAlias , contentItem . Username ,
contentItem . Password . NewPassword ,
contentItem . Email , "" , "" ,
contentItem . IsApproved ,
Guid . NewGuid ( ) , //since it's the umbraco provider, the user key here doesn't make any difference
out status ) ;
2016-02-26 14:30:32 +00:00
2013-11-18 14:25:08 +11:00
break ;
case MembershipScenario . CustomProviderWithUmbracoLink :
//We are using a custom membership provider, we'll create an empty IMember first to get the unique id to use
// as the provider user key.
2013-11-18 15:29:53 +11:00
//create it - this persisted item has already been set in the MemberBinder based on the 'Member' member type:
2013-11-18 14:25:08 +11:00
Services . MemberService . Save ( contentItem . PersistedContent ) ;
//TODO: We are not supporting q/a - passing in empty here
2014-03-18 20:36:02 +11:00
membershipUser = _provider . CreateUser (
2013-11-18 14:25:08 +11:00
contentItem . Username ,
contentItem . Password . NewPassword ,
contentItem . Email ,
"TEMP" , //some membership provider's require something here even if q/a is disabled!
"TEMP" , //some membership provider's require something here even if q/a is disabled!
contentItem . IsApproved ,
contentItem . PersistedContent . Key , //custom membership provider, we'll link that based on the IMember unique id (GUID)
out status ) ;
2013-11-07 17:16:22 +01:00
2013-11-18 14:25:08 +11:00
break ;
case MembershipScenario . StandaloneCustomProvider :
// we don't have a member type to use so we will just create the basic membership user with the provider with no
// link back to the umbraco data
2016-02-26 14:30:32 +00:00
2013-11-18 15:29:53 +11:00
var newKey = Guid . NewGuid ( ) ;
2013-11-18 14:25:08 +11:00
//TODO: We are not supporting q/a - passing in empty here
2014-03-18 20:36:02 +11:00
membershipUser = _provider . CreateUser (
2013-11-18 14:25:08 +11:00
contentItem . Username ,
contentItem . Password . NewPassword ,
contentItem . Email ,
"TEMP" , //some membership provider's require something here even if q/a is disabled!
"TEMP" , //some membership provider's require something here even if q/a is disabled!
contentItem . IsApproved ,
2016-02-26 14:30:32 +00:00
newKey ,
2013-11-18 14:25:08 +11:00
out status ) ;
2016-02-26 14:30:32 +00:00
2013-11-18 14:25:08 +11:00
break ;
default :
throw new ArgumentOutOfRangeException ( ) ;
}
2013-11-07 17:16:22 +01:00
//TODO: Localize these!
switch ( status )
{
case MembershipCreateStatus . Success :
2014-09-19 17:06:15 +10:00
//map the key back
contentItem . Key = membershipUser . ProviderUserKey . TryConvertTo < Guid > ( ) . Result ;
contentItem . PersistedContent . Key = contentItem . Key ;
2013-11-07 17:16:22 +01:00
//if the comments are there then we need to save them
if ( contentItem . Comments . IsNullOrWhiteSpace ( ) = = false )
{
membershipUser . Comment = contentItem . Comments ;
2014-03-18 20:36:02 +11:00
_provider . UpdateUser ( membershipUser ) ;
2013-11-18 14:25:08 +11:00
}
2014-03-17 16:01:09 +11:00
RefetchMemberData ( contentItem , LookupType . ByUserName ) ;
2013-11-07 17:16:22 +01:00
break ;
case MembershipCreateStatus . InvalidUserName :
ModelState . AddPropertyError (
new ValidationResult ( "Invalid user name" , new [ ] { "value" } ) ,
string . Format ( "{0}login" , Constants . PropertyEditors . InternalGenericPropertiesPrefix ) ) ;
break ;
case MembershipCreateStatus . InvalidPassword :
ModelState . AddPropertyError (
new ValidationResult ( "Invalid password" , new [ ] { "value" } ) ,
string . Format ( "{0}password" , Constants . PropertyEditors . InternalGenericPropertiesPrefix ) ) ;
break ;
case MembershipCreateStatus . InvalidQuestion :
case MembershipCreateStatus . InvalidAnswer :
throw new NotSupportedException ( "Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified" ) ;
case MembershipCreateStatus . InvalidEmail :
ModelState . AddPropertyError (
new ValidationResult ( "Invalid email" , new [ ] { "value" } ) ,
string . Format ( "{0}email" , Constants . PropertyEditors . InternalGenericPropertiesPrefix ) ) ;
break ;
case MembershipCreateStatus . DuplicateUserName :
ModelState . AddPropertyError (
new ValidationResult ( "Username is already in use" , new [ ] { "value" } ) ,
string . Format ( "{0}login" , Constants . PropertyEditors . InternalGenericPropertiesPrefix ) ) ;
break ;
case MembershipCreateStatus . DuplicateEmail :
ModelState . AddPropertyError (
new ValidationResult ( "Email address is already in use" , new [ ] { "value" } ) ,
string . Format ( "{0}email" , Constants . PropertyEditors . InternalGenericPropertiesPrefix ) ) ;
break ;
case MembershipCreateStatus . InvalidProviderUserKey :
ModelState . AddPropertyError (
2016-02-26 14:30:32 +00:00
//specify 'default' just so that it shows up as a notification - is not assigned to a property
2013-11-07 17:16:22 +01:00
new ValidationResult ( "Invalid provider user key" ) , "default" ) ;
break ;
case MembershipCreateStatus . DuplicateProviderUserKey :
ModelState . AddPropertyError (
2016-02-26 14:30:32 +00:00
//specify 'default' just so that it shows up as a notification - is not assigned to a property
2013-11-07 17:16:22 +01:00
new ValidationResult ( "Duplicate provider user key" ) , "default" ) ;
break ;
case MembershipCreateStatus . ProviderError :
case MembershipCreateStatus . UserRejected :
ModelState . AddPropertyError (
//specify 'default' just so that it shows up as a notification - is not assigned to a property
new ValidationResult ( "User could not be created (rejected by provider)" ) , "default" ) ;
break ;
default :
throw new ArgumentOutOfRangeException ( ) ;
}
return membershipUser ;
}
/// <summary>
/// Permanently deletes a member
/// </summary>
/// <param name="key"></param>
2013-12-06 13:12:18 +01:00
/// <returns></returns>
///
[HttpPost]
2013-11-07 17:16:22 +01:00
public HttpResponseMessage DeleteByKey ( Guid key )
{
2013-11-18 15:29:53 +11:00
IMember foundMember ;
MembershipUser foundMembershipUser ;
switch ( MembershipScenario )
2013-11-07 17:16:22 +01:00
{
2013-11-18 15:29:53 +11:00
case MembershipScenario . NativeUmbraco :
foundMember = Services . MemberService . GetByKey ( key ) ;
if ( foundMember = = null )
{
return HandleContentNotFound ( key , false ) ;
}
Services . MemberService . Delete ( foundMember ) ;
break ;
case MembershipScenario . CustomProviderWithUmbracoLink :
foundMember = Services . MemberService . GetByKey ( key ) ;
if ( foundMember ! = null )
{
Services . MemberService . Delete ( foundMember ) ;
}
2014-03-18 20:36:02 +11:00
foundMembershipUser = _provider . GetUser ( key , false ) ;
2013-11-18 15:29:53 +11:00
if ( foundMembershipUser ! = null )
{
2014-03-18 20:36:02 +11:00
_provider . DeleteUser ( foundMembershipUser . UserName , true ) ;
2013-11-18 15:29:53 +11:00
}
break ;
case MembershipScenario . StandaloneCustomProvider :
2014-03-18 20:36:02 +11:00
foundMembershipUser = _provider . GetUser ( key , false ) ;
2013-11-18 15:29:53 +11:00
if ( foundMembershipUser ! = null )
{
2014-03-18 20:36:02 +11:00
_provider . DeleteUser ( foundMembershipUser . UserName , true ) ;
2013-11-18 15:29:53 +11:00
}
break ;
default :
throw new ArgumentOutOfRangeException ( ) ;
2013-11-07 17:16:22 +01:00
}
return Request . CreateResponse ( HttpStatusCode . OK ) ;
}
}
}