I have a web application where users connect to their profiles.
I am able to authenticate a user using JAAS againt a database, when he requests a protected ressource (in this case a profile.xhtml page).
After authentication, I can access the profile page, which is obviously empty (no data were transfered from the database).
So, my problem is that : I can't figure out how to serve him his appropriate profile (full of this user information) after the authentication. In other words:
1) How can I grab the username(this id of the table User) from the login module to my profile's page?
2) How can I put the information of the user tuple in the served JSF page so that it can be rendered to the user?
This is my Login module ( a bit long so I cut just the login method ):
#Override
public boolean login() throws LoginException {
if (callbackHandler == null) {
throw new LoginException("Error: no CallbackHandler available "
+ "to garner authentication information from the user");
}
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("username");
callbacks[1] = new PasswordCallback("password: ", false);
try {
callbackHandler.handle(callbacks);
username = ((NameCallback) callbacks[0]).getName();
password = ((PasswordCallback) callbacks[1]).getPassword();
if (debug) {
System.out.println("Username :" + username);
System.out.println("Password : " + password);
}
if (username == null || password == null) {
System.out.println("Callback handler does not return login data properly");
throw new LoginException(
"Callback handler does not return login data properly");
}
if (isValidUser()) { // validate user.
Profile profile = new Profile();
profile.setUsername(username);
succeeded = true;
return true;
}
} catch (IOException e) {
e.printStackTrace();
} catch (UnsupportedCallbackException e) {
e.printStackTrace();
}
return false;
}
This is my form authentication which pops up after requesting the profile.xhtml page (I configured this rule in web.xml ):
<form method=post action="j_security_check">
<p>
<span>Username:</span> <br /> <input type="text" name="j_username">
</p>
<p>
<span>Password:</span> <br /> <input type="password"
name="j_password">
</p>
<p>
<input type="submit" value="Login">
</p>
</form>
And this is a part of my profile's jsf page, associated to "profile.java" which is the managedBean for this page. I don't know how to get information after authentication and put it in the managedBean and serve it to he profile jsf page:
<h:form>
<h:outputText value="#{profile.username}"/><br/>
<h:outputText value="#{profile.password}"/><br/>
</h:form>
If you need other pieces of code, please let me know.
Thank you
Ok, now I am able to display some junk information in my profile.xhtml page using the #PostConstruct annotation like this:
#PostConstruct
private void prepareProfile(){
Subject subject = new Subject();
username = String.valueOf(subject.getPrincipals().size());
System.out.println(username);
}
However, I ignore how to grab information from the just-authenticated user.
Do you have any idea?
Thank you
This may come too late, but as I came across this page in my search this might do other people some good.
We resolved this by storing an authenticated user in the session.
You can retrieve the session from your CustomLoginModule using: ((HttpRequest)PolicyContext.getContext("javax.servlet.http.HttpServletRequest")).getSession()
You can then retrieve it from the session wherever you need it.
In your case, using JSF, you could store it in a session bean.
Injection will not automatically be performed in your CustomLoginModule due to reasons I will not go into here, you will need to ask for injection, for example in the initialize method.
An example method requesting injection for your class here:
public static <T> void programmaticInjection(Class clazz, T injectionObject) throws NamingException {
log.trace("trying programmatic injection for "+clazz.getSimpleName());
InitialContext initialContext = new InitialContext();
Object lookup = initialContext.lookup("java:comp/BeanManager");
BeanManager beanManager = (BeanManager) lookup;
AnnotatedType annotatedType = beanManager.createAnnotatedType(clazz);
InjectionTarget injectionTarget = beanManager.createInjectionTarget(annotatedType);
CreationalContext creationalContext = beanManager.createCreationalContext(null);
injectionTarget.inject(injectionObject, creationalContext);
creationalContext.release();
}
Call it like this: programmaticInjection(CustomLoginModule.class, this);
Then, inject your session bean, insert your user information, and use it in your profile backing bean as desired.
Related
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.
I'm using a custom policy to secure a page in a server-side Blazor app. All is working well except one of my policies requires knowing the query parameters of the request. For example, the URI path is something like https://mywebsite/profile/1234, which is used to view/edit the profile with id=1234. Obviously we only want the user with profileId = 1234 editing this page. How can I check for this in my IAuthorizationHandler?
I tried injecting the HttpContext and reading the request.Query items, but it's just always "/" or "/_blazor", because it's a SPA course. I tried injecting NavigationManager (formerly UriHelper) to get the URI from there, but got an error:
'RemoteNavigationManager' has not been initialized.
I also tried using the Resource parameter to pass the information into my handler. I couldn't find any examples of how to do this, so this is my attempt:
Here is my profile.razor code, where I am limiting access with Policy="CanEditProfile"
#inject NavigationManager NavigationManager
<AuthorizeView Policy="CanEditProfile">
<NotAuthorized>
<h2 class="mt-5">You are not authorized to view this page</h2>
</NotAuthorized>
<Authorized>
<div class="container my-profile">
<h2>My Profile</h2>
And my IAuthorizationHandler code:
public Task HandleAsync(AuthorizationHandlerContext context)
{
if (context == null || httpContextAccessor.HttpContext == null) return Task.CompletedTask;
// try getting path from httpContext
var path = httpContextAccessor.HttpContext.Request.Path.Value;
Console.WriteLine($"Path = {path}"); // this is always "/_blazor"
// try getting path from resource, passed in from blazor page component
var resource = context.Resource?.ToString();
Console.WriteLine($"Resource = {resource}"); // this is always null
var pendingRequirements = context.PendingRequirements.ToList();
foreach (var requirement in pendingRequirements)
{
if (requirement is EditMemberPermission)
{
// if this user is admin, then grant permission
var isAdmin = context.User.IsInRole("Admin");
if (isAdmin)
{
context.Succeed(requirement);
continue;
}
// get requested memberId from uri parameter, e.g. /profile/1234
var requestedMemberId = // How do I get this?
if (IsOwner(context.User, requestedMemberId))
{
context.Succeed(requirement);
}
}
}
return Task.CompletedTask;
}
Any ideas on how to achieve this? It seems like it would be a common scenario, to secure a page based on which page data (query param "id") the user is trying to access. Many of the examples mention securing a Resource, and show it as an optional parameter, but no examples I could find show actually passing a value and using it. How can you secure a resource if you don't know what the resource is?
I thought there might be a way to pass the Resource parameter from the .razor page to the Auth handler, like this, but I haven't gotten that to work either.
<AuthorizeView Policy="CanEditProfile" Resource="<pass url somehow?>" />
Thanks in advance.
I got this working by using this code in my profile.razor:
#page "/profile/{MemberId}"
<AuthorizeView Policy="CanEditProfile" Resource="#MemberId">
... page content
</AuthorizeView>
#code {
[Parameter]
public string MemberId { get; set; }
}
This gets the MemberId parameter from the route, and passes it as a Resource to my IAuthorizationHandler. In that handler method, I can fetch it like this:
public Task HandleAsync(AuthorizationHandlerContext context)
{
if (context == null) return Task.CompletedTask;
// get member id from resource, passed in from blazor page component
var resource = context.Resource?.ToString();
var hasParsed = int.TryParse(resource, out int requestedMemberId);
if (hasParsed)
{
// compare the requested memberId to the user's actual claim of memberId
var isAuthorized = requestedMemberId == context.User.GetMemberIdClaim();
// now we know if the user is authorized or not, and can act accordingly
}
I have a Java Servlet backend with a datastore connected to my app; I am trying to implement a login system using the Android Studio LoginActivity template, using the user's email and password (not the PlusBaseActivity handling the Google Account login), but I don't know how to proceed from here:
How can you say that a User is logged in? and how can I make it so persistently using my datastore? I've read here: How to login User using UserService on AppEngine Java that I just need to call the method resp.sendRedirect(userService.createLoginURL(req.getRequestURI())), and I've done so:
#Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
int size = checkDatastore(); // 0 if empty, > 0 if not empty
if(size==0){
populateDatastore();
}
String asyncMessage = req.getParameter("order");
if(asyncMessage.equals("login")){
mail = req.getParameter("email");
psw = req.getParameter("password");
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
String message="";
resp.setContentType("text/plain");
PrintWriter out = resp.getWriter();
if(user == null) {
//Sends a temporary redirect response to the client using the
// specified redirect location URL and clears the buffer.
String uri = userService.createLoginURL(req.getRequestURI());
resp.sendRedirect(uri);
User user1 = userService.getCurrentUser();
message="No one is logged in!\n" + "Sent from App Engine at " + new Date();
out.println(message);
out.flush();
}if(user !=null) {
// login(user);
message = "Hello, " + user.getEmail() +
", "+user.getNickname()+"!" + "\nSent from App Engine at "+ new Date();
out.println(message);
out.flush();
}
}
}
but the sendRedirect() method only gives me a URI. What for?
Moreover, the User user = userService.getCurrentUser() always returns null. How come?
That's because the resp.sendRedirect(userService.createLoginURL(req.getRequestURI()))
of UserService only works when integrating the Login with Google Accounts as shown in this documentation.
If you want to implement a personalised login system you can do that in many ways. Surely you will need a Servlet checking new users' data and a datastore to persistently store new account registrations.
I've got a problem with a LightSwitch 2011 web application using forms authentication.
I've implemented my own login screen which authenticates the user against the active directory. My code also checks to see if the user is assigned to a specific active directory group to decide if they can add / edit / delete data.
The login form is placed on the Login.aspx page. The button to login holds the following code:
protected void buttonLogin_Click(object sender, EventArgs e)
{
LdapAuthentication authentication = new LdapAuthentication();
try
{
bool isUserAdmin = false;
if (authentication.IsUserAuthenticated(textBoxUserName.Text, textBoxPassword.Text, ref isUserAdmin))
{
FormsAuthenticationTicket authenticationTicket = new FormsAuthenticationTicket(1,
textBoxUserName.Text, DateTime.Now, DateTime.Now.AddSeconds(1), false, String.Empty);
//Encrypt the ticket.
string encryptedTicket = FormsAuthentication.Encrypt(authenticationTicket);
//Create a cookie, and then add the encrypted ticket to the cookie as data.
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
//Add the cookie to the outgoing cookies collection.
Response.Cookies.Add(authCookie);
//If the everyoneAdmin is set to true the validation of the administratorgroup
//is decativated so we have to grant the current user administrator rights
if (everyoneAdmin)
isUserAdmin = true;
Session["isUserAdmin"] = isUserAdmin ;
Response.Redirect("default.htm");
}
}
catch (Exception ex)
{
labelError.Text = ex.Message;
labelError.Visible = true;
textBoxPassword.Text = String.Empty;
}
}
public bool IsUserAuthenticated(String userName, String password, ref bool isUserAdmin)
{
if (String.IsNullOrEmpty(userName) || String.IsNullOrEmpty(password))
return false;
String domain = String.Empty;
if (!String.IsNullOrEmpty(ConfigurationManager.AppSettings["Domain"]))
domain = Convert.ToString(ConfigurationManager.AppSettings["Domain"]).Trim();
else
throw new NullReferenceException("The Domain in the configuration must not be null!");
String ldpa = String.Empty;
if (!String.IsNullOrEmpty(ConfigurationManager.AppSettings["LDPA"]))
ldpa = String.Format("LDAP://{0}", Convert.ToString(ConfigurationManager.AppSettings["LDPA"]).Trim());
else
throw new NullReferenceException("The LDPA in the configuration must not be null!");
String administrationGroup = String.Empty;
if (!String.IsNullOrEmpty(ConfigurationManager.AppSettings["AdministratorGroup"]))
administrationGroup = Convert.ToString(ConfigurationManager.AppSettings["AdministratorGroup"]).Trim();
else
throw new NullReferenceException("The AdministrationGroup in the configuration must not be null!");
String domainUserName = String.Format(#"{0}\{1}", domain.Trim(), userName.Trim());
DirectoryEntry directoryEntry = new DirectoryEntry(ldpa, domainUserName, password);
try
{
//Bind to the native AdsObject to force authentication.
object obj = directoryEntry.NativeObject;
DirectorySearcher directorySearcher = new DirectorySearcher(directoryEntry);
directorySearcher.Filter = String.Format("(SAMAccountName={0})", userName.Trim());
directorySearcher.PropertiesToLoad.Add("cn");
directorySearcher.PropertiesToLoad.Add("memberOf");
SearchResult directorySearchResult = directorySearcher.FindOne();
//unable to find a user with the provided data
if (directorySearchResult == null)
return false;
if (directorySearchResult.Properties["memberof"] != null)
{
//If the memberof string contains the specified admin group
for (int i = 0; i < directorySearchResult.Properties["memberof"].Count; i++)
{
string temp = directorySearchResult.Properties["memberof"].ToString();
// get the group name, for example:
if (directorySearchResult.Properties["memberof"].ToString().ToLower().Contains(administrationGroup.ToLower()))
{
isUserAdmin = true;
break;
}
}
}
}
catch (Exception ex)
{
throw new Exception(String.Format("Error authenticating user.\n\rMessage:\n\r {0}", ex.Message));
}
return true;
}
In the class which holds the CanExcecute (server tier) methods I've implemented the following method:
public bool IsCurrentUserAdmin()
{
if (HttpContext.Current.Session["isUserAdmin"] == null)
return false;
return (bool)(HttpContext.Current.Session["isUserAdmin"]);
}
For example, the CanExcecute methods for one table
partial void dtFacilities_CanDelete(ref bool result)
{
result = this.IsCurrentUserAdmin();
}
partial void dtFacilities_CanInsert(ref bool result)
{
result = this.IsCurrentUserAdmin();
}
partial void dtFacilities_CanUpdate(ref bool result)
{
result = this.IsCurrentUserAdmin();
}
WebConfig
<authentication mode="Forms">
<form>s name=".ASPXAUTH"
loginUrl="Login.aspx"
protection="All"
timeout="30"
path="/"
requireSSL="false"
slidingExpiration="true"
defaultUrl="Home.aspx"
cookieless="UseUri" />
</authentication>
<authorization>
<deny users="?">
</deny></authorization>
Problems:
The problem is that if the user is idle for longer than the timeout the session times out. So, the session token isUserAdmin is NULL. At this point I want the application to return to the login screen. A Response.Redirect and a Server.Transfer did not work in the IsCurrentUserAdmin() method. How can I get the application to return the user to the login screen if the session token isUserAdmin is NULL?! Remember, the session token is set in the login.aspx page code behind
When the user closes the final tab of the Lightswitch application, the application opens a new tab and navigates past the login page and they are automatically logged in without processing the login process on the login.aspx page. This means that the session token isUserAdmin is NULL. This happens even if the user has not logged in before they closed the final tab of the application. This leads again to problem 1.
Thanks in advance!
If I understand your problem correctly, if, for whatever reason, isUserAdmin is set to NULL, you want to return the user to to the login screen.
In my application, I simply use a button that the user can click to log off. But the underlying method should work just the same in your case.
First create a new page called LogOff.aspx. The page itself, you can leave default generated code:
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
</div>
</form>
</body>
</html>
For the code behind, you'll want something like this (please check this, I converted from my project which is in VB):
using System.Web.Security;
namespace LightSwitchApplication
{
public partial class LogOff : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
FormsAuthentication.SignOut();
Response.Redirect("default.htm");
}
}
}
This is my code in which I use a button. But if you take the section where the Dispatcher calls Navigate and place it in your IsCurrentUserAdmin() method, it should do the same trick (again, check the C#):
using Microsoft.LightSwitch.Threading;
using System.Windows.Browser;
partial void btnLogOff_Execute()
{
Dispatchers.Main.Invoke(() =>
{
HtmlPage.Window.Navigate(new Uri("LogOff.aspx", UriKind.Relative));
});
}
In my experience, there is a bit of a gotcha in Lightswitch. If you were to execute as is, you would probably receive the following:
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its
dependencies) could have been removed, had its name changed, or is
temporarily unavailable. Please review the following URL and make
sure that it is spelled correctly.
Requested URL: /LogOff.aspx
The fix is this:
First right click your project name in Solution Explorer and Unload Project. Once the project is unloaded, right click it and Edit project_name.lsproj. Ctrl+F for default.htm. You're looking for the section where it is proceeded by _BuildFile. Copy that section from _BuildFile to /_BuildFile, paste below that section and modify as follows.
<_BuildFile Include="Server/LogOff.aspx">
<SubFolder>
</SubFolder>
<PublishType>
</PublishType>
</_BuildFile>
Now right click and Reload your project. If you get errors when trying to build, try Build | Clean and build again. If you run the application in Debug, this code will just reload the page. But once you publish and subsequently cause isUserAdmin to be NULL the code should log you out and take you back to the log on screen.
References:
Original MSDN Forum Thread
My experience implementing it
I'm trying to use Tridion's ContentManagment API to retrieve taxonomy categories and keywords, but I'm running into an Access denied error.
I have the following method:
public Dictionary<string, string> GetKeywords(string tcmUri)
{
var result = new Dictionary<string, string>();
try
{
// _settings.ImpersonationUser = "MYDOMAIN/myusername"
using (var session = new Session(_settings.ImpersonationUser))
{
var category = new Category(new TcmUri(tcmUri), session);
var keywords = category.GetKeywords(new Filter());
if (keywords != null && keywords.Count > 0)
{
foreach (var keyword in keywords)
{
result.Add(keyword.Id.ToString(), keyword.Title);
}
}
}
}
catch (Exception ex)
{
Logger.Log.Error(
"Failed to retrieve keywords for '{0}'.".FormatWith(tcmUri), ex);
}
return result;
}
The user I've got in _settings.ImpersonationUser has access to the Tridion Content Manager, is configured as an administrator, and has been added to Impersonation users in the "SDL Tridion Content Manager configuration" snap-in.
The error I'm getting is the following:
System.Runtime.InteropServices.COMException (0x80040302):
<?xml version="1.0"?>
<tcm:Error xmlns:tcm="http://www.tridion.com/ContentManager/5.0"
ErrorCode="80040302" Category="16" Source="Kernel" Severity="2">
<tcm:Line ErrorCode="80040302" Cause="true" MessageID="16226">
<![CDATA[Access denied for the user MYDOMAIN\myuser.]]
<tcm:Token>MYDOMAIN\myuser</tcm:Token>
</tcm:Line>
<tcm:Details>
<tcm:CallStack>
<tcm:Location>SystemBLST.GetUserContext</tcm:Location>
<tcm:Location>SystemBLST.IBLSecurityST_GetUserContext</tcm:Location>
</tcm:CallStack>
</tcm:Details>
</tcm:Error>
Does anyone have any clues to what I'm doing wrong?
Thanks in advance!
Here's a few things to understand when it comes to impersonation & Tridion...
The user executing the code should not have access to Tridion.
The user executing the code should be configured as a valid "Impersonation User"
The user that the code impersonates should be a valid Tridion user.
If all those 3 conditions are true, impersonation will work.
By executing the code, I mean the Windows account under which the code is being executed. If this account has access to Tridion, you do NOT need to use impersonation.
Hope this helps.