From 2ba9fd135d54e76d1a605d86f2215be5d786b811 Mon Sep 17 00:00:00 2001 From: Michael Hirsch Date: Wed, 3 Aug 2016 09:08:03 +0200 Subject: [PATCH] Add missing PreAuthorize annotation to secure methods and write unit test Signed-off-by: Michael Hirsch --- .../hawkbit-repository-api/pom.xml | 65 ++++++---- .../repository/ControllerManagement.java | 2 + .../repository/SoftwareManagement.java | 1 + ...gementMethodPreAuthorizeAnnotatedTest.java | 121 ++++++++++++++++++ .../repository/jpa/MethodSecurityUtil.java | 63 --------- .../im/authentication/SpPermission.java | 8 ++ 6 files changed, 170 insertions(+), 90 deletions(-) create mode 100644 hawkbit-repository/hawkbit-repository-api/src/test/java/org/eclipse/hawkbit/repository/RepositoryManagementMethodPreAuthorizeAnnotatedTest.java delete mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/MethodSecurityUtil.java diff --git a/hawkbit-repository/hawkbit-repository-api/pom.xml b/hawkbit-repository/hawkbit-repository-api/pom.xml index 050a9a070..cfd45998c 100644 --- a/hawkbit-repository/hawkbit-repository-api/pom.xml +++ b/hawkbit-repository/hawkbit-repository-api/pom.xml @@ -1,24 +1,16 @@ - + - 4.0.0 - - org.eclipse.hawkbit - hawkbit-repository - 0.2.0-SNAPSHOT - - hawkbit-repository-api - hawkBit :: Repository API - - + 4.0.0 + + org.eclipse.hawkbit + hawkbit-repository + 0.2.0-SNAPSHOT + + hawkbit-repository-api + hawkBit :: Repository API + + org.eclipse.hawkbit hawkbit-security-core @@ -36,14 +28,33 @@ cz.jirutka.rsql rsql-parser - + org.springframework.hateoas spring-hateoas - - org.springframework.boot - spring-boot-configuration-processor - true - - + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + ru.yandex.qatools.allure + allure-junit-adaptor + test + + + org.easytesting + fest-assert-core + test + + + org.easytesting + fest-assert + test + + \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java index e5dcb8f74..f06621fd4 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java @@ -183,6 +183,8 @@ public interface ControllerManagement { * @return the security context of the target, in case no target exists for * the given controllerId {@code null} is returned */ + @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.HAS_AUTH_READ_TARGET_SEC_TOKEN) String getSecurityTokenByControllerId(@NotEmpty String controllerId); /** diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareManagement.java index 0934e871e..bc3c50004 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareManagement.java @@ -340,6 +340,7 @@ public interface SoftwareManagement { * to search for * @return {@link List} of found {@link SoftwareModule}s */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) List findSoftwareModulesById(@NotEmpty Collection ids); /** diff --git a/hawkbit-repository/hawkbit-repository-api/src/test/java/org/eclipse/hawkbit/repository/RepositoryManagementMethodPreAuthorizeAnnotatedTest.java b/hawkbit-repository/hawkbit-repository-api/src/test/java/org/eclipse/hawkbit/repository/RepositoryManagementMethodPreAuthorizeAnnotatedTest.java new file mode 100644 index 000000000..1ccbb8bc5 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/test/java/org/eclipse/hawkbit/repository/RepositoryManagementMethodPreAuthorizeAnnotatedTest.java @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import static org.fest.assertions.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +import org.junit.Test; +import org.springframework.security.access.prepost.PreAuthorize; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Unit Tests - Repository") +@Stories("Security Test") +public class RepositoryManagementMethodPreAuthorizeAnnotatedTest { + + private static final Set METHOD_SECURITY_EXCLUSION = new HashSet<>(); + + static { + METHOD_SECURITY_EXCLUSION.add(getMethod(SystemManagement.class, "currentTenant")); + } + + @Test + @Description("Verfies that repository methods are @PreAuthorize annotated") + public void repositoryManagementMethodsArePreAuthorizedAnnotated() + throws ClassNotFoundException, URISyntaxException, IOException { + final List> findInterfacesInPackage = findInterfacesInPackage(getClass().getPackage(), + Pattern.compile(".*Management")); + for (final Class interfaceToCheck : findInterfacesInPackage) { + assertDeclaredMethodsContainsPreAuthorizeAnnotaions(interfaceToCheck); + } + } + + /** + * asserts that the given methods are annotated with the + * {@link PreAuthorize} annotation for security. Inherited methods are not + * checked. The following methods are excluded due inherited from + * {@link Object}, like equals() or toString(). + * + * @param clazz + * the class to retrieve the public declared methods + */ + private static void assertDeclaredMethodsContainsPreAuthorizeAnnotaions(final Class clazz) { + final Method[] declaredMethods = clazz.getDeclaredMethods(); + for (final Method method : declaredMethods) { + if (!METHOD_SECURITY_EXCLUSION.contains(method) && !method.isSynthetic() + && Modifier.isPublic(method.getModifiers())) { + final PreAuthorize annotation = method.getAnnotation(PreAuthorize.class); + assertThat(annotation).as("The public method " + method.getName() + " in class " + clazz.getName() + + " is not annoated with @PreAuthorize, security leak?").isNotNull(); + } + } + } + + /** + * Finds all interfaces in a given packages which matches the given filter. + * + * @param p + * the package to search for interfaces in + * @param includeFilter + * the pattern which interfaces class names should be included + * @return a list of loaded interfaces in a specific package and matches the + * given filter + * @throws URISyntaxException + * @throws IOException + * @throws ClassNotFoundException + */ + private List> findInterfacesInPackage(final Package p, final Pattern includeFilter) + throws URISyntaxException, IOException, ClassNotFoundException { + final List> interfacesToReturn = new ArrayList<>(); + final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + final Enumeration resources = classLoader.getResources(p.getName().replace(".", "/")); + while (resources.hasMoreElements()) { + final File packageDirectory = new File(new URI(resources.nextElement().toString()).getPath()); + final File[] filesInPackage = packageDirectory.listFiles(); + for (final File classFile : filesInPackage) { + final String classNameWithExtension = classFile.getName(); + final int indexOfExtension = classNameWithExtension.indexOf(".class"); + if (indexOfExtension > 0) { + final String classNameWithoutExtension = classNameWithExtension.substring(0, indexOfExtension); + if (includeFilter.matcher(classNameWithoutExtension).matches()) { + final Class classInPackage = Class.forName(p.getName() + "." + classNameWithoutExtension); + if (classInPackage.isInterface()) { + interfacesToReturn.add(classInPackage); + } + } + } + } + } + return interfacesToReturn; + } + + private static Method getMethod(final Class clazz, final String methodName, final Class... parameterTypes) { + try { + return clazz.getMethod(methodName, parameterTypes); + } catch (NoSuchMethodException | SecurityException e) { + throw new RuntimeException(e.getMessage(), e); + } + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/MethodSecurityUtil.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/MethodSecurityUtil.java deleted file mode 100644 index 032289e87..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/MethodSecurityUtil.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.repository.jpa; - -import static org.fest.assertions.Assertions.assertThat; - -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.HashSet; -import java.util.Set; - -import org.springframework.security.access.prepost.PreAuthorize; - -public class MethodSecurityUtil { - - private static final Set METHOD_SECURITY_EXCLUSION = new HashSet<>(); - - static { - METHOD_SECURITY_EXCLUSION.add("equals"); - METHOD_SECURITY_EXCLUSION.add("toString"); - METHOD_SECURITY_EXCLUSION.add("hashCode"); - METHOD_SECURITY_EXCLUSION.add("clone"); - METHOD_SECURITY_EXCLUSION.add("setEnvironment"); - // this method shouldn't be public on the DeploymentManagemeht but it is - METHOD_SECURITY_EXCLUSION.add("setOverrideObsoleteUpdateActions"); - METHOD_SECURITY_EXCLUSION.add("isOverrideObsoleteUpdateActions"); - // this method must be public accessible without security because it's - // necessary to acccess - // the security-token of a target without being authenticated because - // the security-token is - // the authentication process - // ControllerManagement#getSecurityTokenByControllerId() - METHOD_SECURITY_EXCLUSION.add("getSecurityTokenByControllerId"); - } - - /** - * asserts that the given methods are annotated with the - * {@link PreAuthorize} annotation for security. Inherited methods are not - * checked. The following methods are excluded due inherited from - * {@link Object}, like equals() or toString(). - * - * @param clazz - * the class to retrieve the public declared methods - */ - public static void assertDeclaredMethodsContainsPreAuthorizeAnnotaions(final Class clazz) { - final Method[] declaredMethods = clazz.getDeclaredMethods(); - for (final Method method : declaredMethods) { - if (!METHOD_SECURITY_EXCLUSION.contains(method.getName()) && !method.isSynthetic() - && Modifier.isPublic(method.getModifiers())) { - final PreAuthorize annotation = method.getAnnotation(PreAuthorize.class); - assertThat(annotation).as( - "The public method " + method.getName() + " is not annoated with @PreAuthorize, security leak?") - .isNotNull(); - } - } - } -} diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java index daf5f0dd9..783f27b7e 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java @@ -290,6 +290,14 @@ public final class SpPermission { public static final String HAS_AUTH_READ_TARGET = HAS_AUTH_PREFIX + READ_TARGET + HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE; + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#READ_TARGET_SEC_TOKEN} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_READ_TARGET_SEC_TOKEN = HAS_AUTH_PREFIX + READ_TARGET_SEC_TOKEN + + HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE; + /** * Spring security eval hasAuthority expression to check if spring * context contains {@link SpPermission#CREATE_TARGET} or