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

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.

Related

Which methods should be inside a Page class in POM design pattern?

I am creating a UI test automation framework using POM design pattern. After reading SeleniumHQ page for Page Objects, I am contemplating what all methods should I create inside a Page Object.
Let's take a simple example of login page object consisting of username, password textboxes and submit button. SeleniumHQ link has created the below methods :
1. typeUsername(String username)
2. typePassword(String password)
3. submitLogin()
4. submitLoginExceptionFailure()
5. loginAs(String username, String password)
I am a bit perplexed while looking at these methods. Why should I create first 3 methods(typeUsername, typePassword,submitLogin), when I already am creating a loginAs method. Any thoughts around it ?
SeleniumHQ Link - https://github.com/SeleniumHQ/selenium/wiki/PageObjects
Pasting the code PageObject code for LoginPage :
public class LoginPage {
private final WebDriver driver;
public LoginPage(WebDriver driver) {
this.driver = driver;
// Check that we're on the right page.
if (!"Login".equals(driver.getTitle())) {
// Alternatively, we could navigate to the login page, perhaps logging out first
throw new IllegalStateException("This is not the login page");
}
}
// The login page contains several HTML elements that will be represented as WebElements.
// The locators for these elements should only be defined once.
By usernameLocator = By.id("username");
By passwordLocator = By.id("passwd");
By loginButtonLocator = By.id("login");
// The login page allows the user to type their username into the username field
public LoginPage typeUsername(String username) {
// This is the only place that "knows" how to enter a username
driver.findElement(usernameLocator).sendKeys(username);
// Return the current page object as this action doesn't navigate to a page represented by another PageObject
return this;
}
// The login page allows the user to type their password into the password field
public LoginPage typePassword(String password) {
// This is the only place that "knows" how to enter a password
driver.findElement(passwordLocator).sendKeys(password);
// Return the current page object as this action doesn't navigate to a page represented by another PageObject
return this;
}
// The login page allows the user to submit the login form
public HomePage submitLogin() {
// This is the only place that submits the login form and expects the destination to be the home page.
// A seperate method should be created for the instance of clicking login whilst expecting a login failure.
driver.findElement(loginButtonLocator).submit();
// Return a new page object representing the destination. Should the login page ever
// go somewhere else (for example, a legal disclaimer) then changing the method signature
// for this method will mean that all tests that rely on this behaviour won't compile.
return new HomePage(driver);
}
// The login page allows the user to submit the login form knowing that an invalid username and / or password were entered
public LoginPage submitLoginExpectingFailure() {
// This is the only place that submits the login form and expects the destination to be the login page due to login failure.
driver.findElement(loginButtonLocator).submit();
// Return a new page object representing the destination. Should the user ever be navigated to the home page after submiting a login with credentials
// expected to fail login, the script will fail when it attempts to instantiate the LoginPage PageObject.
return new LoginPage(driver);
}
// Conceptually, the login page offers the user the service of being able to "log into"
// the application using a user name and password.
public HomePage loginAs(String username, String password) {
// The PageObject methods that enter username, password & submit login have already defined and should not be repeated here.
typeUsername(username);
typePassword(password);
return submitLogin();
}
}
You might want to check if typing the username only and then clicking the submit button shows a correct error message, or only the password, etc.
I usually look at the page and try to summarize what "actions" the user can do on that page, every action becomes a method. The different actions might be on different "levels". e.g. on a blog website, the user can enter the blog-title and the blog content, that are two actions the user can do, but looking from an other abstraction layer the user wants to "create" a post. So that function might again call other functions.
Basically its like any other programming, you have multiple abstraction layers, that is why you have Page Objects in the first place.
And just use iterative development, create a function that does what you want to test and if you find yourself reusing the same code (or identifier) in other functions, separate those out in a new function
Finely chopping your code into more methods allows for greater atomization. Nothing stops you from grouping the typeUsername(), typePassword() and submitLogin() into login() method.
The upside of doing so is knowing more precisely that your login failed at, say, typing password as opposed to "somewhere on the login page".

How to extend and validate session in ASP.NET Core Identity?

We want to offer the users to manage their login sessions.
This worked so far pretty easy with ASP.NET Core and WITHOUT the Identity Extensions.
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1#react-to-back-end-changes
But how can we invoke this validation with ASP.NET Core Identity?
Problem we have:
How do we store login-session-based information like Browser Version, Device Type and User Position? Do we extend any type or what is the idea?
How do we dynamically set the cookie expiration based on a specific user?
How do we invalidate the Cookie from the backend (like the link above shows)?
How do we required additional password-prompts for special functions?
It feels the ASP.NET Core Identity is still not that extensible and flexible :(
Unfortunately, this area of ASP.NET Identity is not very well documented, which I personally see as a risk for such a sensitive area.
After I've been more involved with the source code, the solution seems to be to use the SignIn process of the SignIn Manager.
The basic problem is that it's not that easy to get your custom claims into the ClaimsIdentity of the cookie. There is no method for that.
The values for this must under no circumstances be stored in the claims of the user in the database, as otherwise every login receives these claims - would be bad.
So I created my own method, which first searches for the user in the database and then uses the existing methods of the SignInManager.
After having a ClaimsIdentity created by the SignIn Manager, you can enrich the Identity with your own claims.
For this I save the login session with a Guid in the database and carry the id as a claim in the cookie.
public async Task<SignInResult> SignInUserAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure)
{
DateTimeOffset createdLoginOn = DateTimeOffset.UtcNow;
DateTimeOffset validTo = createdLoginOn.AddSeconds(_userAuthOptions.ExpireTimeSeconds);
// search for user
var user = await _userManager.FindByNameAsync(userName);
if (user is null) { return SignInResult.Failed; }
// CheckPasswordSignInAsync checks if user is allowed to sign in and if user is locked
// also it checks and counts the failed login attempts
var attempt = await CheckPasswordSignInAsync(user, password, lockoutOnFailure);
if (attempt.Succeeded)
{
// TODO: Check 2FA here
// create a unique login entry in the backend
string browserAgent = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"];
Guid loginId = await _eventDispatcher.Send(new AddUserLoginCommand(user.Id, user.UserName, createdLoginOn, validTo, browserAgent));
// Write the login id in the login claim, so we identify the login context
Claim[] customClaims = { new Claim(CustomUserClaims.UserLoginSessionId, loginId.ToString()) };
// Signin User
await SignInWithClaimsAsync(user, isPersistent, customClaims);
return SignInResult.Success;
}
return attempt;
}
With each request I can validate the ClaimsIdentity and search for the login id.
public class CookieSessionValidationHandler : CookieAuthenticationEvents
{
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
ClaimsPrincipal userPrincipal = context.Principal;
if (!userPrincipal.TryGetUserSessionInfo(out int userId, out Guid sessionId))
{
// session format seems to be invalid
context.RejectPrincipal();
}
else
{
IEventDispatcher eventDispatcher = context.HttpContext.RequestServices.GetRequiredService<IEventDispatcher>();
bool succeeded = await eventDispatcher.Send(new UserLoginUpdateLoginSessionCommand(userId, sessionId));
if (!succeeded)
{
// session expired or was killed
context.RejectPrincipal();
}
}
}
}
See also
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1#react-to-back-end-changes

When i use role based authorization how does it knows currenly logged in users Role?

Assume i have the following Role based authorization for an action
[AuthorizeDBRoleAttribute(Roles = "Manager")]
public ActionResult Welcome()
{
return View();
}
here is the AuthorizeDBRoleAttribute class
public class AuthorizeDBRoleAttribute : AuthorizeAttribute
{
public string Roles { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContextBase)
{
//Bind User Roles from Database here
string userRoles = "Manager,Supervisor,Inspector";
if (userRoles.IndexOf(Roles) > -1)
return true;
else
return false;
}
}
I have separate DB tables Roles and Users
Assume the current user logged in is of Role Manager. How does this "AuthorizeDBRoleAttribute" attribute knows the current user's role so it can let access to the Action method
How to setup Role based authorization was discussed in this post. I want to drag it a bit further in to the next step on how MVC figure out the current user's role etc
You start with current IPrincipal, taken from the http context and set there by the authentication module.
Then, depending on your current approach (which we obviously don't know) you either have roles already stored in the principal object or you have only the user name and you retrieve roles from the database for the current user name.
The author of the code you cite even put a comment there - retrieve user roles for the current user name, more or less something like:
string username = httpContextBase.User.Identity.Name;
var roles = whereeverYourRolesAreStored.RolesForUser( username );

How to set and retrieve custom attributes in user identity object in Worklight?

I have implemented Custom Authenticator and Login Module in IBM Worklight 6.0. The authentication mechanism works fine.
I had set custom attributes in User identity object like roles, email, etc..
In Login module,
public UserIdentity createIdentity(String realm) {
Map<String, Object> customAttributes= new HashMap<String, Object>();
customAttributes.put("userName", username);
customAttributes.put("mail", customAttrValue); //customAttrValue - this has the email id
UserIdentity uiObj=new UserIdentity("CustomRealm", username, username, null, customAttributes, password);
return uiObj;
}
Now I am unable to retrieve the attribute values using the below api call.
WL.Client.getUserInfo("CustomRealm", "mail");
First you need to get the "attributes" option for the realm. Then get your "mail"
attribute from that set of attributes. Something like this:
var attrs = WL.Client.getUserInfo("CustomRealm", "attributes");
var email = null;;
if (attrs) {
email = attrs.mail;
}

ServiceStack: Custom CredentialsAuthProvider

We need to pass extra info together with the Username and Password from a mobile client with Authentication. Is it possible to inherit from CredentialsAuthProvider and define extra data members that can then be extracted by the server?
Have you seen the Custom Authentication and Authorization section of the wiki? You should be able to access any extra info you pass with the Username and Password doing something like...
public class MyAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var extraInfo = authService.RequestContext.Get<IHttpRequest>().GetParam("extraInfo");
//Add here your custom auth logic (database calls etc)
//Return true if credentials are valid, otherwise false
}
}