How to add a newly created user to a specific security group in sensenet? - sensenet

When the user registers an account in my web application I would like for them to be added to the security group identified users so they have the necessary permissions to run my web application. This is what I've tried.
using SenseNet.ContentRepository.Storage;
using SenseNet.ContentRepository.Storage.Security;
namespace DerAssistantService.Actions
{
public static class UserActions
{
[ODataAction]
public static Content RegisterUser(Content content, string email, string password)
{
if (string.IsNullOrEmpty(email))
throw new ArgumentNullException(nameof(email));
if (string.IsNullOrEmpty(password))
throw new ArgumentNullException(nameof(password));
var username = email.Split('#').First();
using (new SystemAccount())
{
var user = Content.CreateNew("User", content.ContentHandler, username);
user["FullName"] = username;
user["Email"] = email;
user["LoginName"] = email;
user["Enabled"] = true;
user["Password"] = password;
user.Save();
var identifiedUsers = Node.Load<Group>("/Root/IMS/BuiltIn/Portal/IdentifiedUsers");
identifiedUsers.AddMember(user); // Error because type Content is not of type IGroup
return user;
}
}
}
}

The AddMember method of the group class expects either an IUser or an IGroup instance. The user you created previously is of the Content type, which is a wrapper type sensenet uses for everything. The underlying business object sits inside that content object, you can extract it using the ContentHandler property:
identifiedUsers.AddMember(user.ContentHandler as IUser);
The Content object represents the upper, generic API layer where you find fields for example. The lower layer, accessible by the ContentHandler property represents the business layer with strongly typed classes like User, File or Workspace.

Related

login using custom/added field to the user profile in liferay 7.4

I am trying to add new field to the user profile (student number) and allow users to login using either email or the new field (student number) with the same password for both.
I have overridden login.jsp to allow both Email and Student Number.
My idea is to override the login action command with something similar to the code below:
#Component(
property = {
"javax.portlet.name=com_liferay_login_web_portlet_LoginPortlet",
"mvc.command.name=/login/login"
},
service = MVCActionCommand.class
)
public class CustomLoginActionCommand extends BaseMVCActionCommand {
#Override
protected void doProcessAction(ActionRequest actionRequest,
ActionResponse actionResponse) throws Exception {
ThemeDisplay themeDisplay = (ThemeDisplay)actionRequest.getAttribute(
WebKeys.THEME_DISPLAY);
HttpServletRequest request = PortalUtil.getOriginalServletRequest(
PortalUtil.getHttpServletRequest(actionRequest));
HttpServletResponse response = PortalUtil.getHttpServletResponse(
actionResponse);
String login = ParamUtil.getString(actionRequest, "login");
String password = actionRequest.getParameter("password");
boolean rememberMe = ParamUtil.getBoolean(actionRequest, "rememberMe");
String authType = CompanyConstants.AUTH_TYPE_EA;
String email = "";
if(isValidEmail(login)){ //if the user trying to login with his email
email = login ;
}
else if(isNumeric(login)){ //check if the user trying to login with his student number
//fetch User by Student Number (login)
//e.g. fetchUserByStudentNumber(login)
//get the Email Adress for the retrieved user object and use it to login
email = user.getEmailAddress();
}
else{
// Exception
}
AuthenticatedSessionManagerUtil.login(request, response, email, password, rememberMe, authType);
actionResponse.sendRedirect(themeDisplay.getPathMain());
}
}
is this the right way to achive similar requierment?
in Liferay 7.4 U46+, we can extend supported system services with Liferay Objects. so I have two options to extend the User Profile, 1- by adding a new field to the User object. or 2- by creating a new "custom field". which option is better?
in both options, how to force unique values in the added field (student number)?
how to retrieve user object by using added field (fetchUserByStudentNumber)?
Appreciate your feedback!
Thanks
Overwriting the portal login command is possible, but I would rather use a custom Authenticator to not overwrite other logic implemented in the MVC action component. As you want booth (mail and student number), you could implement authenticateByEmailAddress like in Password-Based-Authentication-Pipelines and check both authentication results with a boolean OR approach.
Extending portal model objects should rather be implemented via Custom Fields. Fetching a user like in fetchUserByStudentNumber you will probably need the ExpandoValue service and a dynamic query. Maybe there are better approached, but this is what comes into my mind first.

DDD: Can I pass a Domain-Service as a parameter of aggregate constructor

I have a domain that maintains accounts for other systems(Media).
At first, I derived the following aggregate root
public class Account extends Entity {
private AccountId accountId;
private TenantId tenantId;
private LoginAccount loginAccount;
private Media media;
private LoginValidity validity;
public Account(TenatId shopId, Media media, LoginAccount loginAccount) {
this.accountId = new AccountId();
this.setTenatId(shopId);
this.set(media);
this.setLoginValidity(LoginValidity.NOT_REQUESTED);
}
public void validateLogin(LoginValidationService loginValidationService) {
LoginValidity validity = loginValidationService.validateLoginFor(media,loginAccount);
setLoginValidity(validity);
//TO-DO EVENT PUBLISHING
}
public void changeLoginAccount(LoginAccount loginAccount) {
setLoginAccount(loginAccount);
setLoginValidity(LoginValidity.NOT_REQUESTED);
//TO-DO EVENT PUBLISHING?
}
...Setters
}
And I also derived the LoginValidationService as a Domain-Service.
LoginValidationService determines strategy(policy) using Media
And then I also derive two business logic(invariant)
When the User adds a Account login validation must occur.
When the User changes LoginAccount login validation must occur.
My question is that for the first invariant,LoginValidationService(A Domain-Service) could be a parameter for aggregate root's constructor like this
public class AccountApplicationService {
private LoginValidationService loginValidationService;
private AccountRepository accountRepository;
public Account createAccount(CreateAccountCommand command) {
TenantId tenantId = new TenantId(command.getTenantId());
Media media = mediaService.mediaFrom(command.getMediaId());
Account account = new Account(tenantId,
media,
command.getLoginAccount(),
loginValidationService);
accountRepository.save(account);
return ccount;
}
...
}
public class Account extends Entity {
private AccountId accountId;
private TenantId tenantId;
private LoginAccount loginAccount;
private Media media;
private LoginValidity validity;
public Account(TenatId shopId,
Media media,
LoginAccount
loginAccount,LoginValidationService loginValidationService) {
this.accountId = new AccountId();
this.setTenatId(shopId);
this.set(media);
LoginValidity validity =
loginValidationService.validateLoginFor(media,loginAccount);
this.setLoginValidity(validity);
}
....
}
Is exist the Pattern above? (passing the domain-service to the constructor) and is it the right approach of DDD?
Do I have to derive the first invariant as a use-case? like this,
public class AccountApplicationService {
private LoginValidationService loginValidationService;
private AccountRepository accountRepository;
public Account createAccountAndValidateLogin(CreateAccountAndValidateLoginCommand command) {
TenantId tenantId = new TenantId(command.getTenantId());
Media media = mediaService.mediaFrom(command.getMediaId());
MediaAccount mediaAccount = new MediaAccount(tenantId,media,command.getLoginAccount());
mediaAccount.validateLogin(loginValidationService);
mediaAccountRepository.save(mediaAccount);
}
...
}
Please give me any advice.
-----------Edit---------
I add the Account's constructor code. LoginValidationService is not a member of Account.
Would be a solution for you to pass the LoginValidity as Account aggregate constructor parameter?
public Account createAccount(CreateAccountCommand command) {
TenantId tenantId = new TenantId(command.getTenantId());
Media media = mediaService.mediaFrom(command.getMediaId());
LoginValidity loginValidity =
loginValidationService.validateLoginFor(media,command.getLoginAccount());
Account account = new Account(tenantId,
media,
command.getLoginAccount(),
loginValidity);
accountRepository.save(account);
return ccount;
}
I think that to validate an account is something that the entity cannot do by yourself so it is clear is domain service's responsibility, then I would see in the use case flow logic, or you can create the account with a domain service responsible of validate and create the account and you had the business logic encapsulates in domain service instead of use case.
Can I pass a Domain-Service as a parameter of aggregate constructor
Yes, but I would expect that to make your life harder in the long run.
An AGGREGATE is a cluster of associated objects that we treat as a unit for the purpose of data changes. -- Evans, 2003
Domain Services, however, don't change -- they are stateless, and "any client can use any instance of a particular SERVICE without regard to the instance's individual history."
So mixing the patterns in this way is a bit odd on two fronts; including the service as a member of the aggregate suggests that it changes, and also it implies that this aggregate has a special relationship with a particular instance of the service, which contradicts the notion that they are not interchangeable.
It's not clear to me what compensating advantage you get in a design where the service is embedded within your aggregate.
I found an additional invariant for Account creating that Account can't hold itself so I derived AccountProvisionService(Domain-Service). The Application-Service code is as follows.
...
public Account createAccount(CreateAccountCommand command) {
return AccountProvisioningService.provisionAccount(
command.getTenantId(), command.getMediaId(), command.getLoginAccount());
}
public void changeLoginAccount(ChangeLoginAccountCommand command) {
Account account = existingAccount(command.getShopId(),command.getAccountId());
LoginValidity loginValidity = loginValidationService.validateLoginFor(Account.media(), command.getLoginAccount());
account.changeLoginAccount(command.getLoginAccount(),loginValidity);
AccountRepository.save(account);
}
...

ASP.NET Core Authentication Id?

The tutorials on enabling authentication work all right, but what identifier should be used to store data for a user in the database? The only thing easily available is User.Name, which seems to be my email address.
I see in the database there is an AspNetUsers table with that as the UserName column, and a varchar Id column that appears to be a GUID and is the primary key. It seems like the 'Id' field is the logical value to use, but it's not readily available in my app. I found I can get to it like this:
string ID_TYPE = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
var id = User.Claims.Where(x => x.Type == ID_TYPE).Select(x => x.Value).FirstOrDefault();
But that seems like a weird way to go about it. Is that the proper value to use say if I want to create a 'Posts' table that has a user associated with a post?
I've looked at these pages and it seems that a lot of this might be due to Microsoft integrating the same login process with ActiveDirectory.
Is there a reason to make the id so hard to get to and the name so easy? Should I be using the name instead? Should I be careful not to let the user change their user name then?
The shortest path to UserId is:
User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
Or create extension like so if you need to access UserId a lot:
public static class ClaimsPrincipalExtensions
{
public static string GetUserId(this ClaimsPrincipal principal)
{
if (principal == null)
return null; //throw new ArgumentNullException(nameof(principal));
string ret = "";
try
{
ret = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
catch (System.Exception)
{
}
return ret;
}
}
Usage:
User.GetUserId()
In your controller use dependency injection to get the user manager:
Create a class MyUser that has your extended properties
public class MyUser : IdentityUser
{
public string MyExendedInfo { get; set; }
public int MyOtherInfo {get;set;}
}
add this property to the database using migration, or manually add it.
In Startup.cs in Configure Services add:
services.AddIdentity<MyUser, IdentityRole>()
Now inject this in your controller class:
private readonly UserManager<MyUser> _userManager;
public HomeController(
UserManager<MyUser> userManager)
{
_userManager = userManager;
}
Now you can access your additional proporties and your Id (if you still need this) in your action methods like this:
var user = await _userManager.GetUserAsync(HttpContext.User);
var id = user.Id;
var myExtendedInfo = user.MyExtendedInfo;
var myOtherInfo = user.MyOtherInfo;
etc
You can also update information about your user:
user.myExtendedInfo = "some string";
user.MyOtherInfo = myDatabase.pointer;
var result = await _userManager.UpdateAsync(user);
if (!result.Succeeded)
{
//handle error
}
So as long as you want only limited additional data stored in the database, you can create a custom user class, and use the Identity system to store it for you. I would not store it myself.
If however, you need to store large information in a separate table and/or reference the user from other tables, the Id is the correct field to use and you can access it as shown above.
I don't know what the best practice is for how much information can be stored in AspNetUsers, versus in claims, versus in your own table, but since the provided table already stores things like user name, phonenumber etc, I think it is Ok to extend it like this.

RazorEngine Error trying to send email

I have an MVC 4 application that sends out multiple emails. For example, I have an email template for submitting an order, a template for cancelling an order, etc...
I have an Email Service with multiple methods. My controller calls the Send method which looks like this:
public virtual void Send(List<string> recipients, string subject, string template, object data)
{
...
string html = GetContent(template, data);
...
}
The Send method calls GetContent, which is the method causing the problem:
private string GetContent(string template, object data)
{
string path = Path.Combine(BaseTemplatePath, string.Format("{0}{1}", template, ".html.cshtml"));
string content = File.ReadAllText(path);
return Engine.Razor.RunCompile(content, "htmlTemplate", null, data);
}
I am receiving the error:
The same key was already used for another template!
In my GetContent method should I add a new parameter for the TemplateKey and use that variable instead of always using htmlTemplate? Then the new order email template could have newOrderKey and CancelOrderKey for the email template being used to cancel an order?
Explanation
This happens because you use the same template key ("htmlTemplate") for multiple different templates.
Note that the way you currently have implemented GetContent you will run into multiple problems:
Even if you use a unique key, for example the template variable, you will trigger the exception when the templates are edited on disk.
Performance: You are reading the template file every time even when the template is already cached.
Solution:
Implement the ITemplateManager interface to manage your templates:
public class MyTemplateManager : ITemplateManager
{
private readonly string baseTemplatePath;
public MyTemplateManager(string basePath) {
baseTemplatePath = basePath;
}
public ITemplateSource Resolve(ITemplateKey key)
{
string template = key.Name;
string path = Path.Combine(baseTemplatePath, string.Format("{0}{1}", template, ".html.cshtml"));
string content = File.ReadAllText(path);
return new LoadedTemplateSource(content, path);
}
public ITemplateKey GetKey(string name, ResolveType resolveType, ITemplateKey context)
{
return new NameOnlyTemplateKey(name, resolveType, context);
}
public void AddDynamic(ITemplateKey key, ITemplateSource source)
{
throw new NotImplementedException("dynamic templates are not supported!");
}
}
Setup on startup:
var config = new TemplateServiceConfiguration();
config.Debug = true;
config.TemplateManager = new MyTemplateManager(BaseTemplatePath);
Engine.Razor = RazorEngineService.Create(config);
And use it:
// You don't really need this method anymore.
private string GetContent(string template, object data)
{
return Engine.Razor.RunCompile(template, null, data);
}
RazorEngine will now fix all the problems mentioned above internally. Notice how it is perfectly fine to use the name of the template as key, if in your scenario the name is all you need to identify a template (otherwise you cannot use NameOnlyTemplateKey and need to provide your own implementation).
Hope this helps.
(Disclaimer: Contributor of RazorEngine)

Handle navigation properties when adding new object to entity model

I have the following data model:
I am writing a WCF service that needs to support adding new Report:
public bool CreateNewReport(Report report)
{
MyEntities context = new MyEntities();
context.AddToReports(Report);
context.SaveChanges();
}
So my method gets a report object that was made on the client and adds it to the database throught the data context. (all of the members are included in the DataContract)
My question is regarding navigation properties.
Do the client also needs to create a user object and put it in the new report object before sending it ?
What is the best way to approach this ? one way i think of is adding a UserId field in the ReportEntity
when a new report is inserted, how do i update the UserEntity Report nav property that with the new Report ?
Thanks.
If you import your database, generate navigation properties (the properties in your picture) AND foreign id properties (then you have for example an User and UserID property in your report class). This way you can set the UserID in your client and send it to the server and add it with AddToReports... If you send the whole user object you have to attach it to the entity context otherwise the user will be created once again...
Attach the referenced user: (but it's better to send the user only by id)
public bool CreateNewReport(Report report)
{
using (MyEntities context = new MyEntities())
{
context.AddToReports(Report);
context.Users.Attach(report.User);
context.SaveChanges();
}
}
To change the report of a user:
public bool ChangeUserToNewReport(int userid, Report newReport)
{
using (MyEntities context = new MyEntities())
{
var user = context.Users.Single(u => u.ID = userid);
user.Report = newReport;
context.SaveChanges();
}
}
For an existing report:
public bool ChangeUserReport(int userid, Report existingReport)
{
using (MyEntities context = new MyEntities())
{
context.Reports.Attach(existingReport);
var user = context.Users.Single(u => u.ID = userid);
user.Report = existingReport;
context.SaveChanges();
}
}
This is a sample how your model should look like. Double click on the association line to open the dialog. You can see that the Person and PersonID properties are the same. If you create your model like this, VS should generate the correct SQL.