Custom login method in liferay 7 - liferay

I recently started learning liferay(7.1.2 ga3). my requirement is to change the login code(i.e write my own code for login) not look and feel in login.jsp.
I created a hook file with the following steps(in liferay developer studio) New -> Liferay Module Project -> Project Name as CustomLogin -> Build Type as Maven -> Project Template Name as war-hook -> then Finish.
After This folder was created with the name as CustomLogin and in that src -> main -> java -> CustomLogin -> I can see two Files CustomLoginLoginPostAction.java and CustomLoginStartupAction.java
in CustomLoginStartupAction.java
package CustomLogin;
import com.liferay.portal.kernel.events.ActionException;
import com.liferay.portal.kernel.events.SimpleAction;
public class CustomLoginStartupAction extends SimpleAction {
#Override
public void run(String[] lifecycleEventIds) throws ActionException {
for (String eventId : lifecycleEventIds) {
System.out.println("Startup event ID " + eventId);
}
}
}
in CustomLoginLoginPostAction.java
package CustomLogin;
import com.liferay.portal.kernel.events.Action;
import com.liferay.portal.kernel.model.User;
import com.liferay.portal.kernel.service.UserLocalServiceUtil;
import com.liferay.portal.kernel.util.PortalUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CustomLoginLoginPostAction extends Action
{
#Override
public void run(HttpServletRequest request, HttpServletResponse response)
{
long userId = PortalUtil.getUserId(request);
User user = UserLocalServiceUtil.fetchUser(userId);
System.out.println(user.getFirstName() + " has logged in.");
}
}
But I dont't know what to do after this. please help. or give some sample code.

Strong recommendations:
As you start learning Liferay, start with the current version, not with one from January 2019 (original 7.1.x release in 2018)
Start with OSGi plugins - they deploy a lot quicker than WAR archives and require a lot less memory at runtime
think about the requirement: You're using a platform that gives you the option to ignore all of the user management worries, and the first thing you do is to take back control of this delicate issue. Rather: Use the platform to your advantage. Consider using LDAP or SSO if you need to authenticate against another user database: That's configuration, not code - so nothing for you to maintain
That being said, the code that you posted adds to the login procedure. E.g. the LoginPostAction is executed after Liferay's own login code is already executed. This enables you to intercept a login and impose custom rules when you know what user you deal with (and that they authenticated correctly). Similarly, there's a similar LoginPreAction, that's allowing you to intercept the login process before the user's login is applied.
(A StartupAction is the wrong path - nothing to do with the login process - don't follow that path for this purpose)
What you typically do in these actions is to either deny access based on unconfigurable criteria (e.g. may the user log in from that IP? At this time?) or initialize some additional environments (e.g. triggering some backend action, initialize some information in the session). For this you can determine to do this before or after the login succeeded.
If - against my recommendation - you decide that you'll still want to customize the login process: This code for 7.0 should still work on 7.1 (and likely even on 7.4, the current version). It involves writing a custom login portlet, and the relevant code is in this class, after retrieving the HttpServletRequest (I'm omitting the presentation layer from the linked sample)
#Component(
property = {
"javax.portlet.name=MyLoginPortlet",
"mvc.command.name=/login/login"
},
service = MVCActionCommand.class
)
public class MyLoginMVCActionCommand 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;
AuthenticatedSessionManagerUtil.login(
request, response, login, password, rememberMe, authType);
actionResponse.sendRedirect(themeDisplay.getPathMain());
}
}
But really: Use your time to add business value and work on business problems. Be happy that the infrastructure layer is taken care of already.

Related

Fetching OpenIdConnectConfiguration while offline/no connection to AuthServer

I've been working on how to save OpenIdConnecConfiguration locally in the odd case that the AuthServer is not reachable but the frontend client (e.g. Phone) still has a valid refresh token which still needs to be validated again when signing in. It is also needed to be saved locally to a file in the case that the backend (e.g. WCF) has restarted due to a update or the frequent restarts it has (once a day)
What I've done so far, I've saved the JSON object of the ".well-known/openid-configuration" to a file/variable and now I want to create the OpenIdConnectConfiguration object.
OpenIdConnectConfiguration.Create(json) does a lot of the work but the signingKeys do not get created. I think maybe it's because the authorization endpoint needs to be created in some other manner maybe?
Or maybe I'm doing this the wrong way and there is another solution to this issue. I'm working in C#.
Edit: I know there are some caveats to what I'm doing. I need to check once in awhile to see if the public key has been changed, but security wise it should be fine to save the configuration because it's already public. I only need the public key to validate/sign the jwt I get from the user and nothing more.
Figured out a solution after looking through OpenIdConnectConfiguration.cs on the official github.
When fetching the OpenIdConnectConfiguration the first time, use Write() to get a JSON string and use it to save it to file.
Afterwards when loading the file, use Create() to create the OpenIdConnectConfiguration again from the JSON string (This had the issue of not saving the signingKeys as said in the question, but alas! there is a fix)
Lastly to fix the issue with the signingKeys not being created, (this is what I found out from the github class) all we need to do is loop through the JsonWebKeySet and create them as is done in the class. We already have all the information needed from the initial load and therefore only need to create them again.
I'll leave the code example below of what I did. I still need to handle checking if he key has been changed/expired which is the next step I'll be tackling.
interface IValidationPersistence
{
void SaveOpenIdConnectConfiguration(OpenIdConnectConfiguration openIdConfig);
OpenIdConnectConfiguration LoadOpenIdConnectionConfiguration();
}
class ValidationPersistence : IValidationPersistence
{
private readonly string _windowsTempPath = Path.GetTempPath();
private readonly string _fileName = "TestFileName";
private readonly string _fullFilePath;
public ValidationPersistence()
{
_fullFilePath = _windowsTempPath + _fileName;
}
public OpenIdConnectConfiguration LoadOpenIdConnectionConfiguration()
{
FileService fileService = new FileService();
OpenIdConnectConfiguration openIdConfig = OpenIdConnectConfiguration.Create(fileService.LoadFromJSONFile<string>(_fullFilePath));
foreach (SecurityKey key in openIdConfig.JsonWebKeySet.GetSigningKeys())
{
openIdConfig.SigningKeys.Add(key);
}
return openIdConfig;
}
public void SaveOpenIdConnectConfiguration(OpenIdConnectConfiguration openIdConfig)
{
FileService fileService = new FileService();
fileService.WriteToJSONFile(OpenIdConnectConfiguration.Write(openIdConfig), _fullFilePath);
}
}

Project Server Online CSOM - GeneralSecurityAccessDenied while reading TimePhase Assignments

This is my first SO question so please let me know if this question is not very clear or if I am missing anything.
FYI SO prevented me from attaching links, so sorry for all the bad formatting.
Overview
I'm trying to read (and write) the "Actual work" for a resource in Project Server Online by using the CSOM library available by Microsoft. Reading and writing the assignments and Actual work is working perfectly, as long as I am reading the assignments for the currently authenticated user. If I attempt to read this for another resource, I receive a GeneralSecurityAccessDenied error.
I've done this in the past using Impersonation, which is supposed to be called transparently in the background if the user has the StatusBrokerPermission, but it doesn't seem to be working for me. Impersonation has been removed in 2013+, so that's no longer an option.
Problem summary
The CSOM is supposed to transparently enable statusing extensions to allow status updates to be made for resources other than the currently authenticated user (as long as the user has the status broker permission). This works fine for adding new assignments, but does not work when trying to update actual TimePhased hours via the TimePhased assignments. The assignments cannot be queried, and thus, we cannot call SubmitAllStatusUpdates to submit the hours.
Research
Usage scenarios for the CSOM: https:// msdn.microsoft.com/en-us/library/office/jj163082(v=office.15).aspx#pj15_WhatTheCSOM_UsageScenarios
Impersonation Deprecated: https:// msdn.microsoft.com/en-us/library/office/ee767690(v=office.15).aspx#pj15_WhatsNew_Deprecated)
Picture: Supposed to read on behalf of another user...
People with the same problem # 1: https:// social.technet.microsoft.com/Forums/projectserver/en-US/dccdb543-18a1-4a0e-a948-5d861305516e/how-to-get-resource-assignments-summary-view-data-project-server-online-2013?forum=projectonline)
People with the same problem # 2: http:// uzzai.com/ZB43wp95/ps2013-app-how-to-read-and-update-timephased-data-with-jsom-javascript-csom.html
People with the same problem # 4: https:// social.technet.microsoft.com/Forums/Sharepoint/en-US/be27d497-e959-44b6-97cb-8f19fe0278fe/csom-how-to-set-timephase-data-on-an-assignment?forum=project2010custprog
Other things I've tried
Using the CSOM with the MsOnlineClaimsHelper to retrieve the FedAuth cookies for a user (and assigning them using the CookieContainer).
Using the REST/OData API.
a) https:// URL.sharepoint.com/sites/pwa/_api/ProjectServer/EnterpriseResources('c39ba8f1-00fe-e311-8894-00155da45f0e')/Assignments/GetTimePhaseByUrl(start='2014-12-09',end='2014-12-09')/assignments
Enabling the "StatusBrokerPermission" for the user
Unchecking the “Only allow task updates via Tasks and Timesheets.” Option within the server settings screen (Task settings and display).
Creating a SharePoint-hosted app and using JSOM code equivalent to the CSOM code above.
a) The code we wrote was JavaScript being executed from within SharePoint app, so we did not need to provide authentication. The user who was logged in had the StatusBrokerPermission.
Using a Provider-hosted SharePoint app and using the CSOM code above. We tried using all authentication methods for CSOM above, with an additional test:
a) using Fiddler to view the FedAuth cookies being set by the SharePoint app authentication, and overriding the WebRequest to manually insert the FedAuth/rtFA cookies: webRequestEventArgs.WebRequestExecutor.WebRequest.CookieContainer = getStaticCookieContainer();
Using timesheets to submit time phased data.
a) We can only create a timesheet for the currently-authenticated user, and cannot populate timesheet lines with projects / assignments not available to him (or a GeneralItemDoesNotExist error is thrown).
Manually issuing a “SubmitAllStatusUpdates” CSOM request using fiddler, as a different user.
a) The purpose of this test was to determine if we can write time phased data, even if we can’t read it.
Making sure the project was checked out to the current user.
Using administrative delegation for a resource.
Setting all available options within project permissions.
Using the Project Web UI to enter the TimePhased data for other resources.
Using SharePoint permission mode instead of Project Permission Mode.
The code
See failing code screenshot here
using System;
using System.Security;
using Microsoft.ProjectServer.Client;
using Microsoft.SharePoint.Client;
namespace ProjectOnlineActuals
{
static class Program
{
const string projectSite = "https://URL.sharepoint.com/sites/pwa/";
private const string edward = "c39ba8f1-00fe-e311-8894-00155da45f0e";
private const string admin = "8b1bcfa4-1b7f-e411-af75-00155da4630b";
static void Main(string[] args)
{
TestActuals();
}
private static void TestActuals()
{
Console.WriteLine("Attempting test # 1 (login: admin, resource: admin)");
TestActuals("admin#URL.onmicrosoft.com", "123", admin);
Console.WriteLine("Attempting test # 2 (login: admin, resource: edward)");
TestActuals("adminy#hmssoftware.onmicrosoft.com", "123", edward);
Console.ReadLine();
}
private static void TestActuals(string username, string password, string resourceID)
{
try
{
using (ProjectContext context = new ProjectContext(projectSite))
{
DateTime startDate = DateTime.Now.Date;
DateTime endDate = DateTime.Now.Date;
Login(context, username, password);
context.Load(context.Web); // Query for Web
context.ExecuteQuery(); // Execute
Guid gResourceId = new Guid(resourceID);
EnterpriseResource enterpriseResource = context.EnterpriseResources.GetByGuid(gResourceId);
context.Load(enterpriseResource, p => p.Name, p => p.Assignments, p => p.Email);
Console.Write("Loading resource...");
context.ExecuteQuery();
Console.WriteLine("done! {0}".FormatWith(enterpriseResource.Name));
Console.Write("Adding new resource assignment to collection...");
enterpriseResource.Assignments.Add(new StatusAssignmentCreationInformation
{
Comment = "testing comment - 2016-02-17",
ProjectId = new Guid("27bf182c-2339-e411-8e76-78e3b5af0525"),
Task = new StatusTaskCreationInformation
{
Start = DateTime.Now,
Finish = DateTime.Now.AddDays(2),
Name = "testing - 2016-02-17",
}
});
Console.WriteLine("done!");
Console.Write("Trying to save new resource assignment...");
enterpriseResource.Assignments.Update();
context.ExecuteQuery();
Console.WriteLine("done!");
Console.Write("Loading TimePhase...");
TimePhase timePhase = enterpriseResource.Assignments.GetTimePhase(startDate.Date, endDate.Date);
context.ExecuteQuery();
Console.WriteLine("done!");
Console.Write("Loading TimePhase assignments...");
context.Load(timePhase.Assignments);
context.ExecuteQuery();
Console.WriteLine("done! Found {0} assignments.".FormatWith(timePhase.Assignments.Count));
Console.WriteLine("Updating TimePhase assignments...");
foreach (var assignment in timePhase.Assignments)
{
Console.WriteLine("Updating assignment: {0}. ActualWork: {1}".FormatWith(assignment.Name, assignment.ActualWork));
assignment.ActualWork = "9h";
assignment.RegularWork = "3h";
assignment.RemainingWork = "0h";
}
timePhase.Assignments.SubmitAllStatusUpdates("Status update comment test 2016-02-17");
context.ExecuteQuery();
Console.WriteLine("done!");
Console.WriteLine("Success (retrieved & updated {0} time phase assignments)!".FormatWith(timePhase.Assignments.Count));
}
}
catch (Exception ex)
{
if (ex.ToString().Contains("GeneralSecurityAccessDenied"))
Console.WriteLine("ERROR! - GeneralSecurityAccessDenied");
else
throw;
}
finally
{
Console.WriteLine();
Console.WriteLine();
}
}
private static void Login(ProjectContext projContext, string username, string password)
{
var securePassword = new SecureString();
foreach (char c in password)
securePassword.AppendChar(c);
projContext.Credentials = new SharePointOnlineCredentials(username, securePassword);
}
static string FormatWith(this string str, params object[] args)
{
return String.Format(str, args);
}
}
}
Can anyone help??

ServiceStack Json Serializer ignore properties

I have a business requirement to only send permissioned properties in our response payload. For instance, our response DTO may have several properties, and one of them is SSN. If the user doesn't have permissions to view the SSN then I would never want it to be in the Json response. The second requirement is that we send null values if the client has permissions to view or change the property. Because of the second requirement setting the properties that the user cannot view to null will not work. I have to still return null values.
I have a solution that will work. I create an expandoObject by reflecting through my DTO and add only the properties that I need. This is working in my tests.
I have looked at implementing ITextSerializer. I could use that and wrap my response DTO in another object that would have a list of properties to skip. Then I could roll my own SerializeToString() and SerializeToStream(). I don't really see any other ways at this point. I can't use the JsConfig and make a SerializeFn because the properties to skip would change with each request.
So I think that implementing ITextSerializer is a good option. Are there any good examples of this getting implemented? I would really like to use all the hard work that was already done in the serializer and take advantage of the great performance. I think that in an ideal world I would just need to add a check in the WriteType.WriteProperties() to look and the property is one to write, but that is internal and really, most of them are so I can't really take advantage of them.
If someone has some insight please let me know! Maybe I am making the implementation of ITextSerialzer a lot harder that it really is?
Thanks!
Pull request #359 added the property "ExcludePropertyReference" to the JsConfig and the JsConfigScope. You can now exclude references in scope like I needed to.
I would be hesitant to write my own Serializer. I would try to find solutions that you can plug in into the existing ServiceStack code. That way you will have to worry less about updating dlls and breaking changes.
One potential solution would be decorating your properties with a Custom Attributes that you could reflect upon and obscure the property values. This could be done in the Service before Serialization even happens. This would still include values that they user does not have permission to see but I would argue that if you null those properties out they won't even be serialized by JSON anyways. If you keep all the properties the same they you will keep the benefits of strong typed DTOs.
Here is some hacky code I quickly came up with to demonstrate this. I would move this into a plugin and make the reflection faster with some sort of property caching but I think you will get the idea.
Hit the url twice using the following routes to see it in action.
/test?role
/test?role=Admin (hack to pretend to be an authenticated request)
[System.AttributeUsage(System.AttributeTargets.Property)]
public class SecureProperty : System.Attribute
{
public string Role {get;set;}
public SecureProperty(string role)
{
Role = role;
}
}
[Route("/test")]
public class Test : IReturn
{
public string Name { get; set; }
[SecureProperty("Admin")]
public string SSN { get; set; }
public string SSN2 { get; set; }
public string Role {get;set;}
}
public class TestService : Service
{
public object Get(Test request)
{
// hack to demo roles.
var usersCurrentRole = request.Role;
var props = typeof(Test).GetProperties()
.Where(
prop => ((SecureProperty[])prop
.GetCustomAttributes(typeof(SecureProperty), false))
.Any(att => att.Role != usersCurrentRole)
);
var t = new Test() {
Name = "Joe",
SSN = "123-45-6789",
SSN2 = "123-45-6789" };
foreach(var p in props) {
p.SetValue(t, "xxx-xx-xxxx", null);
}
return t;
}
}
Require().StartHost("http://localhost:8080/",
configurationBuilder: host => { });
I create this demo in ScriptCS. Check it out.

Spring security integration with open id in grails

I am working on Integrating spring security with openId for my grails Application using springsecurity core and springsecurity openid plugins. I have integrated it, and it works well but I need to access the email for the logged in person. How can I get that, all that I am able to access is a token which is used for identifying the person.
Thanks to Ian Roberts.
He gives me this reply,Which exactly solves my problem.
His reply was:
As it happens I implemented exactly this in one of my applications
yesterday :-) Unfortunately it's not an open-source app so I can't just
point you at my code but I can explain what I did.
The spring-security-openid plugin supports the "attribute exchange"
mechanism of OpenID, although the support is not documented much (if at
all). How well it works depends on the provider at the far end but this
at least worked for me using Google and Yahoo.
In order to request the email address from the provider you need to add
the following to Config.groovy:
grails.plugins.springsecurity.openid.registration.requiredAttributes.email
= "http://axschema.org/contact/email"
Now to wire that into your user registration process you need an email
field in your S2 user domain class, and you need to edit the generated
OpenIdController.groovy in a few places.
add an email property to the OpenIdRegisterCommand
in the createAccount action there's a line
"if(!createNewAccount(...))" which passes the username, password and
openid as parameters. Change this along with the method definition to
pass the whole command object instead of just these two fields.
in createNewAccount pass the email value forward from the command
object to the User domain object constructor.
And finally add an input field for email to your
grails-app/views/openId/createAccount.gsp.
You can do the same with other attributes such as full name.
grails.plugins.springsecurity.openid.registration.requiredAttributes.fullname
= "http://axschema.org/namePerson"
The important thing to wire it together is that the thing after the last
dot following requiredAttributes (fullname in this example) must match
the name of the property on the OpenIdRegisterCommand.
Regards
Charu Jain
I've never used the springsecurity openid plugin, but when using springsecurity core you can expose additional information about the current user by implmenting a custom UserDetails. In my app, I added this implementation, so that I can show the name property of logged-in users. You'll need to change this slightly, so that the email address is exposed instead
/**
* Custom implementation of UserDetails that exposes the user's name
* http://grails-plugins.github.com/grails-spring-security-core/docs/manual/guide/11%20Custom%20UserDetailsService.html
*/
class CustomUserDetails extends GrailsUser {
// additional property
final String name
CustomUserDetails(String username,
String password,
boolean enabled,
boolean accountNonExpired,
boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<GrantedAuthority> authorities,
long id,
String displayName) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities, id)
this.name = displayName
}
}
You then need to create a custom implementation of UserDetailsService which returns instances of the class above
class UserDetailsService implements GrailsUserDetailsService {
/**
* Some Spring Security classes (e.g. RoleHierarchyVoter) expect at least one role, so
* we give a user with no granted roles this one which gets past that restriction but
* doesn't grant anything.
*/
static final List NO_ROLES = [new GrantedAuthorityImpl(SpringSecurityUtils.NO_ROLE)]
UserDetails loadUserByUsername(String username, boolean loadRoles) {
return loadUserByUsername(username)
}
UserDetails loadUserByUsername(String username) {
User.withTransaction { status ->
User user = User.findByUsername(username)
if (!user) {
throw new UsernameNotFoundException('User not found', username)
}
def authorities = user.authorities.collect {new GrantedAuthorityImpl(it.authority)}
return new CustomUserDetails(
user.username,
user.password,
user.enabled,
!user.accountExpired,
!user.passwordExpired,
!user.accountLocked,
authorities ?: NO_ROLES,
user.id,
user.name)
}
}
}
You need to register an instance of this class as a Spring bean named userDetailsService. I did this by adding the following to Resources.groovy
userDetailsService(UserDetailsService)

Sub domain issue

basic overview...
i have a site setup in iis...
- "mysite" (wwwroot\mysite) under that there are 2 virtual directory applications
- "uploads" (\uploadfiles)
- "app" (wwwroot\myapp)
I also have a subdomain that is set up as a different site in iis...
- "beta.mysite" (wwwroot\mysitebeta) under that there are 2 virtual directory
- "uploads" (\uploadfiles)
- "app" (wwwroot\myappbeta)
the sub domain is working fine.... i can type in https://beta.mysite.com/app ... and it brings up the beta site log in perfectly fine.... the problem is, when i click on any of the buttons that create a post back... it reverts to https://www.mysite.com/app...
all of the links display the correct relative path to their files.... and if i type in https://beta.mysite.com/app/dir/page.aspx... it will actually go to that page on the beta site, all the links are going to the right spots... its just the postbacks that are killing me...
Have you tried setting a different application pool for these two websites? Looks like it's trying to be "smart" and concludes that the two virtual directories are actually the same website.
If all else fails, you could rewrite the postback URL in the FORM-tag that ASP.NET generates manually. Using an App_Browsers file and a ControlAdapter are probably the cleanest way of doing that.
I have an example of such a ControlAdapter implementation, though it is intended to work with URL rewriting to prevent reverting to the actual behind-the-scenes URL on postback. However, I think it would work for your problem out-of-the-box
public class FormRewriterControlAdapter : System.Web.UI.Adapters.ControlAdapter
{
protected override void Render(HtmlTextWriter writer)
{
base.Render(new RewriteFormHtmlTextWriter(writer));
}
}
public class RewriteFormHtmlTextWriter : HtmlTextWriter
{
private const string contextItemKey = "FormActionWritten";
public RewriteFormHtmlTextWriter(HtmlTextWriter writer) : base(writer)
{
InnerWriter = writer.InnerWriter;
}
public RewriteFormHtmlTextWriter(System.IO.TextWriter writer) : base(writer)
{
base.InnerWriter = writer;
}
public override void WriteAttribute(string name, string value, bool fEncode)
{
// If the attribute we are writing is the "action" attribute, and we are not on a sub-control,
// then replace the value to write with the raw URL of the request - which ensures that we'll
// preserve the PathInfo value on postback scenarios
if (name == "action" && !HttpContext.Current.Items.Contains(contextItemKey))
{
// Use the Request.RawUrl property to retrieve the un-rewritten URL
value = HttpContext.Current.Request.RawUrl;
HttpContext.Current.Items[contextItemKey] = true;
}
base.WriteAttribute(name, value, fEncode);
}
}
Form.browser file:
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.HtmlControls.HtmlForm" adapterType="FormRewriterControlAdapter" />
</controlAdapters>
</browser>
</browsers>

Resources