Whitelist of service operations that are excempt from authorization
In some cases however a service method requires no or very specialized
local authorization. In these cases this
annotation is used to whitelist and mark these business cases and
exclude from regular authorization.
Change-Id: I3ade8e7368f461be7c8afcf132879c372f3da522
Signed-off-by: Ivan Motsch <ivan.motsch@bsiag.com>
Reviewed-on: https://git.eclipse.org/r/92957
Tested-by: Hudson CI
Reviewed-by: Andi Bur <andi.bur@gmail.com>
diff --git a/org.eclipse.scout.rt.server.test/src/test/java/org/eclipse/scout/rt/server/services/RemoteServiceAccessTest.java b/org.eclipse.scout.rt.server.test/src/test/java/org/eclipse/scout/rt/server/services/RemoteServiceAccessDeniedTest.java
similarity index 99%
rename from org.eclipse.scout.rt.server.test/src/test/java/org/eclipse/scout/rt/server/services/RemoteServiceAccessTest.java
rename to org.eclipse.scout.rt.server.test/src/test/java/org/eclipse/scout/rt/server/services/RemoteServiceAccessDeniedTest.java
index 1881ee2..2cc6c3f 100644
--- a/org.eclipse.scout.rt.server.test/src/test/java/org/eclipse/scout/rt/server/services/RemoteServiceAccessTest.java
+++ b/org.eclipse.scout.rt.server.test/src/test/java/org/eclipse/scout/rt/server/services/RemoteServiceAccessDeniedTest.java
@@ -24,7 +24,7 @@
import org.eclipse.scout.rt.shared.servicetunnel.RemoteServiceAccessDenied;
import org.junit.Test;
-public class RemoteServiceAccessTest {
+public class RemoteServiceAccessDeniedTest {
@Test
public void testAnnotations() throws Exception {
diff --git a/org.eclipse.scout.rt.server.test/src/test/java/org/eclipse/scout/rt/server/services/RemoteServiceWithoutAuthorizationTest.java b/org.eclipse.scout.rt.server.test/src/test/java/org/eclipse/scout/rt/server/services/RemoteServiceWithoutAuthorizationTest.java
new file mode 100644
index 0000000..bb97fa3
--- /dev/null
+++ b/org.eclipse.scout.rt.server.test/src/test/java/org/eclipse/scout/rt/server/services/RemoteServiceWithoutAuthorizationTest.java
@@ -0,0 +1,180 @@
+/*******************************************************************************
+ * Copyright (c) 2010-2015 BSI Business Systems Integration AG.
+ * 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
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.scout.rt.server.services;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Method;
+
+import org.eclipse.scout.rt.platform.IgnoreBean;
+import org.eclipse.scout.rt.platform.service.IService;
+import org.eclipse.scout.rt.server.ServiceOperationInvoker;
+import org.eclipse.scout.rt.server.services.common.ping.PingService;
+import org.eclipse.scout.rt.shared.services.common.ping.IPingService;
+import org.eclipse.scout.rt.shared.servicetunnel.RemoteServiceWithoutAuthorization;
+import org.junit.Test;
+
+public class RemoteServiceWithoutAuthorizationTest {
+
+ @Test
+ public void testMustAuthorize() throws Exception {
+ ServiceOperationInvokerMock bo = new ServiceOperationInvokerMock();
+ //
+ assertMustAuthorize(bo, IMockProcessService.class, Object.class.getMethod("hashCode"), IMockProcessService.class);
+ //
+ assertMustAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("hello"), IMockProcessService.class);
+ assertNoAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("interna1"), IMockProcessService.class);
+ assertMustAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("interna2"), IMockProcessService.class);
+ assertMustAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("interna3"), IMockProcessService.class);
+ //
+ assertMustAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("hello"), AbstractMockProcessService.class);
+ assertNoAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("interna1"), AbstractMockProcessService.class);
+ assertNoAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("interna2"), AbstractMockProcessService.class);
+ assertMustAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("interna3"), AbstractMockProcessService.class);
+ //
+ assertMustAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("hello"), MockProcessService1.class);
+ assertNoAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("interna1"), MockProcessService1.class);
+ assertNoAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("interna2"), MockProcessService1.class);
+ assertMustAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("interna3"), MockProcessService1.class);
+ //
+ assertNoAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("hello"), MockProcessService2.class);
+ assertNoAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("interna1"), MockProcessService2.class);
+ assertNoAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("interna2"), MockProcessService2.class);
+ assertNoAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("interna3"), MockProcessService2.class);
+ //
+ assertNoAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("hello"), MockProcessService2Sub.class);
+ assertNoAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("interna1"), MockProcessService2Sub.class);
+ assertNoAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("interna2"), MockProcessService2Sub.class);
+ assertNoAuthorize(bo, IMockProcessService.class, IMockProcessService.class.getMethod("interna3"), MockProcessService2Sub.class);
+ //
+ assertMustAuthorize(bo, IPingService.class, IPingService.class.getMethod("ping", String.class), PingService.class);
+
+ assertMustAuthorize(bo, IMockChildProcessService.class, IMockChildProcessService.class.getMethod("interna1"), MockChildProcessService.class);
+ }
+
+ private static void assertMustAuthorize(ServiceOperationInvokerMock bo, Class<?> serviceInterfaceClass, Method serviceOp, Class<?> serviceImplClass) throws Exception {
+ assertTrue(bo.test(serviceInterfaceClass, serviceOp, serviceImplClass));
+ }
+
+ private static void assertNoAuthorize(ServiceOperationInvokerMock bo, Class<?> serviceInterfaceClass, Method serviceOp, Class<?> serviceImplClass) throws Exception {
+ assertFalse(bo.test(serviceInterfaceClass, serviceOp, serviceImplClass));
+ }
+
+ @IgnoreBean
+ static class ServiceOperationInvokerMock extends ServiceOperationInvoker {
+
+ public ServiceOperationInvokerMock() {
+ super();
+ }
+
+ public boolean test(Class<?> interfaceClass, Method interfaceMethod, Class<?> implClass) throws Exception {
+ return mustAuthorize(interfaceClass, implClass, interfaceMethod, new Object[0]);
+ }
+ }
+
+ interface IMockProcessService extends IService {
+ void hello();
+
+ @RemoteServiceWithoutAuthorization
+ void interna1();
+
+ void interna2();
+
+ void interna3();
+ }
+
+ interface IMockChildProcessService extends IMockProcessService {
+ void internal4();
+
+ @Override
+ void interna1();
+ }
+
+ class MockChildProcessService implements IMockChildProcessService {
+
+ @Override
+ public void hello() {
+ }
+
+ @Override
+ public void interna1() {
+ }
+
+ @Override
+ public void interna2() {
+ }
+
+ @Override
+ public void interna3() {
+ }
+
+ @Override
+ public void internal4() {
+ }
+ }
+
+ abstract class AbstractMockProcessService implements IMockProcessService {
+
+ @Override
+ public void hello() {
+ }
+
+ @Override
+ public void interna1() {
+ }
+
+ @RemoteServiceWithoutAuthorization
+ @Override
+ public void interna2() {
+ }
+ }
+
+ class MockProcessService1 extends AbstractMockProcessService {
+
+ @Override
+ public void hello() {
+ }
+
+ @Override
+ public void interna2() {
+ }
+
+ @Override
+ public void interna3() {
+ }
+ }
+
+ @RemoteServiceWithoutAuthorization
+ class MockProcessService2 extends AbstractMockProcessService {
+
+ @Override
+ public void hello() {
+ }
+
+ @Override
+ public void interna3() {
+ }
+ }
+
+ class MockProcessService2Sub extends MockProcessService2 {
+
+ @RemoteServiceWithoutAuthorization
+ @Override
+ public void hello() {
+ }
+
+ @Override
+ public void interna3() {
+ }
+ }
+
+}
diff --git a/org.eclipse.scout.rt.server/src/main/java/org/eclipse/scout/rt/server/ServiceOperationInvoker.java b/org.eclipse.scout.rt.server/src/main/java/org/eclipse/scout/rt/server/ServiceOperationInvoker.java
index 0badfdf..5bd7e03 100644
--- a/org.eclipse.scout.rt.server/src/main/java/org/eclipse/scout/rt/server/ServiceOperationInvoker.java
+++ b/org.eclipse.scout.rt.server/src/main/java/org/eclipse/scout/rt/server/ServiceOperationInvoker.java
@@ -33,6 +33,7 @@
import org.eclipse.scout.rt.shared.security.RemoteServiceAccessPermission;
import org.eclipse.scout.rt.shared.services.common.security.ACCESS;
import org.eclipse.scout.rt.shared.servicetunnel.RemoteServiceAccessDenied;
+import org.eclipse.scout.rt.shared.servicetunnel.RemoteServiceWithoutAuthorization;
import org.eclipse.scout.rt.shared.servicetunnel.ServiceTunnelRequest;
import org.eclipse.scout.rt.shared.servicetunnel.ServiceTunnelResponse;
import org.eclipse.scout.rt.shared.servicetunnel.ServiceUtility;
@@ -140,7 +141,9 @@
checkServiceAvailable(serviceInterfaceClass, service);
checkRemoteServiceAccessByInterface(serviceInterfaceClass, serviceOp, args);
checkRemoteServiceAccessByAnnotations(serviceInterfaceClass, service.getClass(), serviceOp, args);
- checkRemoteServiceAccessByPermission(serviceInterfaceClass, service.getClass(), serviceOp, args);
+ if (mustAuthorize(serviceInterfaceClass, service.getClass(), serviceOp, args)) {
+ checkRemoteServiceAccessByPermission(serviceInterfaceClass, service.getClass(), serviceOp, args);
+ }
return service; // if we come there, the service is available and valid to call
}
@@ -180,6 +183,8 @@
/**
* Check pass 2 on instance
+ * <p>
+ * Using blacklist {@link RemoteServiceAccessDenied}
*/
protected void checkRemoteServiceAccessByAnnotations(Class<?> interfaceClass, Class<?> implClass, Method interfaceMethod, Object[] args) {
//check: grant/deny annotation (type level is base, method level is finegrained)
@@ -221,15 +226,58 @@
* <p>
* Deny access by default.
* <p>
- * Accepts when a {@link RemoteServiceAccessPermission} was implied.
+ * Accepts when a {@link RemoteServiceAccessPermission} was implied or authorization was waved using whitelist
+ * {@link RemoteServiceWithoutAuthorization} in {@link #mustAuthorize(Class, Class, Method, Object[])}
*/
protected void checkRemoteServiceAccessByPermission(Class<?> interfaceClass, Class<?> implClass, Method interfaceMethod, Object[] args) {
if (ACCESS.check(new RemoteServiceAccessPermission(interfaceClass.getName(), interfaceMethod.getName()))) {
+ //granted
return;
}
throw new SecurityException("access denied (code 3a).");
}
+ /**
+ * @return true unless there is a {@link RemoteServiceWithoutAuthorization} on the called method or interface in the
+ * class tree
+ * @since 6.1
+ */
+ protected boolean mustAuthorize(Class<?> interfaceClass, Class<?> implClass, Method interfaceMethod, Object[] args) {
+ //check: authorize/no-authorize annotation (type level is base, method level is finegrained)
+ Class<?> c = implClass;
+ while (c != null) {
+ //method level
+ Method m = null;
+ try {
+ m = c.getMethod(interfaceMethod.getName(), interfaceMethod.getParameterTypes());
+ }
+ catch (NoSuchMethodException | RuntimeException t) {
+ LOG.debug("Could not lookup service method", t);
+ }
+ if (m != null && m.isAnnotationPresent(RemoteServiceWithoutAuthorization.class)) {
+ //granted
+ return false;
+ }
+
+ //type level
+ if (c.isAnnotationPresent(RemoteServiceWithoutAuthorization.class)) {
+ //granted
+ return false;
+ }
+
+ //next
+ if (c == interfaceClass) {
+ break;
+ }
+ c = c.getSuperclass();
+ if (c == Object.class) {
+ //use interface at last
+ c = interfaceClass;
+ }
+ }
+ return true;
+ }
+
private CallInspector getCallInspector(ServiceTunnelRequest serviceReq, IServerSession serverSession) {
if (serverSession != null) {
SessionInspector sessionInspector = BEANS.get(ProcessInspector.class).getSessionInspector(serverSession, true);
diff --git a/org.eclipse.scout.rt.shared/src/main/java/org/eclipse/scout/rt/shared/clientnotification/IClientNotificationService.java b/org.eclipse.scout.rt.shared/src/main/java/org/eclipse/scout/rt/shared/clientnotification/IClientNotificationService.java
index be782d2..5a3900a 100644
--- a/org.eclipse.scout.rt.shared/src/main/java/org/eclipse/scout/rt/shared/clientnotification/IClientNotificationService.java
+++ b/org.eclipse.scout.rt.shared/src/main/java/org/eclipse/scout/rt/shared/clientnotification/IClientNotificationService.java
@@ -14,12 +14,14 @@
import org.eclipse.scout.rt.platform.ApplicationScoped;
import org.eclipse.scout.rt.shared.TunnelToServer;
+import org.eclipse.scout.rt.shared.servicetunnel.RemoteServiceWithoutAuthorization;
/**
* Service to consume notifications. Accessible from the client.
*/
@ApplicationScoped
@TunnelToServer
+@RemoteServiceWithoutAuthorization
public interface IClientNotificationService {
/**
diff --git a/org.eclipse.scout.rt.shared/src/main/java/org/eclipse/scout/rt/shared/services/common/context/IRunMonitorCancelService.java b/org.eclipse.scout.rt.shared/src/main/java/org/eclipse/scout/rt/shared/services/common/context/IRunMonitorCancelService.java
index 7286feb..1956b41 100644
--- a/org.eclipse.scout.rt.shared/src/main/java/org/eclipse/scout/rt/shared/services/common/context/IRunMonitorCancelService.java
+++ b/org.eclipse.scout.rt.shared/src/main/java/org/eclipse/scout/rt/shared/services/common/context/IRunMonitorCancelService.java
@@ -15,11 +15,13 @@
import org.eclipse.scout.rt.platform.service.IService;
import org.eclipse.scout.rt.platform.util.concurrent.ICancellable;
import org.eclipse.scout.rt.shared.TunnelToServer;
+import org.eclipse.scout.rt.shared.servicetunnel.RemoteServiceWithoutAuthorization;
/**
* Provides cancellation support for operations initiated by the client.
*/
@TunnelToServer
+@RemoteServiceWithoutAuthorization
public interface IRunMonitorCancelService extends IService {
/**
diff --git a/org.eclipse.scout.rt.shared/src/main/java/org/eclipse/scout/rt/shared/servicetunnel/RemoteServiceWithoutAuthorization.java b/org.eclipse.scout.rt.shared/src/main/java/org/eclipse/scout/rt/shared/servicetunnel/RemoteServiceWithoutAuthorization.java
new file mode 100644
index 0000000..94f8c3f
--- /dev/null
+++ b/org.eclipse.scout.rt.shared/src/main/java/org/eclipse/scout/rt/shared/servicetunnel/RemoteServiceWithoutAuthorization.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2010-2015 BSI Business Systems Integration AG.
+ * 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
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.scout.rt.shared.servicetunnel;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.eclipse.scout.rt.shared.security.RemoteServiceAccessPermission;
+
+/**
+ * By default remote service access must be authorized. Typically by a {@link RemoteServiceAccessPermission}
+ * <p>
+ * In some cases however a service method requires no or very specialized local authorization. In these cases this
+ * annotation is used to whitelist and mark these business cases and exclude from regular authorization.
+ * <p>
+ * Warning: This annotation therefore passes unauthorized calls to the annotated method!
+ *
+ * @since 6.1
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface RemoteServiceWithoutAuthorization {
+}