package org.triiskelion.tinyspring.security;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Set this interceptor in spring-mvc configurations to enable security checks.
* <p/>
* A {@link TinySecurityManager} is obligatory to provider authentication logic.
* <p/>
* <pre>
* {@code<mvc:interceptors>
* <mvc:interceptor>
* <mvc:mapping path="/**"/>
* <bean class="org.triiskelion.tinyspring.security.TinySecurityInterceptor">
* <property name="securityManager" ref="securityManager"/>
* </bean>
* </mvc:interceptor>
* </mvc:interceptors>
* }
* </pre>
*
* @author Sebastian MA
*/
public class TinySecurityInterceptor extends HandlerInterceptorAdapter {
private static final Logger log = LoggerFactory.getLogger(TinySecurityInterceptor.class);
TinySecurityManager securityManager;
public TinySecurityManager getSecurityManager() {
return securityManager;
}
public void setSecurityManager(TinySecurityManager securityManager) {
this.securityManager = securityManager;
}
@Override
public final boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object
handler) throws Exception {
if(handler instanceof ResourceHttpRequestHandler) {
return true;
}
if(handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
SecurityCheck classAnnotation =
handlerMethod.getMethod()
.getDeclaringClass()
.getAnnotation(SecurityCheck.class);
SecurityCheck methodAnnotation =
handlerMethod
.getMethodAnnotation(SecurityCheck.class);
String url = request.getRequestURI().replace(request.getContextPath(), "");
// check method annotation
if(methodAnnotation != null) { // if method is annotated
if(!methodAnnotation.value()) {
log.debug("Access granted [{}]: @SecurityCheck disabled.", url);
return true;
}
return checkAndRespond(url, request, response,
methodAnnotation.requireRoles(),
methodAnnotation.requireAnyPrivileges(),
methodAnnotation.requireAllPrivileges(),
methodAnnotation.stateless());
}
// check type annotation
if(classAnnotation == null) {
log.debug(" Access granted [{}]: @SecurityCheck not found", url);
return true;
}
if(!classAnnotation.value()) {
log.debug("Access granted [{}]: @SecurityCheck disabled.", url);
return true;
}
String[] matches = classAnnotation.matches();
String[] excludes = classAnnotation.excludes();
boolean requireCheck = false;
for(String pattern : matches) {
if(pattern.contains("**")) {
pattern = pattern.replace("**", ".*");
} else {
pattern = pattern.replace("*", "[^/]*");
}
if(url.matches(pattern)) {
requireCheck = true;
break;
}
}
if(!requireCheck) {
log.debug("Access granted [{}]: pattern not match", url);
return true;
} else {
for(String excludePattern : excludes) {
if(excludePattern.contains("**")) {
excludePattern = excludePattern.replace("**", ".*");
} else {
excludePattern = excludePattern.replace("*", "[^/]*");
}
excludePattern = ".*" + excludePattern + ".*";
if(url.matches(excludePattern)) {
requireCheck = false;
log.debug("Access granted [{}]: excluded", url);
break;
}
}
}
if(requireCheck) { // now check it
return checkAndRespond(url, request, response,
classAnnotation.requireRoles(),
classAnnotation.requireAnyPrivileges(),
classAnnotation.requireAllPrivileges(),
classAnnotation.stateless());
}
}
return true; //if fall here, access will be granted
}
private final boolean checkAndRespond(String url,
HttpServletRequest request,
HttpServletResponse response,
String[] requireRoles,
String[] requireAnyPrivileges,
String[] requireAllPrivileges,
boolean stateless)
throws ServletException, IOException {
log.debug("Security check for [{}], requireAnyPrivileges[{}], requireAllPrivileges[{}]",
url,
StringUtils.join(requireAnyPrivileges, ","),
StringUtils.join(requireAllPrivileges, ",")
);
if(stateless) { // doAuthenticate via request
return securityManager.doAuthenticateStatelessly(request, response);
}
TinyUser user = (TinyUser) request.getSession()
.getAttribute(TinySecurityManager.SESSION_NAME_USER);
if(user == null) { // not login
log.debug("Security check for [{}] user not found. Access denied.", url);
securityManager.onNotLogin(request, response);
return false;
} else { // logged in
boolean success
= checkRequireRoles(request, response, user, requireRoles);
if(!success) {
log.error("Access denied [{}]: requireRoles failed: values={}",
url,
StringUtils.join(requireRoles, ","));
return false;
}
success = checkRequireAnyPrivileges(
request, response, user, requireAnyPrivileges);
if(!success) {
log.error("Access denied [{}]: requireAnyPrivileges failed: values={}",
url,
StringUtils.join(requireAnyPrivileges, ","));
return false;
}
success = checkRequireAllPrivileges(
request, response, user, requireAllPrivileges);
if(!success) {
log.error("Access denied [{}]: requireAllPrivileges failed: values={}",
url,
StringUtils.join(requireAllPrivileges, ","));
return false;
}
}
log.debug(" Access granted [{}]: passed", url);
return true;
}
private boolean checkRequireRoles(HttpServletRequest request, HttpServletResponse response,
TinyUser user, String[] requireRoles) {
if(requireRoles == null || requireRoles.length == 0) {
return true;
}
for(String requireRole : requireRoles) {
for(Role userRole : user.getRoles()) {
if(userRole.getId().equals(requireRole)) {
return true;
}
}
}
securityManager.onRequireRolesFail(request, response, user, requireRoles);
return false;
}
/**
* Checks items from <code>requireAnyPrivileges</code>.
* The check is passed if user has any of the privileges enumerated.
*
* @param req
* @param res
* @param user
* the current user
* @param privileges
* the privileges to check.
*
* @return <code>TRUE</code> if passed, and <code>FALSE</code> otherwise.
*/
private boolean checkRequireAnyPrivileges(HttpServletRequest req, HttpServletResponse res,
TinyUser user, String[] privileges) {
if(privileges == null || privileges.length == 0) {
return true;
}
boolean okay = false;
for(String key : privileges) {
if((user.getPrivilege().getValue(key)) > 0) {
okay = true;
break;
}
}
if(!okay) {
securityManager.onRequireAnyPrivilegeFail(req, res, user, privileges);
return false;
}
return true;
}
/**
* Checks items from <code>requireAllPrivileges</code>.
* The check is passed if user has all of the privileges enumerated.
*
* @param req
* @param res
* @param user
* the current user
* @param privileges
* the privileges to check.
*
* @return <code>TRUE</code> if passed, and <code>FALSE</code> otherwise.
*/
private boolean checkRequireAllPrivileges(
HttpServletRequest req, HttpServletResponse res,
TinyUser user, String[] privileges) {
if(privileges == null || privileges.length == 0) {
return true;
}
if(privileges != null) {
for(String key : privileges) {
int value = user.getPrivilege().getValue(key);
if(value <= 0) {
securityManager.onRequireAllPrivilegesFail(req, res, user, privileges);
return false;
}
}
}
return true;
}
@Override
public final void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
super.postHandle(request, response, handler, modelAndView);
}
@Override
public final void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
super.afterCompletion(request, response, handler, ex);
}
}