WebjetSecurityService.java

package sk.iway.iwcm.system.spring.services;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import sk.iway.iwcm.Constants;
import sk.iway.iwcm.DB;
import sk.iway.iwcm.Tools;
import sk.iway.iwcm.users.UserDetails;
import sk.iway.iwcm.users.UsersDB;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional;
import java.util.StringTokenizer;

/**
 *
 *  V pripade, ze nejaka metoda ma byt dostupna len pre prihlaseneho pouzivatela, admina, prip. nejaku pouzivatelsku skupinu mozeme pouzit anotacie:
 *  @PreAuthorize("@WebjetSecurityService.isLogged()") - prihalseny pouzivatel
 *  @PreAuthorize("@WebjetSecurityService.isAdmin()") - admin
 *  @PreAuthorize("@WebjetSecurityService.isInUserGroup('nazov-skupiny')") - patri do skupiny
 *  @PreAuthorize("@WebjetSecurityService.hasPermission('editDir|addSubdir')") - ma pravo na modul editDir ALEBO addSubdir
 *  @PreAuthorize("@WebjetSecurityService.hasPermission('editDir&addSubdir')") - musi mat pravo na modul editDir SUCASNE na addSubdir
 *
 * @author mpijak
 */
@Service("WebjetSecurityService") //NOSONAR
public class WebjetSecurityService {
    private final HttpSession session;
    private final HttpServletRequest request;

    public WebjetSecurityService(HttpSession session, HttpServletRequest request) {
        this.session = session;
        this.request = request;
    }

    private Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    protected boolean hasAuthority(String authority) {
        Authentication auth = getAuthentication();
        return auth != null && auth.getAuthorities().stream().anyMatch(g-> g.getAuthority().equalsIgnoreCase(authority));
    }

    private UserDetails getUser() {
        return UsersDB.getCurrentUser(session);
    }

    public boolean isAdmin() {
        if (hasAuthority("ROLE_Group_admin")) {
            return true;
        }

        UserDetails currentUser = getUser();
        return currentUser != null && currentUser.isAdmin();
    }

    public boolean isLogged() {
        Authentication auth = getAuthentication();
        if (auth != null && !auth.getClass().isAssignableFrom(AnonymousAuthenticationToken.class)) {
            return true;
        }

        return getUser() != null;
    }

    /**
     * Check user permissions. Perms can be separated by '|' or '&'.
     * If separated by '|' user must have at least one of this permissions.
     * If separated by '&' user must have all of this permissions.
     * @param permission
     * @return
     */
    public boolean hasPermission(String permission) {
        if (!isAdmin()) {
            return false;
        }

        if (permission.startsWith("Constants:")) {
            //get permission from constants
            permission = Constants.getString(permission.substring(10));
        }

        //Cant contain both separators at same time
        if(permission.contains("|") && permission.contains("&")) {
            return false;
        }

        //OR user must have at least one of this permissions
        if(permission.contains("|")) {
            StringTokenizer st = new StringTokenizer(permission, "|");
            while (st.hasMoreTokens()) {
                if (hasAuthority("ROLE_Permission_" + normalizeUserGroupName(st.nextToken().trim()))) return true;
            }

            return false;
        }

        //AND user must have all of this permissions
        if(permission.contains("&")) {
            StringTokenizer st = new StringTokenizer(permission, "&");
            while (st.hasMoreTokens()) {
                if (hasAuthority("ROLE_Permission_" + normalizeUserGroupName(st.nextToken().trim())) == false) return false;
            }

            return true;
        }

        //Just need this one permission
        return hasAuthority("ROLE_Permission_" + normalizeUserGroupName(permission.trim()));
    }

    public boolean isInUserGroup(String group) {

        if (hasAuthority("ROLE_Group_" + normalizeUserGroupName(group))) {
            return true;
        }

        UserDetails currentUser = getUser();

        //Authentication auth = getAuthentication();

        if (currentUser == null) {
            return false;
        }

        String groupName = WebjetSecurityService.normalizeUserGroupName(group);
        String userGroupNames = currentUser.getUserGroupsNames();
        if (Tools.isNotEmpty(userGroupNames)) {
            for (String userGroupName : userGroupNames.split(",")) {
                if (groupName.equalsIgnoreCase(WebjetSecurityService.normalizeUserGroupName(userGroupName))) {
                    return true;
                }
            }
        }
        return false;
    }

    public static String normalizeUserGroupName(String userGropName) {
        String userGropNameLocal = DB.internationalToEnglish(userGropName);

        userGropNameLocal = userGropNameLocal.replaceAll("\\s+", "-");

        return userGropNameLocal.toLowerCase();
    }

    public boolean checkAccessAllowedOnController(Object controller) {
        //najskor over prava na celu triedu, lebo toto na DT metodach overwrite anotaciu celej triedy
        if (controller.getClass().isAnnotationPresent(PreAuthorize.class)) {
            String value = controller.getClass().getAnnotation(org.springframework.security.access.prepost.PreAuthorize.class).value();
            //value je nieco ako: @WebjetSecurityService.hasPermission('editor_edit_media_group')
            if (value.startsWith("@WebjetSecurityService.isAdmin")) {
                if (isAdmin()==false) return false;
            } else if (value.startsWith("@WebjetSecurityService.isLogged")) {
                if (isLogged()==false) return false;
            } else if (value.startsWith("@WebjetSecurityService.")) {
                int dot = value.indexOf(".");
                int bracketStart = value.indexOf("(");
                int bracketEnd = value.indexOf(")");

                if (bracketEnd > bracketStart && bracketStart > dot) {
                    String methodName = value.substring(dot+1, bracketStart);
                    String perms = value.substring(bracketStart+1, bracketEnd).trim();
                    //odstran apostrofy/uvodzovky
                    perms = Tools.replace(perms, "'", "");
                    perms = Tools.replace(perms, "\"", "");
                    perms = perms.trim();

                    if ("hasPermission".equals(methodName)) {
                        if (hasPermission(perms)==false) return false;
                    } else if ("isInUserGroup".equals(methodName)) {
                        if (isInUserGroup(perms)==false) return false;
                    } else {
                        //neznama metoda, pre istotu vratme false
                        return false;
                    }
                }
                else {
                    //nieco je zle zapisane v anotacii
                    return false;
                }
            }
        }

        try {
            //custom metoda
            Method[] methods = controller.getClass().getMethods();
            Optional<Method> first = Arrays.stream(methods).filter(m->m.getName().equals("checkAccessAllowed")).findFirst();
            if (first.isPresent()) {
                Method method = first.get();
                boolean ret = (boolean) method.invoke(controller, request);
                //Logger.debug(WebjetSecurityService.class, "calling checkAccessAllowed, ret="+ret);
                return ret;
            }
        } catch (IllegalAccessException | InvocationTargetException e) {
            //sk.iway.iwcm.Logger.error(e);
        }

        //zavolaj defaultnu v DTv2
        //if (DatatableRestControllerV2.class.isAssignableFrom(controller.getClass())) {
        //    return ((DatatableRestControllerV2)controller).checkAccessAllowed(request);
        //}

        return true;
    }
}