-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow Duplicate Email for Members #14784
Allow Duplicate Email for Members #14784
Conversation
…o honor RequireUniqueEmailForMembers.
…o honor RequireUniqueEmailForMembers.
…/jasont0101/Umbraco-CMS into temp-RequireUniqueEmailForMember
Hi there @jasont0101, thank you for this contribution! 👍 While we wait for one of the Core Collaborators team to have a look at your work, we wanted to let you know about that we have a checklist for some of the things we will consider during review:
Don't worry if you got something wrong. We like to think of a pull request as the start of a conversation, we're happy to provide guidance on improving your contribution. If you realize that you might want to make some changes then you can do that by adding new commits to the branch you created for this work and pushing new commits. They should then automatically show up as updates to this pull request. Thanks, from your friendly Umbraco GitHub bot 🤖 🙂 |
Hi @jasont0101! Thanks for your PR to allow for duplicate member emails. I'm just going to check with HQ about adding this into Umbraco. Best wishes Emma |
@@ -92,7 +96,8 @@ public class MemberController : ContentControllerBase | |||
IJsonSerializer jsonSerializer, | |||
IPasswordChanger<MemberIdentityUser> passwordChanger, | |||
ICoreScopeProvider scopeProvider, | |||
ITwoFactorLoginService twoFactorLoginService) | |||
ITwoFactorLoginService twoFactorLoginService, | |||
IOptionsSnapshot<SecuritySettings>? securitySettings) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason for using IOptionsSnapshot<>
instead of IOptions<>
and making this nullable? Making it optional will result in different behaviour, where using the 'wrong' constructor will not honour the setting value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have change this to IOption. I was not sure what was best to use here. I was looking at other controllers that were using IOptionSnapshot and mimicking what was done in those classes. IOptionSnapshot changed to IOption.
@@ -143,7 +149,8 @@ public class MemberController : ContentControllerBase | |||
jsonSerializer, | |||
passwordChanger, | |||
scopeProvider, | |||
StaticServiceProvider.Instance.GetRequiredService<ITwoFactorLoginService>()) | |||
StaticServiceProvider.Instance.GetRequiredService<ITwoFactorLoginService>(), | |||
null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should get the options/settings using the StaticServiceProvider.Instance
as fallback:
null) | |
StaticServiceProvider.Instance.GetRequiredService<IOptions<SecuritySettings>>()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, change made.
if(_securitySettings.RequireUniqueEmailForMembers == false) | ||
{ | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check should be moved to the ValidatePropertiesData()
method, so this ValidateUniqueEmail()
keeps doing what you expect it to do (based on the method name) and is only called when you require unique emails.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see your point. I have moved the conditional check to the ValidatePropertiesData()
method. I am not in loving with my change. I did it as a separate conditional statement, but the number of conditional statements in this method is become a lot but the trade off is that the current validEmail
variable check would become more complex. i.e. drop my conditional on _securitySettings.MemberRequireUniqueEmail
and change if(validEmail == false) { ...
to if (_securitySettings.MemberRequireUniqueEmail && ValidateUniqueEmail(model) == false) { ...
. Thoughts?
New to committing to Umbraco so not sure if there is a preference for verbose easier to read code or more concise code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I changes my code to the more concise version. Without major class refactoring I am not sure how to make CodeScene happy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need help to know if there are actions I need to take? Builds local, but fails some check on the GitHub pipeline that I need help understanding. Also would like to know if this will be considered for inclusion. Thank you.
…ller unit tests to support the addition of the SecuritySetting object.
This was building on Friday, but not today. Looks like the changes I made are causing the Run dotnet pack step to fail since they do not match the baseline. Not sure it this is something I can fix. What do I need to do here, anything? |
# Conflicts: # tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs
Hi @jasont0101 Apologies for the delay. It looked like there was since a conflict introduced, so I have merged Hopefully the builds will now pass but if not, I'll ask HQ to review what we need to do. Thanks Emma |
Hi @jasont0101 The build seemed to pass, except CodeScene, but I can't think how to get around that for now without bigger refactoring. Other than that do you have any other questions, or are you awaiting further review of your changes to this PR from @ronaldbarendse and then we could be ready to merge (presuming all good from HQ)? Thanks Emma |
Emma,
I do not think I have other questions. Since this is my first really contribution to the core, I am just learning the process, so thanks for the feedback. If there is nothing I can do, and HQ is cool with the code I am good to go on having it merged.
Jason Thomas
Lead of Software Development
simpli__FYIN since 2016
[cid:83d89d1e-41a1-4977-8e50-d252c5a501b6]
…________________________________
From: Emma L Garland ***@***.***>
Sent: Thursday, February 1, 2024 8:58 AM
To: umbraco/Umbraco-CMS ***@***.***>
Cc: Jason Thomas ***@***.***>; Mention ***@***.***>
Subject: Re: [umbraco/Umbraco-CMS] Allow Duplicate Email for Members (PR #14784)
Hi @jasont0101<https://github.com/jasont0101>
The build seemed to pass, except CodeScene, but I can't think how to get around that for now without bigger refactoring.
Other than that do you have any other questions, or are you awaiting further review of your changes to this PR from @ronaldbarendse<https://github.com/ronaldbarendse> and then we could be ready to merge (presuming all good from HQ)?
Thanks
Emma
—
Reply to this email directly, view it on GitHub<#14784 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AGMU54LNX26AIFOFJL4U2GDYRO3RDAVCNFSM6AAAAAA4Q7MXD6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSMRRGY2TCNBWGM>.
You are receiving this because you were mentioned.
Fyin.com is an Umbraco Gold Certified Partner<https://umbraco.com/partners/partnerlist/fyin/>
NOTICE: This message and any replies are subject to FYIN LLC.'s<https://Fyin.com?Signature> standard Terms & Conditions available online. All correspondence is tracked for billing purposes in fifteen minute increments.
Fyin.com<https://www.Fyin.com?Signature> : 888.466.0101
|
That's awesome @jasont0101 and love that this is your first contribution! Sorry it's taken a little while. I'll check it on my side and run it past HQ tomorrow, and hopefully we can get it merged asap 😃 Emma |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@emmagarland I've left some additional comments that need to be fixed first and I'd recommend doing a fair bit of testing on these changes after that as well (as we don't want any regressions in creating/fetching members) 😃
@jasont0101 Awesome work on this PR, especially if it's your first! 🙌🏻 The CodeScene build errors can sometimes be triggered on existing code that already didn't follow the rules/suggestions, so don't be too afraid of this single step not passing 😉
/// <summary> | ||
/// Get an list of <see cref="IMember"/> for all members with the specified email. | ||
/// </summary> | ||
//// <param name="email">Email to use for retrieval</param> | ||
/// <returns> | ||
/// <see cref="IEnumerable{IMember}" /> | ||
/// </returns> | ||
IEnumerable<IMember> GetMembersByEmail(string email) | ||
{ | ||
return Enumerable.Empty<IMember>(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding default implementations to interfaces to avoid breaking changes is a great solution, but it should still return a usable result or (worst case) an exception indicating the method isn't implemented in the inherited class...
In this case, it could lookup all members and filter in-memory (which is really inefficient, but still return the expected value), only return a single member by using the GetByEmail()
method or actually use the paged FindByEmail()
method using StringPropertyMatchType.Exact
:
/// <summary> | |
/// Get an list of <see cref="IMember"/> for all members with the specified email. | |
/// </summary> | |
//// <param name="email">Email to use for retrieval</param> | |
/// <returns> | |
/// <see cref="IEnumerable{IMember}" /> | |
/// </returns> | |
IEnumerable<IMember> GetMembersByEmail(string email) | |
{ | |
return Enumerable.Empty<IMember>(); | |
} | |
/// <summary> | |
/// Get an list of <see cref="IMember"/> for all members with the specified email. | |
/// </summary> | |
/// <param name="email">Email to use for retrieval</param> | |
/// <returns> | |
/// <see cref="IEnumerable{IMember}" /> | |
/// </returns> | |
IEnumerable<IMember> GetMembersByEmail(string email) | |
=> FindByEmail(email, 0, int.MaxValue, out _, StringPropertyMatchType.Exact); // TODO: Remove default implementation in v14 |
Although this means there's already an existing method implemented that allows you to get all members by email, it does have the overhead of pagination and ordering by the email column... And having an easy to use GetMembersByEmail()
method still adds value, so I'd keep this new method 👍🏻
{ | ||
var members = this.GetMembersByEmail(email); | ||
return members?.FirstOrDefault(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be simplified to a single line expression:
{ | |
var members = this.GetMembersByEmail(email); | |
return members?.FirstOrDefault(); | |
} | |
=> GetMembersByEmail(email).FirstOrDefault(); |
The CMS codebase also doesn't use the this
qualifier and since GetMembersByEmail()
doesn't return null, shouldn't require the null-coalescing operator...
_memberService = memberService ?? throw new ArgumentNullException(nameof(memberService)); | ||
_shortStringHelper = shortStringHelper ?? throw new ArgumentNullException(nameof(shortStringHelper)); | ||
_securitySettings = securitySettings; | ||
} | ||
|
||
public MemberSaveModelValidator( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove this constructor, because it's not used and MemberSaveModelValidator
is an internal class 😄
If it was a public class however, it should me marked as obsolete and call the new constructor using this(...)
, so the fields (and null checks) don't have to be duplicated.
@@ -55,6 +56,7 @@ public class MemberController : ContentControllerBase | |||
private readonly ITwoFactorLoginService _twoFactorLoginService; | |||
private readonly IShortStringHelper _shortStringHelper; | |||
private readonly IUmbracoMapper _umbracoMapper; | |||
private readonly SecuritySettings? _securitySettings; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see this field being set anywhere, so it ends up always being null
and not applying the correct setting: have you (re-)tested creating a member using a duplicate e-mail address?
I'd also recommend using IOptionsMonitor<SecuritySettings>
, so any updates to the configuration is always used (since controllers are registered as singletons). Also keep the constructor updates/obsoletion recommendations in mind when fixing this!
@jasont0101 We're in a similar situation to yourself with this issue. Are you planning to implement the amendments requested by @ronaldbarendse? |
@connectisl Got a bit side tracked, but yes, picking up work on this again. |
That's excellent news, @jasont0101. Without wanting to sound too direct, are you working to a particular timeline to implement the suggested changes? We're just looking at the date for Umbraco to end support for v10 (https://umbraco.com/products/knowledge-center/long-term-support-and-end-of-life/) to try and see which release(s) this pull request/fork could be included in. We'd be more than happy to help with any testing on this, too. Feel free to drop me an email at seo@connect.org.uk if you need an extra pair of eyes/hands. |
@connectisl Originally I started this with v12, but it got ignored by Umbraco for a long time so I lost my momentum and let this sit too long. Now the contrib branch is on a RC of v14. I think what I will need to do is abandon this PR and start new. I tried to rebase my fork with v13/contrib branch but it did not going so well (assuming that was even the correct thing to do). I am now trying to targeting v13 first then v14. I need to get the done this week, after that it is up to Umbraco to approve and merge, fairly sure they will have move suggestions. I need this in at least v13 as we have client that needs this but does not want to go to v14. Might even be a hard sell to get them to v13. |
Abandoning this PR. |
@connectisl Please see #16202 |
Prerequisites
Description
Oddly have had two project in a row that the client had the business need to allow member to have duplicate email address. While there are other way to resolve this issue, want to add (re-add?) this feature to Umbraco so that it was easy to enable with out having to work out complicated alternative contact email options. So, added the option to allow member accounts to be created with duplicate emails.
The current default behavior is that members must have unique emails. While it was possible to override the ASP.NET Identity provider value for RequireUniqueEmail in startup.cs or program.cs, validators still checked if the email existed and prevented the member from being added if the email was a duplicate.
Add a new appsettings.json option to the Umbraco:CMS:Security section, RequireUniqueEmailForMembers with a default value of true. This now sets the value for options.User.RequireUniqueEmail in the ConfigureMemberIdentityOptions class.
Also added a new method to the MemberService, GetMembersByEmail.The GetByEmail method will only return FirstOrDefault. This new method allows for the retrieval of all member with the same email.
This option only allows duplicate emails for members and not users. Usernames must still be unique.
TEST