package controllers; import java.lang.reflect.InvocationTargetException; import java.util.List; import play.Play; import play.mvc.*; import play.data.validation.*; import play.libs.*; import play.utils.*; public class Secure extends Controller { @Before(unless={"login", "authenticate", "logout"}) static void checkAccess() throws Throwable { // Authent if(!session.contains("username")) { flash.put("url", "GET".equals(request.method) ? request.url : "/"); // seems a good default login(); } // Checks Check check = getActionAnnotation(Check.class); if(check != null) { check(check); } check = getControllerInheritedAnnotation(Check.class); if(check != null) { check(check); } } private static void check(Check check) throws Throwable { for(String profile : check.value()) { boolean hasProfile = (Boolean)Security.invoke("check", profile); if(!hasProfile) { Security.invoke("onCheckFailed", profile); } } } // ~~~ Login public static void login() throws Throwable { Http.Cookie remember = request.cookies.get("rememberme"); if(remember != null && remember.value.indexOf("-") > 0) { String sign = remember.value.substring(0, remember.value.indexOf("-")); String username = remember.value.substring(remember.value.indexOf("-") + 1); if(Crypto.sign(username).equals(sign)) { session.put("username", username); redirectToOriginalURL(); } } flash.keep("url"); render(); } public static void authenticate(@Required String username, String password, boolean remember) throws Throwable { // Check tokens Boolean allowed = false; try { // This is the deprecated method name allowed = (Boolean)Security.invoke("authentify", username, password); } catch (UnsupportedOperationException e ) { // This is the official method name allowed = (Boolean)Security.invoke("authenticate", username, password); } if(validation.hasErrors() || !allowed) { flash.keep("url"); flash.error("secure.error"); params.flash(); login(); } // Mark user as connected session.put("username", username); // Remember if needed if(remember) { response.setCookie("rememberme", Crypto.sign(username) + "-" + username, "30d"); } // Redirect to the original URL (or /) redirectToOriginalURL(); } public static void logout() throws Throwable { Security.invoke("onDisconnect"); session.clear(); response.removeCookie("rememberme"); Security.invoke("onDisconnected"); flash.success("secure.logout"); login(); } // ~~~ Utils static void redirectToOriginalURL() throws Throwable { Security.invoke("onAuthenticated"); String url = flash.get("url"); if(url == null) { url = "/"; } redirect(url); } public static class Security extends Controller { /** * @Deprecated * * @param username * @param password * @return */ static boolean authentify(String username, String password) { throw new UnsupportedOperationException(); } /** * This method is called during the authentication process. This is where you check if * the user is allowed to log in into the system. This is the actual authentication process * against a third party system (most of the time a DB). * * @param username * @param password * @return true if the authentication process succeeded */ static boolean authenticate(String username, String password) { return true; } /** * This method checks that a profile is allowed to view this page/method. This method is called prior * to the method's controller annotated with the @Check method. * * @param profile * @return true if you are allowed to execute this controller method. */ static boolean check(String profile) { return true; } /** * This method returns the current connected username * @return */ static String connected() { return session.get("username"); } /** * Indicate if a user is currently connected * @return true if the user is connected */ static boolean isConnected() { return session.contains("username"); } /** * This method is called after a successful authentication. * You need to override this method if you with to perform specific actions (eg. Record the time the user signed in) */ static void onAuthenticated() { } /** * This method is called before a user tries to sign off. * You need to override this method if you wish to perform specific actions (eg. Record the name of the user who signed off) */ static void onDisconnect() { } /** * This method is called after a successful sign off. * You need to override this method if you wish to perform specific actions (eg. Record the time the user signed off) */ static void onDisconnected() { } /** * This method is called if a check does not succeed. By default it shows the not allowed page (the controller forbidden method). * @param profile */ static void onCheckFailed(String profile) { forbidden(); } private static Object invoke(String m, Object... args) throws Throwable { try { return Java.invokeChildOrStatic(Security.class, m, args); } catch(InvocationTargetException e) { throw e.getTargetException(); } } } }