/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package controllers.security;
import com.emc.vipr.client.exceptions.ViPRHttpException;
import com.google.common.collect.Lists;
import controllers.deadbolt.Deadbolt;
import models.security.UserInfo;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpStatus;
import play.Logger;
import play.Play;
import play.cache.Cache;
import play.mvc.Controller;
import play.mvc.Http;
import play.mvc.Util;
import util.BourneUtil;
import util.MessagesUtils;
import java.net.URLEncoder;
import java.util.List;
import static com.emc.vipr.client.impl.Constants.AUTH_PORTAL_TOKEN_KEY;
import static com.emc.vipr.client.impl.Constants.AUTH_TOKEN_KEY;
import static util.BourneUtil.getViprClient;
/**
* @author Chris Dail
*/
public class Security extends Controller {
public static final String SYSTEM_AUDITOR = "SYSTEM_AUDITOR";
public static final String SYSTEM_ADMIN = "SYSTEM_ADMIN";
public static final String SYSTEM_MONITOR = "SYSTEM_MONITOR";
public static final String TENANT_ADMIN = "TENANT_ADMIN";
public static final String TENANT_APPROVER = "TENANT_APPROVER";
public static final String SECURITY_ADMIN = "SECURITY_ADMIN";
public static final String PROJECT_ADMIN = "PROJECT_ADMIN";
public static final String RESTRICTED_SYSTEM_ADMIN = "RESTRICTED_SYSTEM_ADMIN";
public static final String RESTRICTED_SECURITY_ADMIN = "RESTRICTED_SECURITY_ADMIN";
// These are Portal only Roles!
// Members who have admin over any tenant will get the TENANT_ADMIN role,
// but some actions are limited to the admin of the root tenant, or
// someone who has admin on the tenant that they belong to,
public static final String ROOT_TENANT_ADMIN = "ROOT_TENANT_ADMIN";
public static String HOME_TENANT_ADMIN = "HOME_TENANT_ADMIN";
public static final String CACHE_EXPR = "1min";
@Util
public static UserInfo getUserInfo() {
String key = getUserInfoCacheKey();
UserInfo user = (UserInfo) Cache.get(key);
if (user == null) {
user = new UserInfo(getViprClient().getUserInfo());
Cache.set(key, user, CACHE_EXPR);
}
return user;
}
@Util
public static void clearUserInfo() {
Cache.delete(getUserInfoCacheKey());
}
@Util
private static String getUserInfoCacheKey() {
return "userInfo." + getAuthToken();
}
@Util
public static boolean isApiRequest() {
return request != null && ("json".equalsIgnoreCase(request.format) || "xml".equalsIgnoreCase(request.format));
}
@Util
public static String getAuthToken() {
// Look in cookies
Http.Cookie cookie = request.cookies.get(AUTH_PORTAL_TOKEN_KEY);
if (cookie != null) {
return cookie.value;
}
cookie = request.cookies.get(AUTH_TOKEN_KEY);
if (cookie != null) {
return cookie.value;
}
// Look In Headers
Http.Header header = request.headers.get(AUTH_PORTAL_TOKEN_KEY.toLowerCase());
if (header != null) {
return header.value();
}
header = request.headers.get(AUTH_TOKEN_KEY.toLowerCase());
if (header != null) {
return header.value();
}
return null;
}
@Util
public static String getProxyAuthToken() {
String authToken = getAuthToken();
if (authToken == null) {
return null;
}
return BourneUtil.getViprClient().auth().proxyToken();
}
@Util
public static boolean isRootTenantAdmin() {
return hasRoles(ROOT_TENANT_ADMIN);
}
@Util
public static boolean isHomeTenantAdmin() {
return hasRoles(HOME_TENANT_ADMIN);
}
@Util
public static boolean isSystemAdmin() {
return hasRoles(SYSTEM_ADMIN);
}
@Util
public static boolean isSystemAuditor() {
return hasRoles(SYSTEM_AUDITOR);
}
@Util
public static boolean isSystemMonitor() {
return hasRoles(SYSTEM_MONITOR);
}
@Util
public static boolean isTenantAdmin() {
return hasRoles(TENANT_ADMIN);
}
@Util
public static boolean isTenantApprover() {
return hasRoles(TENANT_APPROVER);
}
@Util
public static boolean isProjectAdmin() {
return hasRoles(PROJECT_ADMIN);
}
@Util
public static boolean isSecurityAdmin() {
return hasRoles(SECURITY_ADMIN);
}
@Util
public static boolean isRestrictedSecurityAdmin() {
return hasRoles(RESTRICTED_SECURITY_ADMIN);
}
@Util
public static boolean isRestrictedSystemAdmin() {
return hasRoles(RESTRICTED_SYSTEM_ADMIN);
}
@Util
public static boolean isSecurityAdminOrRestrictedSecurityAdmin() {
return isSecurityAdmin() || isRestrictedSecurityAdmin();
}
@Util
public static boolean isLocalUser() {
return !getUserInfo().getCommonName().contains("@");
}
@Util
public static boolean isSystemAdminOrRestrictedSystemAdmin() {
return isSystemAdmin() || isRestrictedSystemAdmin();
}
@Util
public static void clearAuthToken() {
clearUserInfo();
removeResponseCookie(AUTH_PORTAL_TOKEN_KEY);
removeResponseCookie(AUTH_TOKEN_KEY);
}
@Util
static void clearSession() {
session.clear();
}
/**
* Returns true if user has all of the roles specified.
*/
public static boolean hasRoles(String... roles) {
return hasRoles(Lists.newArrayList(roles));
}
public static boolean hasOneOfRoles(List<String> roles) {
if (roles == null || roles.isEmpty()) {
return true;
}
for (String role : roles) {
if (hasRoles(role)) {
return true;
}
}
return false;
}
/**
* Returns true if the user has any of the roles specified.
*
* @param roles
* the roles to check.
* @return true if the user has any of the roles.
*/
public static boolean hasAnyRole(String... roles) {
for (String role : roles) {
if (hasRoles(role)) {
return true;
}
}
return false;
}
private static boolean hasRoles(List<String> roles) {
try {
return Deadbolt.hasRoles(roles);
}
// I'm not sure why deadbolt throws this
catch (Throwable t) { // NOSONAR
// ("Suppressing Sonar violation Catch Exception instead of Throwable as the above method throws Throwable ")
throw new RuntimeException(t);
}
}
// This is a special login page to display the no-cookies error.
// If there are actually no cookies, we cannot take advantage of the flash scope on redirect because it uses cookies
public static void noCookies() {
flash.error(MessagesUtils.get("security.noCookies"));
render("@nologin");
}
// Sign out
public static void logout() {
try {
getViprClient().auth().logout();
} catch (Exception e) {
Logger.warn(e, "Error logging out");
}
clearAuthToken();
clearSession();
render("@nologin");
}
/**
* AJAXable method that can be used to determine if the user is authenticated.
*/
public static void authenticated() {
if (StringUtils.isBlank(getAuthToken())) {
renderJSON(false);
}
try {
UserInfo user = getUserInfo();
renderJSON(user != null);
} catch (ViPRHttpException e) {
Logger.error(e, "HTTP Error: %s %s", e.getHttpCode(), e.getMessage());
if (e.getHttpCode() == HttpStatus.SC_UNAUTHORIZED) {
renderJSON(false);
}
// Propogate other errors (502, 503 most importantly)
error(e.getHttpCode(), e.getMessage());
} catch (Exception e) {
Logger.error(e, "Error getting user info");
renderJSON("error");
}
}
@Util
public static void redirectToAuthPage() {
if (Security.isApiRequest()) {
// Redirecting to the apisvc login page will fail in most browsers due to the same origin policy.
// Return a 401 and let the client handle it.
error(401, "Unauthorized");
}
else {
try {
String base = request.getBase();
String path = "GET".equalsIgnoreCase(request.method) ? request.url : Play.ctxPath + "/";
String service = URLEncoder.encode(base + path, "UTF-8");
String authSvcPort = Play.configuration.getProperty("authsvc.port");
String url = String.format("https://%s:%s/formlogin?service=%s&src=portal", request.domain, authSvcPort, service);
Logger.debug("No cookie detected. Redirecting to login page %s", url);
redirect(url);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/***
* Removes the session cookie from the response by
* setting the cookie value with "" and path with "/".
* We could have used play framework's Http.Response.removeCookie()
* only but the reason for not using that is,
* Http.Response.removeCookie() sets the HttpOnly and secure
* attributes of the cookie to false and that could lead to
* XSS.
*
* @param name of the cookie to be removed from the response.
*/
@Util
private static void removeResponseCookie(String name) {
response.setCookie(name, "", null, "/", 0, true, true);
}
}