CCM NG: CDI Interceptor for Authorization

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@3745 8810af33-2d31-482b-a856-94f89814c4df
pull/2/head
jensp 2015-11-24 19:22:44 +00:00
parent 5b3a8a134b
commit c433cadaae
18 changed files with 590 additions and 25 deletions

View File

@ -7,7 +7,7 @@ import static ${package}.${typeName}Constants.*;
import org.hibernate.envers.Audited;
import org.libreccm.cms.contentsection.ContentItem;
import org.libreccm.contentsection.ContentItem;
import java.io.Serializable;

View File

@ -12,7 +12,7 @@ import org.libreccm.modules.ShutdownEvent;
import org.libreccm.modules.UnInstallEvent;
@Module(packageName="${package}",
requiredModules = {@RequiredModule(module = org.libreccm.core.CcmCore)})
requiredModules = {@RequiredModule(module = org.libreccm.core.CcmCore.class)})
public class ${typeName}Module implements CcmModule {
@Override

View File

@ -7,7 +7,7 @@ import static org.librecms.contenttypes.externallink.ExternallinkConstants.*;
import org.hibernate.envers.Audited;
import org.libreccm.cms.contentsection.ContentItem;
import org.librecms.contentsection.ContentItem;
import java.io.Serializable;

View File

@ -11,8 +11,9 @@ import org.libreccm.modules.RequiredModule;
import org.libreccm.modules.ShutdownEvent;
import org.libreccm.modules.UnInstallEvent;
@Module(packageName="org.librecms.contenttypes.externallink",
requiredModules = {@RequiredModule(module = org.libreccm.core.CcmCore)})
@Module(packageName = "org.librecms.contenttypes.externallink",
requiredModules = {
@RequiredModule(module = org.libreccm.core.CcmCore.class)})
public class ExternallinkModule implements CcmModule {
@Override
@ -35,5 +36,4 @@ public class ExternallinkModule implements CcmModule {
//ToDo Remove module data
}
}

View File

@ -3,17 +3,16 @@
*/
package org.librecms.contenttypes.faqitem;
import static org.librecms.contenttypes.faqitem.FAQitemConstants.*;
import org.hibernate.envers.Audited;
import org.libreccm.cms.contentsection.ContentItem;
import org.librecms.contentsection.ContentItem;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Table;
import static org.librecms.contenttypes.faqitem.FAQitemConstants.*;
@Entity
@Audited
@Table(name = "${type_name}", schema = DB_SCHEMA)

View File

@ -11,8 +11,9 @@ import org.libreccm.modules.RequiredModule;
import org.libreccm.modules.ShutdownEvent;
import org.libreccm.modules.UnInstallEvent;
@Module(packageName="org.librecms.contenttypes.faqitem",
requiredModules = {@RequiredModule(module = org.libreccm.core.CcmCore)})
@Module(packageName = "org.librecms.contenttypes.faqitem",
requiredModules = {
@RequiredModule(module = org.libreccm.core.CcmCore.class)})
public class FAQitemModule implements CcmModule {
@Override
@ -35,5 +36,4 @@ public class FAQitemModule implements CcmModule {
//ToDo Remove module data
}
}

View File

@ -6,8 +6,7 @@ package org.librecms.contenttypes.glossaryitem;
import static org.librecms.contenttypes.glossaryitem.GlossaryitemConstants.*;
import org.hibernate.envers.Audited;
import org.libreccm.cms.contentsection.ContentItem;
import org.librecms.contentsection.ContentItem;
import java.io.Serializable;

View File

@ -12,7 +12,7 @@ import org.libreccm.modules.ShutdownEvent;
import org.libreccm.modules.UnInstallEvent;
@Module(packageName="org.librecms.contenttypes.glossaryitem",
requiredModules = {@RequiredModule(module = org.libreccm.core.CcmCore)})
requiredModules = {@RequiredModule(module = org.libreccm.core.CcmCore.class)})
public class GlossaryitemModule implements CcmModule {
@Override

View File

@ -7,7 +7,7 @@ import static org.librecms.contenttypes.newsitem.NewsitemConstants.*;
import org.hibernate.envers.Audited;
import org.libreccm.cms.contentsection.ContentItem;
import org.librecms.contentsection.ContentItem;
import java.io.Serializable;

View File

@ -12,7 +12,7 @@ import org.libreccm.modules.ShutdownEvent;
import org.libreccm.modules.UnInstallEvent;
@Module(packageName="org.librecms.contenttypes.newsitem",
requiredModules = {@RequiredModule(module = org.libreccm.core.CcmCore)})
requiredModules = {@RequiredModule(module = org.libreccm.core.CcmCore.class)})
public class NewsitemModule implements CcmModule {
@Override

View File

@ -0,0 +1,108 @@
/*
* Copyright (C) 2015 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.security;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.subject.Subject;
import org.libreccm.core.CcmObject;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
import org.libreccm.security.PermissionChecker;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@AuthorizationRequired
@Interceptor
public class AuthorizationInterceptor {
private static final Logger LOGGER = LogManager.getLogger(
AuthorizationInterceptor.class);
@Inject
private Subject subject;
@Inject
private PermissionChecker permissionChecker;
@AroundInvoke
public Object intercept(final InvocationContext context) throws Exception {
LOGGER.debug("Intercepting method invocation");
final Method method = context.getMethod();
if (method == null) {
throw new IllegalArgumentException(
"The authoriziation interceptor can only be used for method");
}
if (method.isAnnotationPresent(RequiresRole.class)) {
final String requiredRole = method.getAnnotation(RequiresRole.class)
.value();
subject.checkRoles(requiredRole);
}
if (method.isAnnotationPresent(RequiresPrivilege.class)) {
final String requiredPrivilege = method.getAnnotation(
RequiresPrivilege.class).value();
permissionChecker.checkPermission(requiredPrivilege);
}
final Annotation[][] annotations = method.getParameterAnnotations();
final Object[] parameters = context.getParameters();
if (parameters != null && parameters.length > 0
&& annotations != null && annotations.length > 0) {
for (int i = 0; i < parameters.length; i++) {
checkParameterPermission(parameters[i], annotations[i]);
}
}
return context.proceed();
}
private void checkParameterPermission(final Object parameter,
final Annotation[] annotations) {
if (parameter instanceof CcmObject
&& annotations != null
&& annotations.length > 0) {
final CcmObject object = (CcmObject) parameter;
String requiredPrivilege = null;
for(Annotation annotation : annotations) {
if (annotation instanceof RequiresPrivilege) {
requiredPrivilege = ((RequiresPrivilege) annotation).value();
break;
}
}
if (requiredPrivilege != null && !requiredPrivilege.isEmpty()) {
permissionChecker.checkPermission(requiredPrivilege, object);
}
}
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2015 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.interceptor.InterceptorBinding;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@Inherited
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@InterceptorBinding
public @interface AuthorizationRequired {
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2015 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@Inherited
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPrivilege {
String value();
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2015 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@Inherited
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRole {
String value();
}

View File

@ -0,0 +1,11 @@
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="annotated">
<interceptors>
<class>org.libreccm.security.AuthorizationInterceptor</class>
</interceptors>
</beans>

View File

@ -0,0 +1,268 @@
/*
* Copyright (C) 2015 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.security;
import com.arsdigita.kernel.KernelConfig;
import com.arsdigita.kernel.security.SecurityConfig;
import com.arsdigita.runtime.AbstractConfig;
import com.arsdigita.util.UncheckedWrapperException;
import com.arsdigita.util.parameter.AbstractParameterContext;
import com.arsdigita.web.CCMApplicationContextListener;
import com.arsdigita.xml.XML;
import com.arsdigita.xml.formatters.DateTimeFormatter;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.subject.Subject;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.ShouldThrowException;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.junit.InSequence;
import org.jboss.arquillian.persistence.CreateSchema;
import org.jboss.arquillian.persistence.PersistenceTest;
import org.jboss.arquillian.persistence.UsingDataSet;
import org.jboss.arquillian.transaction.api.annotation.TransactionMode;
import org.jboss.arquillian.transaction.api.annotation.Transactional;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.resolver.api.maven.Maven;
import org.jboss.shrinkwrap.resolver.api.maven.PomEquippedResolveStage;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.libreccm.categorization.Categorization;
import org.libreccm.core.CcmObject;
import org.libreccm.core.CcmObjectRepository;
import org.libreccm.jpa.EntityManagerProducer;
import org.libreccm.jpa.utils.MimeTypeConverter;
import org.libreccm.l10n.LocalizedString;
import org.libreccm.security.authorization.LabBean;
import org.libreccm.tests.categories.IntegrationTest;
import org.libreccm.testutils.EqualsVerifier;
import org.libreccm.web.CcmApplication;
import java.io.File;
import javax.inject.Inject;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@Category(IntegrationTest.class)
@RunWith(Arquillian.class)
@PersistenceTest
@Transactional(TransactionMode.COMMIT)
@CreateSchema({"create_ccm_core_schema.sql"})
public class AuthorizationInterceptorTest {
@Inject
private Subject subject;
@Inject
private CcmObjectRepository objectRepository;
@Inject
private LabBean labBean;
public AuthorizationInterceptorTest() {
}
@BeforeClass
public static void setUpClass() {
}
@AfterClass
public static void tearDownClass() {
}
@Before
public void setUp() {
}
@After
public void tearDown() {
}
@Deployment
public static WebArchive createDeployment() {
final PomEquippedResolveStage pom = Maven
.resolver()
.loadPomFromFile("pom.xml");
final PomEquippedResolveStage dependencies = pom.
importCompileAndRuntimeDependencies();
final File[] libs = dependencies.resolve().withTransitivity().asFile();
for (File lib : libs) {
System.err.printf("Adding file '%s' to test archive...%n",
lib.getName());
}
return ShrinkWrap
.create(WebArchive.class,
"LibreCCM-org.libreccm.security.PermissionCheckerTest.war")
.addPackage(User.class.getPackage())
.addPackage(CcmObject.class.getPackage())
.addPackage(Categorization.class.getPackage())
.addPackage(LocalizedString.class.getPackage())
.addPackage(CcmApplication.class.getPackage())
.addPackage(EntityManagerProducer.class.getPackage())
.addPackage(MimeTypeConverter.class.getPackage())
.addPackage(EqualsVerifier.class.getPackage())
.addPackage(IntegrationTest.class.getPackage())
.addPackage(KernelConfig.class.getPackage())
.addPackage(SecurityConfig.class.getPackage())
.addPackage(AbstractConfig.class.getPackage())
.addPackage(AbstractParameterContext.class.getPackage())
.addPackage(UncheckedWrapperException.class.getPackage())
.addPackage(CCMApplicationContextListener.class.getPackage())
.addPackage(XML.class.getPackage())
.addPackage(DateTimeFormatter.class.getPackage())
.addPackage(LabBean.class.getPackage())
.addAsLibraries(libs)
.addAsResource("test-persistence.xml",
"META-INF/persistence.xml")
.addAsResource("com/arsdigita/kernel/"
+ "KernelConfig_parameter.properties",
"com/arsdigita/kernel/"
+ "KernelConfig_parameter.properties")
.addAsResource("com/arsdigita/kernel/security/"
+ "SecurityConfig_parameter.properties",
"com/arsdigita/kernel/security/"
+ "SecurityConfig_parameter.properties")
.addAsWebInfResource(
"configs/org/libreccm/security/UserManagerTest/"
+ "registry.properties",
"conf/registry/registry.properties")
.addAsResource(
"configs/org/libreccm/security/UserManagerTest/ccm-core.config",
"ccm-core.config")
.addAsResource(
"configs/org/libreccm/security/ShiroTest/shiro.ini",
"shiro.ini")
.addAsResource(
"configs/org/libreccm/security/ShiroTest/log4j2.xml",
"log4j2.xml")
.addAsWebInfResource(
"configs/org/libreccm/security/ShiroTest/"
+ "kernel.properties",
"conf/registry/ccm-core/kernel.properties")
.addAsWebInfResource(
"configs/org/libreccm//security/ShiroTest/"
+ "security.properties",
"conf/registry/ccm-core/security.properties")
.addAsWebInfResource("test-web.xml", "web.xml")
.addAsWebInfResource("META-INF/beans.xml", "beans.xml");
}
@Test
@InSequence(10)
public void labBeanIsInjected() {
assertThat(labBean, is(not(nullValue())));
}
@Test
@UsingDataSet("datasets/org/libreccm/security/ShiroTest/data.yml")
@InSequence(100)
public void checkRequiresRoleAuthorized() {
final UsernamePasswordToken token = new UsernamePasswordToken("mmuster",
"foo123");
token.setRememberMe(true);
subject.login(token);
labBean.doSomethingWhichRequiresRole();
}
@Test(expected = AuthorizationException.class)
@UsingDataSet("datasets/org/libreccm/security/ShiroTest/data.yml")
@ShouldThrowException(AuthorizationException.class)
@InSequence(200)
public void checkRequiresRoleNotAuthorized() {
final UsernamePasswordToken token = new UsernamePasswordToken("jdoe",
"foo123");
token.setRememberMe(true);
subject.login(token);
labBean.doSomethingWhichRequiresRole();
}
@Test
@UsingDataSet("datasets/org/libreccm/security/ShiroTest/data.yml")
@InSequence(300)
public void checkRequiresPermissionAuthorized() {
final UsernamePasswordToken token = new UsernamePasswordToken("mmuster",
"foo123");
token.setRememberMe(true);
subject.login(token);
labBean.doSomethingWhichRequiresPermission();
}
@Test(expected = AuthorizationException.class)
@UsingDataSet("datasets/org/libreccm/security/ShiroTest/data.yml")
@ShouldThrowException(AuthorizationException.class)
@InSequence(400)
public void checkRequiresPermissionNotAuthorized() {
final UsernamePasswordToken token = new UsernamePasswordToken("jdoe",
"foo123");
token.setRememberMe(true);
subject.login(token);
labBean.doSomethingWhichRequiresPermission();
}
@Test
@UsingDataSet("datasets/org/libreccm/security/ShiroTest/data.yml")
@InSequence(500)
public void checkRequiresPermissionOnObjectAuthorized() {
final UsernamePasswordToken token = new UsernamePasswordToken("mmuster",
"foo123");
token.setRememberMe(true);
subject.login(token);
final CcmObject object1 = objectRepository.findById(-20001L);
labBean.doSomethingWhichRequiresPermissionOnObject(object1);
}
@Test(expected = AuthorizationException.class)
@UsingDataSet("datasets/org/libreccm/security/ShiroTest/data.yml")
@ShouldThrowException(AuthorizationException.class)
@InSequence(600)
public void checkRequiresPermissionOnObjectNotAuthorized() {
final UsernamePasswordToken token = new UsernamePasswordToken("jdoe",
"foo123");
token.setRememberMe(true);
subject.login(token);
final CcmObject object1 = objectRepository.findById(-20001L);
labBean.doSomethingWhichRequiresPermissionOnObject(object1);
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2015 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.security.authorization;
import org.apache.shiro.subject.Subject;
import org.libreccm.core.CcmObject;
import org.libreccm.security.AuthorizationInterceptorTest;
import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.security.RequiresRole;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* A simple bean used by the {@link AuthorizationInterceptorTest}.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
public class LabBean {
@Inject
private Subject subject;
@AuthorizationRequired
@RequiresRole("role1")
public void doSomethingWhichRequiresRole() {
assertThat(subject.hasRole("role1"), is(true));
}
@AuthorizationRequired
@RequiresPrivilege("privilege1")
public void doSomethingWhichRequiresPermission() {
assertThat(subject.isPermitted("privilege1"), is(true));
}
@AuthorizationRequired
public void doSomethingWhichRequiresPermissionOnObject(
@RequiresPrivilege("privilege2")
final CcmObject object) {
assertThat(subject.isPermitted(
String.format("privilege2:%d", object.getObjectId())),
is(true));
}
}

View File

@ -54,10 +54,10 @@
<module>ccm-cms-types-minutes</module>
<module>ccm-cms-types-decisiontree</module>
<module>ccm-cms-types-mparticle</module>
<module>ccm-cms-types-glossaryitem</module>
<module>ccm-cms-types-faqitem</module>
<module>ccm-cms-types-externallink</module>
<module>ccm-cms-types-newsitem</module>
<module>ccm-cms-types-glossaryitem</module>
<module>ccm-cms-types-faqitem</module>
<module>ccm-cms-types-externallink</module>
<module>ccm-cms-types-newsitem</module>
</modules>
<!--<repositories>