Skip to content

Commit

Permalink
Merge pull request #40 from homburgs/master
Browse files Browse the repository at this point in the history
add facility to remove/add/update security chains at runtime
  • Loading branch information
kaosko committed May 22, 2017
2 parents 301414d + 505c294 commit bb15cc3
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 102 deletions.
10 changes: 6 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</parent>

<properties>
<tapestry-release-version>5.4.1</tapestry-release-version>
<tapestry-release-version>5.4.2</tapestry-release-version>
</properties>

<!-- Developers section inherited from the parent pom -->
Expand Down Expand Up @@ -100,8 +100,8 @@
<connection>scm:git:git@github.com:tynamo/tapestry-security.git</connection>
<developerConnection>scm:git:git@github.com:tynamo/tapestry-security.git</developerConnection>
<url>https://github.com/tynamo/tapestry-security</url>
<tag>HEAD</tag>
</scm>
<tag>HEAD</tag>
</scm>

<repositories>
<!-- Needed as long as we depend on Tapestry snapshot
Expand Down Expand Up @@ -165,7 +165,9 @@
<artifactId>jetty-maven-plugin</artifactId>
<version>9.2.4.v20141103</version>
<configuration>
<contextPath>/</contextPath>
<webApp>
<contextPath>/</contextPath>
</webApp>
<useTestClasspath>true</useTestClasspath>
<webAppSourceDirectory>${basedir}/src/test/webapp</webAppSourceDirectory>
<systemProperties>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.tynamo.security.internal.services.impl;

import java.io.IOException;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Expand All @@ -21,6 +20,7 @@
import org.apache.tapestry5.services.LocalizationSetter;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.RequestGlobals;

import org.tynamo.security.SecuritySymbols;
import org.tynamo.security.internal.services.LoginContextService;

Expand Down Expand Up @@ -207,6 +207,6 @@ public void redirectToSavedRequest(String fallbackUrl) throws IOException {
servletResponse.setHeader("Location", servletResponse.encodeRedirectURL(requestUri));
// if you don't flush the buffer, filters can and will change the headers afterwards
servletResponse.flushBuffer();
// servletResponse.sendRedirect(servletResponse.encodeRedirectURL(requestUri));
// servletResponse.sendRedirect(servletResponse.encodeRedirectURL(requestUri));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.tynamo.security.services;

import java.util.Collection;

import org.tynamo.security.services.impl.SecurityFilterChain;
import org.tynamo.security.shiro.AccessControlFilter;

public interface SecurityFilterChainHub {

void insertChain(String path, AccessControlFilter filter, String config);

void updateChain(String path, AccessControlFilter filter, String config);

void removeChain(String path);

void commitModifications(Collection<SecurityFilterChain> chains);
}
111 changes: 46 additions & 65 deletions src/main/java/org/tynamo/security/services/SecurityModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.apache.tapestry5.services.HttpServletRequestFilter;
import org.apache.tapestry5.services.LibraryMapping;
import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;

import org.slf4j.Logger;
import org.tynamo.security.Security;
import org.tynamo.security.SecurityComponentRequestFilter;
Expand All @@ -53,6 +54,7 @@
import org.tynamo.security.services.impl.SecurityConfiguration;
import org.tynamo.security.services.impl.SecurityFilterChain;
import org.tynamo.security.services.impl.SecurityFilterChainFactoryImpl;
import org.tynamo.security.services.impl.SecurityFilterChainHubImpl;
import org.tynamo.security.services.impl.SecurityServiceImpl;
import org.tynamo.security.shiro.SimplePrincipalSerializer;
import org.tynamo.shiro.extension.authz.aop.AopHelper;
Expand All @@ -61,16 +63,12 @@

/**
* The main entry point for Security integration.
*
*/
@Security
public class SecurityModule
{
public class SecurityModule {
private static final String PATH_PREFIX = "security";

public static void bind(final ServiceBinder binder)
{

public static void bind(final ServiceBinder binder) {
binder.bind(WebSecurityManager.class, TapestryRealmSecurityManager.class);
// TYNAMO-155 It's not enough to identify ModularRealmAuthenticator by it's Authenticator interface only
// because Shiro tests if the object is an instanceof LogoutAware to call logout handlers
Expand All @@ -81,15 +79,16 @@ public static void bind(final ServiceBinder binder)
binder.bind(SecurityService.class, SecurityServiceImpl.class);
binder.bind(SecurityFilterChainFactory.class, SecurityFilterChainFactoryImpl.class);
binder.bind(ComponentRequestFilter.class, SecurityComponentRequestFilter.class);
binder.bind(SecurityFilterChainHub.class, SecurityFilterChainHubImpl.class);
// binder.bind(ShiroExceptionHandler.class);
binder.bind(LoginContextService.class, LoginContextServiceImpl.class);
binder.bind(Serializer.class, SimplePrincipalSerializer.class);
}

@SuppressWarnings({ "rawtypes", "unchecked" })
@SuppressWarnings({"rawtypes", "unchecked"})
public static RememberMeManager buildRememberMeManager(Serializer serializer, Logger logger,
@Symbol(SymbolConstants.HMAC_PASSPHRASE) String hmacPassphrase,
@Symbol(SecuritySymbols.REMEMBERME_CIPHERKERY) String rememberMeCipherKey) throws UnsupportedEncodingException {
@Symbol(SymbolConstants.HMAC_PASSPHRASE) String hmacPassphrase,
@Symbol(SecuritySymbols.REMEMBERME_CIPHERKERY) String rememberMeCipherKey) throws UnsupportedEncodingException {
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
// the default Shiro serializer produces obnoxiously long cookies
rememberMeManager.setSerializer(serializer);
Expand All @@ -99,27 +98,27 @@ public static RememberMeManager buildRememberMeManager(Serializer serializer, Lo
if (cipherKey.length <= 0) {
if (hmacPassphrase.isEmpty()) {
logger
.error("Neither symbol '"
+ SecuritySymbols.REMEMBERME_CIPHERKERY
+ "' nor '"
+ SymbolConstants.HMAC_PASSPHRASE
+ "' is set. Using a random value as the cipher key for encrypting rememberMe information. Cookies will be invalidated when the JVM is restarted");
.error("Neither symbol '"
+ SecuritySymbols.REMEMBERME_CIPHERKERY
+ "' nor '"
+ SymbolConstants.HMAC_PASSPHRASE
+ "' is set. Using a random value as the cipher key for encrypting rememberMe information. Cookies will be invalidated when the JVM is restarted");
return rememberMeManager;
}

logger.warn("Symbol '" + SecuritySymbols.REMEMBERME_CIPHERKERY + "' is not set, using '"
+ SymbolConstants.HMAC_PASSPHRASE
+ "' as the cipher. Beware that changing the value will invalidate rememberMe cookies");
if (hmacPassphrase.length() < 16) hmacPassphrase = hmacPassphrase + ("================".substring(hmacPassphrase.length()));
+ SymbolConstants.HMAC_PASSPHRASE
+ "' as the cipher. Beware that changing the value will invalidate rememberMe cookies");
if (hmacPassphrase.length() < 16)
hmacPassphrase = hmacPassphrase + ("================".substring(hmacPassphrase.length()));
cipherKey = hmacPassphrase.getBytes("UTF-8");
if (cipherKey.length > 16) cipherKey = Arrays.copyOf(cipherKey, 16);
}
rememberMeManager.setCipherKey(cipherKey);
return rememberMeManager;
}

public static void contributeFactoryDefaults(MappedConfiguration<String, String> configuration)
{
public static void contributeFactoryDefaults(MappedConfiguration<String, String> configuration) {
configuration.add(SecuritySymbols.LOGIN_URL, "/" + PATH_PREFIX + "/login");
configuration.add(SecuritySymbols.SUCCESS_URL, "/" + "${" + SymbolConstants.START_PAGE_NAME + "}");
configuration.add(SecuritySymbols.UNAUTHORIZED_URL, "/" + PATH_PREFIX + "/unauthorized");
Expand All @@ -131,33 +130,26 @@ public static void contributeFactoryDefaults(MappedConfiguration<String, String>
/**
* Create ClassInterceptorsCache through annotations on the class page,
* which then will use SecurityFilter.
* <p/>
* <p>
*/
public void contributeApplicationInitializer(OrderedConfiguration<ApplicationInitializerFilter> configuration,
final ComponentClassResolver componentClassResolver,
final ClassInterceptorsCache classInterceptorsCache)
{
final ComponentClassResolver componentClassResolver,
final ClassInterceptorsCache classInterceptorsCache) {

configuration.add("SecurityApplicationInitializerFilter", new ApplicationInitializerFilter()
{
configuration.add("SecurityApplicationInitializerFilter", new ApplicationInitializerFilter() {
@Override
public void initializeApplication(Context context, ApplicationInitializer initializer)
{
public void initializeApplication(Context context, ApplicationInitializer initializer) {

initializer.initializeApplication(context);

for (String name : componentClassResolver.getPageNames())
{
for (String name : componentClassResolver.getPageNames()) {
String className = componentClassResolver.resolvePageNameToClassName(name);
Class<?> clazz = ClassUtils.forName(className);

while (clazz != null)
{
for (Class<? extends Annotation> annotationClass : AopHelper.getAutorizationAnnotationClasses())
{
while (clazz != null) {
for (Class<? extends Annotation> annotationClass : AopHelper.getAutorizationAnnotationClasses()) {
Annotation classAnnotation = clazz.getAnnotation(annotationClass);
if (classAnnotation != null)
{
if (classAnnotation != null) {
//Add in the cache which then will be used in RequestFilter
classInterceptorsCache.add(className, new DefaultSecurityInterceptor(classAnnotation));
}
Expand All @@ -171,9 +163,8 @@ public void initializeApplication(Context context, ApplicationInitializer initia


public static void contributeComponentRequestHandler(OrderedConfiguration<ComponentRequestFilter> configuration,
@Local ComponentRequestFilter filter)
{
configuration.add("SecurityFilter", filter, "before:*");
@Local ComponentRequestFilter filter) {
configuration.add("SecurityFilter", filter, "before:*");
}

@SuppressWarnings("rawtypes")
Expand All @@ -186,54 +177,44 @@ public static void addSafePrincipalTypes(Configuration<Class> configuration) {
}

@Contribute(ComponentClassTransformWorker2.class)
public static void addTransformWorkers(OrderedConfiguration<ComponentClassTransformWorker2> configuration)
{
public static void addTransformWorkers(OrderedConfiguration<ComponentClassTransformWorker2> configuration) {
configuration.addInstance(ShiroAnnotationWorker.class.getSimpleName(), ShiroAnnotationWorker.class);
}


public static void contributeComponentClassResolver(Configuration<LibraryMapping> configuration)
{
public static void contributeComponentClassResolver(Configuration<LibraryMapping> configuration) {
configuration.add(new LibraryMapping(PATH_PREFIX, "org.tynamo.security"));
}

/**
* Secure all service methods that are marked with authorization annotations.
* <p/>
* <p>
* <b>Restriction:</b> Only service interfaces can be annotated.
*/
@Match("*")
@Order("before:*")
public static void adviseSecurityAssert(MethodAdviceReceiver receiver,
final @Core Environment environment)
{
final @Core Environment environment) {
Class<?> serviceInterface = receiver.getInterface();

for (Method method : serviceInterface.getMethods())
{
for (Method method : serviceInterface.getMethods()) {

List<SecurityInterceptor> interceptors =
AopHelper.createSecurityInterceptorsSeeingInterfaces(method, serviceInterface);

for (final SecurityInterceptor interceptor : interceptors)
{
MethodAdvice advice = new MethodAdvice()
{
for (final SecurityInterceptor interceptor : interceptors) {
MethodAdvice advice = new MethodAdvice() {
@Override
public void advise(MethodInvocation invocation)
{
public void advise(MethodInvocation invocation) {
// Only (try to) intercept if subject is bound.
// This is useful in case background or initializing operations
// call service operations that are secure
if (ThreadContext.getSubject() != null)
{
if (ThreadContext.getSubject() != null) {
environment.push(MethodInvocation.class, invocation);
try
{
try {
interceptor.intercept();
}
finally
{
finally {
environment.pop(MethodInvocation.class);
}
}
Expand All @@ -253,16 +234,16 @@ public void contributeRequestExceptionHandler(MappedConfiguration<Class, Object>
}

public static void contributeHttpServletRequestHandler(OrderedConfiguration<HttpServletRequestFilter> configuration,
@InjectService("SecurityConfiguration") HttpServletRequestFilter securityConfiguration) {
@InjectService("SecurityConfiguration") HttpServletRequestFilter securityConfiguration) {
configuration.add("SecurityConfiguration", securityConfiguration, "after:StoreIntoGlobals", "before:IgnoredPaths");
}

@Contribute(HttpServletRequestFilter.class)
@Security
public static void defaultSecurity(Configuration<SecurityFilterChain> configuration,
SecurityFilterChainFactory factory) {
configuration.add(factory.createChain("/modules.gz/**").add(factory.anon()).build());
configuration.add(factory.createChain("/modules/**").add(factory.anon()).build());
configuration.add(factory.createChain("/assets/**").add(factory.anon()).build());
public static void defaultSecurity(OrderedConfiguration<SecurityFilterChain> configuration,
SecurityFilterChainFactory factory) {
configuration.add("ModulesCompressed", factory.createChain("/modules.gz/**").add(factory.anon()).build(), "before:*");
configuration.add("Modules", factory.createChain("/modules/**").add(factory.anon()).build(), "before:*", "after:ModulesCompressed");
configuration.add("Assets", factory.createChain("/assets/**").add(factory.anon()).build(), "before:*", "after:Modules");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.io.IOException;
import java.util.Collection;

import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Expand All @@ -16,27 +16,31 @@
import org.apache.tapestry5.services.HttpServletRequestFilter;
import org.apache.tapestry5.services.HttpServletRequestHandler;
import org.apache.tapestry5.services.RequestGlobals;

import org.tynamo.security.internal.services.LoginContextService;
import org.tynamo.security.services.SecurityFilterChainHub;

public class SecurityConfiguration implements HttpServletRequestFilter {

private final SecurityManager securityManager;
private final ServletContext servletContext;
private final LoginContextService loginContextService;
private final SecurityFilterChainHub securityFilterChainHub;

private final Collection<SecurityFilterChain> chains;
private Collection<SecurityFilterChain> chains;
private final RequestGlobals requestGlobals;

public SecurityConfiguration(ApplicationGlobals applicationGlobals, RequestGlobals requestGlobals,
final WebSecurityManager securityManager, LoginContextService loginContextService,
final Collection<SecurityFilterChain> chains) {
final WebSecurityManager securityManager, LoginContextService loginContextService,
final List<SecurityFilterChain> chains,
final SecurityFilterChainHub securityFilterChainHub) {

this.securityManager = securityManager;
this.loginContextService = loginContextService;
this.securityFilterChainHub = securityFilterChainHub;
this.servletContext = applicationGlobals.getServletContext();
this.requestGlobals = requestGlobals;
this.chains = chains;

}

public boolean service(final HttpServletRequest originalRequest, final HttpServletResponse response, final HttpServletRequestHandler handler)
Expand All @@ -49,6 +53,8 @@ public boolean service(final HttpServletRequest originalRequest, final HttpServl

final String requestURI = loginContextService.getLocalelessPathWithinApplication();

runChainListeners();

final SecurityFilterChain chain = getMatchingChain(requestURI);

requestGlobals.storeServletRequestResponse(request, response);
Expand All @@ -60,11 +66,11 @@ public boolean service(final HttpServletRequest originalRequest, final HttpServl
try {
// return subject.execute(new Callable<Boolean>() {
// public Boolean call() throws Exception {
if (chain == null) return handler.service(request, response);
else {
boolean handled = chain.getHandler().service(request, response);
return handled || handler.service(request, response);
}
if (chain == null) return handler.service(request, response);
else {
boolean handled = chain.getHandler().service(request, response);
return handled || handler.service(request, response);
}
// }
// });
}
Expand All @@ -78,6 +84,10 @@ public boolean service(final HttpServletRequest originalRequest, final HttpServl
}
}

private void runChainListeners() {
securityFilterChainHub.commitModifications(chains);
}

private SecurityFilterChain getMatchingChain(final String requestURI) {
for (SecurityFilterChain chain : chains) {
// If the path does match, then pass on to the subclass implementation for specific checks:
Expand Down
Loading

0 comments on commit bb15cc3

Please sign in to comment.