Liferay custom single-sign-on with redirect and parameters - authentication

this is my first question here and it's quite tricky as i didn't find enough relevant resources on the web. So I really hope, to get some help with this:
I am doing a custom single-sign-on with Liferay 6.2 GA4. Therefore I have developed a custom login-hook, that can handle my URL that looks like:
http://localhost:8080/c/portal/login?q=10C7vCMqv%2BlYThuxjdOmbUIXj1LKz9W3s5IUg%2F8H%2FvqRPUGOuzoC7pRT4hwVp2TpLf%2FnDRaGfpvoubhvSxlhidyX1SAowlvhdeWb95xRyxyCGZmKxE%2FZejk%2BIS5PvVHYIprBmM5Ec1cM%2Fq5dd5OGpEJJislrctRP
From this i decode the q-parameter to get the login parameters and other parameters, that i need to decide on which page I get redirected and i pass the attribute to the request
request.setAttribute("myParam", recordId);
and
return credentials;
I have also a LoginPostAction, that gets called afterwards and here it is, where it gets tricky:
public final void run(final HttpServletRequest request, final HttpServletResponse response)
throws ActionException {
...
final PortletURL redirectURL = PortletURLFactoryUtil.create(
request, "portlet_WAR_myportlet",
>>>> PortalUtil.getPlidFromPortletId(???, "portlet_WAR_myportlet"),
PortletRequest.RENDER_PHASE);
redirectURL.setParameter("myParam", String.valueOf(request.getAttribute("myParam")));
response.sendRedirect(redirectURL.toString());
}
So, I have the portlet name (PortletId) but I cannot get the Plid from this, as i don't have the ThemeDisplay at this time.
First question: How can i create a PortletURL at this time, which i can use in sendRedirect()?
Second: I have also tried to setAttribute and hardcode the sendRedirect("/urlToMyPortlet"), but I cannot use this at this time anymore as it gets lost afterwards (and is null) when i try to get it on the next page.
Any suggestions?
Thanks a lot in advance,
rom12three

Related

Implicit cast operator in ActionResult<bool> not working

I am following this guide to build my controllers. I do the following.
This is my controller:
// GET api/sth/{sthId}/isValid
[HttpGet("{sthId: int}/isValid")]
[ProducesResponseType(StatusCodes.Status200OK)]
[MyAuthorizeFilter]
public ActionResult<bool> Whatever(int sthId)
{
return this.myService.Whatever(sthId);
}
Theoretically, this should be converted to an Ok() ActionResult. However, If I write the following unit test:
[Fact]
public void Whatever()
{
this.myServiceMock.Setup(x => x.Whatever(It.IsAny<int>())).Returns(true);
-> I DEBUG HERE -> var result = this.myController.Whatever(1);
Assert.IsType<OkObjectResult>(result);
Assert.True((bool)result.Value);
}
I see that my result is an ActionResult<bool> indeed, whose Value is true as expected, but result.Result is null. So: no Ok action result whatsoever.
What am I missing? Do I have to write explicitly the return Ok() to get it? With the sentence
Implicit cast operators support the conversion of both T and ActionResult to ActionResult<T>. T converts to ObjectResult, which means return new ObjectResult(T); is simplified to return T;.
in the documentation I thought it was not necessary...?
The ActionResult<TValue> class:
wraps either an[sic] TValue instance or an ActionResult.
See also the source, its constructors assign either Value or Result, never both.
The MVC pipeline will assign a success status code to the response if no status code was explicitly set. But I can't find documentation for that claim.
This means the response as obtained in this test won't have a status code or OkActionResult anywhere. You can convert it to an ObjectResult, but that won't have a status code.
If you use something like swagger you will indeed get an OK from the server.
This happens to you because you dont perform an http request you simple call a method(your controller method) and you get a return type. You dont create a web server or something so no http status code is generated by .net core.
If you want to get status codes you should write test using http requests. Generally you could look up something like postman to perform your testing.

intermittent error from rally 'Not authorized to perform action: Invalid key' for POST request in chrome extension

I developed a chrome extension using Rally's WSAPI v2.0, and it basically does the following things:
get user and project, and store them
get current iteration everytime
send a post request to create a workitem
For the THIRD step, I sometimes get error ["Not authorized to perform action: Invalid key"] since end of last month.
[updated]Error can be reproduced everytime if I log in Rally website via SSO before using the extension to send requests via apikey.
What's the best practice to send subsequent requests via apikey in my extension since I can't control end users' habits?
I did see some similar posts but none of them is helpful... and in case it helps:
I'm adding ZSESSIONID:apikey in my request header, instead of user /
password to authenticate, so I believe no security token is needed
(https://comm.support.ca.com/kb/api-key-and-oauth-client-faq/kb000011568)
url starts with https://rally1.rallydev.com/slm/webservice/v2.0/
issue is fixed after clearing cookies for
https://rally1.rallydev.com/, but somehow it appears again some time
later
I checked the cookie when the issue was reproduced, and found one with name of ZSESSIONID and its value became something else rather than the apikey. Not sure if that matters though...
code for request:
function initXHR(method, url, apikey, cbFunc) {
let httpRequest = new XMLHttpRequest();
...
httpRequest.open(method, url);
httpRequest.setRequestHeader('Content-Type', ' application\/json');
httpRequest.setRequestHeader('Accept', ' application\/json');
httpRequest.setRequestHeader('ZSESSIONID', apikey);
httpRequest.onreadystatechange = function() {
...
};
return httpRequest;
}
...
usReq = initXHR ('POST', baseURL+'hierarchicalrequirement/create', apikey, function(){...});
Anyone has any idea / suggestion? Thanks a million!
I've seen this error when the API key had both read-only and full-access grants configured. I would start by making sure your key only has the full-access grant.

Javascript redirects on page->open

Using behat/mink, I'm testing the "remember me" functionality. Functionally, when the user visits the main page, javascript/ajax code verifies if the user is "remembered". If yes, then the javascripts redirects to another page. My LoginPage is defined with $path = '/login.html' - after the redirect, I will end up on /main.html.
In my context, I use $loginPage->open() - however this throws exception Expected to be on "https://example.com/login.html" but found "https://example.com/main.html" instead. Naturally, this aborts the execution and results in the test failing - yet this is exactly the behaviour I want.
How can I tell behat/mink to not verify URL or ignore URL mismatch?
If you check the open() method you will see that the check that fails for you is when is checking the url parameters in verify() method.
If you want to avoid this you can override this method to check only for the response and not the url parameters.
In order to do this you need to extend Page and declare a new method like this:
public function verify(array $urlParameters){
$this->verifyResponse();
}

#Auth Injection in ContainerFilter

I'm using DW 0.9.1 and it would be cool, if I could inject the #Auth XYzObject into some ContainerRequest or even better in a ContainerResponseFilter (or servlet filter).
Does anyone knows if this is possible?
The usecase: Some users does have different allowd access rates (rate limiting), e.g. max. 2 request per second and max 60 per Minute. This can be verified with the injected #Auth XYzObject.
In the end I can do this also in the Ressource, where this information is available, but as I said it would be cool to do this outside of my ressources in a filter or something else. And I do not want to do this is the authenticating/ authorization process, because rate limiting is not related to this. At the moment,all the variants I tried, nothing works, so it seems not possible, but I hope someone knows the trick.
How the #Auth annotation works is that it is handled by a ValueFactoryProvider, which is used only for (resource) method parameter injection. So you can't inject it into arbitrary locations.
However, when you created XyzObject you made it implement java.security.Principal. The reason DW makes you use this type, is because after it authenticates, it sets the Principal in the SecurityContext, as seen in the AuthFilter.
If you look at the implementation for AuthValueFactoryProvider, you will see that the way it obtains the Principal is by getting it from the SecurityContext. And that's how the Principal is injected with #Auth as a method argument.
In a ContainerRequestFilter (and in may other locations), you have access to the SecurityContext. In a filter you can get it with requestContext.getSecurityContext(). So in your filter you can get the Principal from the SecurityContext and just cast it
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Principal principal = requestContext.getSecurityContext().getUserPrincipal();
if (principal != null) {
XyzObject xyz = (XyzObject)principal;
}
}

AccessDeniedException when writing into JCR (JackRabbit) [Magnolia]

I wrote a request filter for geoIP localization. It works the way that I request an external service for the localization and then write the information into JCR, into a dedicated workspace for caching/storage.
On the author instance this works, but on the public instance I constantly get a AccessDeniedException. I probably need to authenticate with the JCR, and I tried that too, using the crendentials from the magnolia.properties file:
magnolia.connection.jcr.userId = username
magnolia.connection.jcr.password = password
And this code for authentication:
Session session = MgnlContext.getJCRSession(WORKSPACE_IP_ADDRESSES);
session.impersonate(new SimpleCredentials("username", "password".toCharArray()));
I have the this xml to bootstrap the filter, and a FilterOrdering Task, configured as follows:
tasks.add(new FilterOrderingTask("geoIp", new String[] { "contentType", "login", "logout", "csrfSecurity",
"range", "cache", "virtualURI" }));
What am I missing?
What would be the proper to write into the JCR in Magnolia on the public instance?
Yeah, that could not work :D
Is your filter configured in Magnolia's filter chain or directly in web.xml? It needs to live in filter chain and it needs to be configured somewhere down the chain after the security filters so that user is already authenticated.
Then you can simply call MgnlContext.getJCRSession("workspace_name") to get access to repo and do whatever you need.
HTH,
Jan