Play Framework and Deadbolt redirect onAuthFailure - java

I have implemented my own Authenticator from Play Framework and DeadboltHandler from Deadbolt.
Using the methods onUnauthorized respective onAuthFailure I can send users that are not logged in to the "login page" instead of the actual page they are trying to access.
However, instead of sending a user directly to the "login page", I want to specify what page the user should be sent to depending on which page the user tries to reach. For example, if the user tries to access /settings the user should be redirected to the login page. If the user tries to access /player/1 the user should be redirected to another page, say, "create user" page.
I was hoping that there is some smart way to do this with annotations, something like: #someannotation(redirect = route/id) so I can redirect to the relevant route if the user is not logged in, else to the standard "login page".
Any one got any ideas?
Code snippet example for controller and route method:
#Security.Authenticated(Secured.class)
#SubjectPresent(content = "createuser")
#DeferredDeadbolt
public class Settings extends Controller {
#SubjectPresent(content = "login")
#CustomRestrict(value = { #RoleGroup({ UserRole.player}), #RoleGroup(UserRole.server_owner) })
public static Result settings() {
Code snippet example for DeadboltHandler onAuthFailure:
#Override
public F.Promise<Result> onAuthFailure(Http.Context context, String content) {
return F.Promise.promise(new F.Function0<Result>() {
#Override
public Result apply() throws Throwable {
System.out.println(content);

There are a couple of different ways you can do this.
Approach 1: Repurpose the content value
In this approach, you can use the content value of the constraint annotations to give a hint to the handler. You can use a class-level constraint to define the default redirect, e.g. go to the login page, and method-level constraints to override the default redirect. All constraints have the content value, I'm just using SubjectPresent as an example; you can also mix constraints, e.g. have SubjectPresent at the class level and Restrict at the method level.
#SubjectPresent(content = "login")
public class FooController extends Controller {
public Result settings() {
// ...
}
public Result somethingElse() {
// ...
}
#SubjectPresent(content = "create-user")
public Result viewUser() {
// ...
}
}
In your DeadboltHandler implementation, you would then need a test on the content:
public CompletionStage<Result> onAuthFailure(final Http.Context context,
final Optional<String> content) {
return CompletableFuture.supplyAsync(() -> content.map(redirectKey -> {
final Result result;
if ("login".equals(redirectKey)) {
result = [redirect to login action]
}
else if ("create-user".equals(redirectKey)) {
result = [redirect to create user action]
} else {
result = [redirect to default authorization failure action]
}
}).orElseGet(() -> [redirect to default authorization failure action]), executor);
}
Approach 2: Use the ROUTE_PATTERN tag
Instead of specifying keys in the constraint annotations, you can instead use the route specified in the request to determine the requested action.
public CompletionStage<Result> onAuthFailure(final Http.Context context,
final Optional<String> content) {
final String route = requestHeader.tags().get(Router.Tags.ROUTE_PATTERN);
// examine the route and work out what you want to do
}

Related

Jersey Conditional Pathing

I have a scenario where we support 2 different types of authenticated users (UserTypeA, UserTypeB), but they will never be used in the same server environment. Right now, we use 2 different url paths /path/usertypea/list vs /path/usertypeb/list. We would like to make them use the same path if possible, for example /path/list, and have an environment variable be the condition to know where to route the traffic. The parameters for each user type aren't exactly the same, there are some differences in how the data is organized. We're using Jersey.
I've tried a few things like Singleton classes: https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest/user-guide.html#d0e2650 / https://stackoverflow.com/a/33585724/12183373 but it never routes the value, it just returns the name of the class instead of the JSON payload I'm expecting.
Here's some of the code:
#Path("/list")
public class GlobalSegmentServiceRouter {
#GET
#Produces("application/json")
public Class<?> findAll() {
boolean isUserTypeA = false;
if (isUserTypeA) {
return UserTypeAService.class;
} else {
return UserTypeBService.class;
}
}
}
Then I have 2 separate class files for the following:
#Singleton
public class UserTypeAService {
public List<String> findAll(/*Parameters for A*/) {
// Do work here for User Type A
}
}
#Singleton
public class UserTypeBService {
public List<String> findAll(/*Parameters for B*/) {
// Do work here for User Type B
}
}
When I try and hit the endpoint, this is the response I get:
"com.test.services.UserTypeAService"
Any suggestions on how to accomplish this?
add some flag for checking which kind of user is logged in to a custom principal impl. Then you can inject the current user and then call UserTypeAService.findAll or UserTypeBService.findAll in your method.
#GET
#Path("/path/list")
public String yourMethod(#Context SecurityContext securityContext)

Play Framework 2.7: How to update session within a Action composition in Java

I'm trying to update an app from to Play 2.7. I see that now the access to the session object via Http.Context is deprecated. Instead I have to use the Http.Request object. Additionally before I could just change the Session object right away - now it seems like I have to create a new Session and add to the Result by myself. But how can I achieve this within an Action composition where I don't have access to the Result object?
An Action composition can look like:
public class VerboseAction extends play.mvc.Action.Simple {
public CompletionStage<Result> call(Http.Request req) {
...
return delegate.call(req);
}
}
I can't see how to add something to the Session here!
EDIT:
I couldn't find an easy solution but a workaround with a second action annotation. It's possible to access the Result object via .thenApply and attache the new Session object.
public CompletionStage<Result> call(Http.Request request) {
return delegate.call(request).thenApply(result -> {
Http.Session session = ... change the session
return result.withSession(session);
});
}
Still if someone has a better idea how to change the Session directly in the action composition please feel free to answer.
A session in cleared by withNewSession(). A new session is created when you add something with addingToSession(...), perhaps after a login. Here is my complete working code : I have 2 timestamp : one for the log file and one for an application timeout.
public class ActionCreator implements play.http.ActionCreator {
private final int msTimeout;
#Inject
public ActionCreator(Config config) {
this.msTimeout = config.getInt("application.msTimeout");
}
#Override
public Action<?> createAction(Http.Request request, Method actionMethod) {
return new Action.Simple() {
#Override
public CompletionStage<Result> call(Http.Request req) {
// add timestamp for the elapsed time in log
req.getHeaders().addHeader("x-log-timestamp", "" + System.currentTimeMillis());
// did a session timeout occur
boolean timeout = SessionUtils.isTimeout(req, msTimeout);
// apply current action
return delegate.call(req).thenApply(result -> {
// display some info in log
Utils.logInfo(req);
// return final result
if (timeout) {
return result.withNewSession();
} else if (SessionUtils.isOpen(req)) {
return result.addingToSession(req, "timestamp", "" + System.currentTimeMillis());
} else {
return result;
}
});
}
};
}
}

Play Framework 2 Java Optional authentication

I have been playing around with authentication. I want to be able to have some extra functions on certain pages available for those who log in. The problem is that if I don't use the #Security.Authenticated(Secured.class) annotation for the controller class I cannot get the username from the session so I cannot check if the user is logged in or not.
How should I go about this? Should I make sure all pages are authenticated and then have some sort of a guest login that automatically gets used for those other sessions or is there a way to check if the user is logged in even on a class without the #Security.Authenticated(Secured.class) annotation.
It would be great if someone could point me in the right direction, if there is a tutorial available that does this or just some guidance.
You should do two things:
Prevent unauthenticated users from viewing the functionality in your template:
#if(session().containsKey(Secured.SESSION_AUTH_KEY)) { /* Your comment form */ }
Prevent unauthenticated users from accessing your action:
#Security.Authenticated(Secured.class)
public static Result submitComment() {
...
}
With:
public class Secured extends Security.Authenticator {
public static final String SESSION_AUTH_KEY = "email";
public String getUsername(Http.Context context) {
return context.session().get(SESSION_AUTH_KEY);
}
public Result onUnauthorized(Http.Context context) {
...
}
}

Cyclic Interference Error with Nested Lambdas

I am attempting to create a registration handler for my Play Framework application but I am running into some trouble. The goal of this code is to grab the data that was submitted and then check if the username or email is in use. This is done using methods built into my User class, which is a model.
The register method:
public static F.Promise<Result> register() {
// Get the POST data and turn it into something we can read from
DynamicForm form = Form.form().bindFromRequest();
// Get the email, username, and password
String email = form.get("email");
String username = form.get("username");
String password = form.get("password");
return User.findByUsername(username).map(user -> {
// For .map to run we must not have encountered an error, this means
// a user already has this username (otherwise the doc would not exist)
return redirect("/register");
}).recover(userCheckError -> {
// For .recover to run there must have been an error. This will happen
// if a doc was not found with the username (therefore it is not in use)
return User.findByEmail(email).map(option -> { // ERROR START
// We now have an F.Option object. If the email is already taken,
// then the Option is defined (has a value within it), if the email
// is free, the Option will be undefined (no value within it)
if (option.isDefined()) {
return redirect("/register");
} else {
return redirect("/register");
}
}); // ERROR END
});
}
The findByUsername and findByEmail methods (in the User class):
public static F.Promise<User> findByUsername(String username) {
return bucket.get(username.toLowerCase(), User.class);
}
public static F.Promise<F.Option<User>> findByEmail(String email) {
return bucket.find("users", "by_email", QueryHelper.findOne(email), User.class).map(result ->{
if (result.isEmpty()) {
return F.Option.None();
} else {
User user = result.iterator().next();
return F.Option.Some(user);
}
});
}
Edit: I have added comments to the register method and removed some unneeded code. I have also labeled where the error starts and ends (the lines that IntelliJ is highlighting. The exact error is "bad return type in lambda expression". The register method needs to return a Promise<Result> but that block of code is returning a Promise<B> (generic?).
Javadocs:
http://www.playframework.com/documentation/2.3.x/api/java/play/libs/F.Promise.html
http://www.playframework.com/documentation/2.3.x/api/java/play/libs/F.Option.html

How do I use autologin in liferay?

I want to login my users automatically from our application. I know liferay has an auto login feature, but I don't know how to use it. I didn't find much valuable information on the web.
What do I need to do to make autologin work?
I want to login a user automaticaly when he clicks a link, without him having to enter name and password. The name and password is saved on our application database.
I believe the OP has no use for an answer now. Nonetheless, this deserves a comprehensive answer. In fact, I am surprised that it does not have one yet.
First of all, this is a bad idea: such an arrangement as the one proposed by the OP is really too insecure. Nevertheless, a solution to the described problem can be a good prototype for someone creating an autologin for Liferay.
Now, let us say you want to automatically log in any user whose screen name is sent in a query string parameter. For example, if one access http://localhost:8080/web/guest/home?insecurely_login_user=juju then the Liferay in the juju user should be logged in. How to do that? Follow the steps below:
Create the autologin class
Firstly, create a hook plugin. In its docroot/WEB-INF/src directory, creates a class implementing the com.liferay.portal.security.auth.AutoLogin interface. In my example, I will call it br.brandizzi.adam.liferay.insecure.InsecureAutoLogin.
The AutoLogin interface has only one method, called login(), which expects two parameters (an HttpServletRequest and an HttpServletResponse instances) and returns an array of strings. So, my class will look like this without implementation:
public class InsecureAutoLogin implements AutoLogin {
#Override
public String[] login(HttpServletRequest request,
HttpServletResponse response) throws AutoLoginException {
// TODO Auto-generated method stub
return null;
}
}
The AutoLogin.login() method will try to retrieve the information necessary to the authentication from many sources, mainly the request object. If it decides that the user should be logged in, it returns an array with relevant data for authentication; if it decides to not log the user in, it can just return null.
In our case, we try to get the name of the user from the the insecurely_login_user parameter from the request. If there is such parameter, we will proceed with the login; if there is no such parameter, it just returns null:
String screenName = request.getParameter("insecurely_login_user");
if (screenName == null || screenName.isEmpty()) {
return null;
}
So we have the screen name. What to do now? Let us get a user from the database with the same screen name.
long companyId = PortalUtil.getCompanyId(request);
User user = UserLocalServiceUtil.getUserByScreenName(companyId,
screenName);
If a user wich such a screen name exists, it will be retrieved and attributed to the user variable. In this case, the authentication should be successful and the autologin class should return an array of three strings - the credentials. Those are the values to be returned as credentials, in the order they should appear in the array:
the user id as a string
the password of the user, which can be encrypted or not;
a boolean value, cast to string, indicating if the password is encrypted.
So here is the line:
return new String[] {
String.valueOf(user.getUserId()),
user.getPassword(),
String.valueOf(user.isPasswordEncrypted())
};
If a user is not found, however, an exception will be thrown. So, we have to surround the code above with a try/catch construction. If an exception is thrown, just return null:
try {
long companyId = PortalUtil.getCompanyId(request);
User user = UserLocalServiceUtil.getUserByScreenName(companyId,
screenName);
return new String[] { String.valueOf(user.getUserId()),
user.getPassword(),
String.valueOf(user.isPasswordEncrypted()) };
} catch (Exception e) {
return null;
}
In the end, this is my InsecureAutoLogin class:
public class InsecureAutoLogin implements AutoLogin {
public String[] login(HttpServletRequest request,
HttpServletResponse response) throws AutoLoginException {
String screenName = request.getParameter("insecurely_login_user");
if (screenName == null || screenName.isEmpty())
return null;
try {
long companyId = PortalUtil.getCompanyId(request);
User user = UserLocalServiceUtil.getUserByScreenName(companyId,
screenName);
return new String[] { String.valueOf(user.getUserId()),
user.getPassword(),
String.valueOf(user.isPasswordEncrypted()) };
} catch (Exception e) {
return null;
}
}
}
Registering the autologin class
Now our hook should register this class as an autologin processor. That is really easy.
First, edit the file docroot/WEB-INF/liferay-hook.xml adding a portal-properties element with the value portal.properties:
<?xml version="1.0"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 6.1.0//EN" "http://www.liferay.com/dtd/liferay-hook_6_1_0.dtd">
<hook>
<portal-properties>portal.properties</portal-properties>
</hook>
Now, create a file named portal.properties at docroot/WEB-INF/src. It should contain a property named auto.login.hooks whose value should be the name of our class:
auto.login.hooks=br.brandizzi.adam.liferay.insecure.InsecureAutoLogin
And that is it. Deploy this hook and your autologin will work.
Conclusion
As I have said, you should not use such an unsafe "authentication" method. It is too easy to bypass it, getting even administration permissions! However, if you follow these steps, you have a skeleton to create a better autologin feature. Also, I know some people really want to do something like this insecure "authentication" method and sometimes we have to suspend our judgments and just help one to shoot one's feet...
The source code of this project can be found here and you can download the WAR here.
Step 1: Create a class CustomLoginFilter and implements from AutoLogin interface.Override login method. Code as follows.
public String[] login(HttpServletRequest req, HttpServletResponse response)throws AutoLoginException {
//Get the login parameter
String loginEmailId = ParamUtil.getString(req, “_58_login”);
String password = req.getParameter(“_58_password”);
String[] credentials = new String[3];
credentials[0] = userId
credentials[1] = loginEmailId;
credentials[2] = password;
//Write your business logic Here and return String[].
}
Step 2: Write below code in portal-ext.properties
// you get full control from this custom class.
auto.login.hooks=com.bolog.portal.login.security.CustomLoginFilter
//Override Liferay Authentication pipeline
auth.pipeline.enable.liferay.check=false
auth.pipeline.pre=com.bolog.portal.login.security.CustomLoginAuthenticator
Step 3: Create class CustomLoginAuthenticator and implements from Authenticator.
Override authentication methods.
public int authenticateByEmailAddress(long arg0, String arg1, String arg2,
Map<String, String[]> arg3, Map<String, String[]> arg4)
throws AuthException {
//Write Your business login here and if authentication success then return 1 otherwise return 0;
return 0;
}
public int authenticateByScreenName(long arg0, String arg1, String arg2,
Map<String, String[]> arg3, Map<String, String[]> arg4)
throws AuthException {
//Write Your business login here and if authentication success then return 1 otherwise return 0;
return 0;
}
public int authenticateByUserId(long arg0, long arg1, String arg2,
Map<String, String[]> arg3, Map<String, String[]> arg4)
throws AuthException {
//Write Your business login here and if authentication success then return 1 otherwise return 0;
return 0;
}
Step 4: If authentication fail then you can also redirect any page by following code
if(Validator.isNull(credentials) || credentials[0]==null){
req.setAttribute(AutoLogin.AUTO_LOGIN_REDIRECT, “Your Login page path”);
}
What exactly do you mean by "autologin"? If you want Liferay to check wheter the user is already authenticated by some external entity (like a single sign-on server as CAS), you can just enable that in the portal.properties. There it's already preconfigured for the liferay supported authentication mechanisms. Otherwise you might need to implement your own autologin hook (as indicated in this post for example
Well found it.
Step 1: Click on add iframe and let the configuration view pop up.
Step 2: Provide the url and if there are any variables like (www.mysite.com/Action=Login&User . . . .), add the Action=Login in hidden variables text field.
Step 3: Click authenticate and select form based authentication. In this, make usre that the user field name and password field name are given correctly, and the values will be '#screen_name#','#password#'.
For example, suppose the url is something like www.mysite.com/Action=Login?User=aj&Password=aj.
User Name (field)=User
Password (field)=Password
User Name (Value)=aj
Password (Value)=aj
Hidden variables(field)=Action=Login
Now, whenever any user logs into the liferay applicaiton, if his/her account exists in the site specified(in the url), it will log into that site automatically(acts like a single sign on).
This is working !!!
-Aj

Categories