/*
* Copyright 2014-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.rockagen.gnext.service.spring.security.extension;
import com.rockagen.gnext.po.AuthResource;
import com.rockagen.gnext.po.AuthRole;
import com.rockagen.gnext.service.AuthResourceServ;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
*
* Extension implementation of <tt>FilterInvocationDefinitionSource</tt>.
* <p>
* Stores an ordered map of {@link RequestMatcher}s to <tt>ConfigAttribute</tt> collections and provides matching
* of {@code FilterInvocation}s against the items stored in the map.
* <p>
* The order of the {@link RequestMatcher}s in the map is very important. The <b>first</b> one which matches the
* request will be used. Later matchers in the map will not be invoked if a match has already been found.
* Accordingly, the most specific matchers should be registered first, with the most general matches registered last.
* <p>
* The most common method creating an instance is using the Spring Security namespace. For example, the {@code pattern}
* and {@code access} attributes of the {@code <intercept-url>} elements defined as children of the
* {@code <http>} element are combined to build the instance used by the {@code FilterSecurityInterceptor}.
*
* <code>This need some like:</code>
* <pre>
* PATH | ROLE
* -------------+-------------
* /xx/root/x | ROLE_ROOT
* /xx/admin/x | ROLE_ADMIN
* /xx/auth/x | ROLE_AUTH
* -------------+-------------
* </pre>
* <p>
* PATH used regular expression
* </p>
* @author Ben Alex
* @author Luke Taylor
* @author ra
* @since JDK1.8
* @since 3.0
*/
public class ExFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource{
private static final Logger log = LoggerFactory.getLogger(ExFilterInvocationSecurityMetadataSource.class);
private AuthResourceServ authResourceServ;
private Cache cache;
private Map<RequestMatcher, Collection<ConfigAttribute>> processMap() {
Map<RequestMatcher, Collection<ConfigAttribute>> requestToExpressionAttributesMap =
new LinkedHashMap<>();
List<AuthResource> resources=authResourceServ.findAll();
if(resources!=null&&resources.size()>0) {
// Sort by priority
// Jdk8 only
resources.stream().sorted((a, b) -> a.getPriority().compareTo(b.getPriority())).forEach(x->{
RequestMatcher request = new RegexRequestMatcher(x.getPath(), null);
Set<AuthRole> roles = x.getRoles();
List<ConfigAttribute> attrs = new ArrayList<>(roles.size());
roles.forEach(y -> attrs.add(new SecurityConfig(y.getName().trim())));
requestToExpressionAttributesMap.put(request, attrs);
});
}
return requestToExpressionAttributesMap;
}
/**
* Get Attributes map from cache if cache set.
* @return Map
*/
@SuppressWarnings("unchecked")
private Map<RequestMatcher, Collection<ConfigAttribute>> getAttributesMap(){
if(cache!=null){
Map<RequestMatcher, Collection<ConfigAttribute>> map;
String key="ex.securityMetadataSource";
Element e=cache.get(key);
if(e!=null && !e.isExpired()){
log.debug("Cache [Hit] ex.securityMetadataSource: {}",e);
map= (Map<RequestMatcher, Collection<ConfigAttribute>>)e.getObjectValue();
}else{
map=processMap();
Element enew = new Element(key, map);
cache.put(enew);
log.debug("Cache [Update] ex.securityMetadataSource: {}",enew);
}
return map;
}
return processMap();
}
//~ Methods ========================================================================================================
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
Set<ConfigAttribute> allAttributes = new HashSet<>();
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : getAttributesMap().entrySet()) {
allAttributes.addAll(entry.getValue());
}
return allAttributes;
}
@Override
public Collection<ConfigAttribute> getAttributes(Object object) {
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : getAttributesMap().entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
}
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
public void setAuthResourceServ(AuthResourceServ authResourceServ) {
this.authResourceServ = authResourceServ;
}
public void setCache(Cache cache) {
this.cache = cache;
}
}