Repository: eclipse/leshan
Pull request #226 (2016-12-21 10:47:58)

Add authorizer interface to allow custom LWM2M client authorization
Since we merged registrationStore with observationStore, we doesn't have a way to customize LWM2M client authorization anymore.

So we add a new interface responsible to do that : `Authorizer`.

sbernard31: :gift: for @msangoi, now the `Authorizer` does not depend on `SecurityStore` anymore. The securityStore is set as constructor of the `BasicAuthorizer`



Patch:
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/ResponseCode.java b/leshan-core/src/main/java/org/eclipse/leshan/ResponseCode.java
index 3a324c22..ebadfd35 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/ResponseCode.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/ResponseCode.java
@@ -33,7 +33,7 @@ public enum ResponseCode {
     BAD_REQUEST,
     /** This method (GET/PUT/POST/DELETE) is not allowed on this resource */
     METHOD_NOT_ALLOWED,
-    /** The End-point Client Name results in a duplicate entry on the LWM2M Server */
+    /** The Endpoint Client Name registration in the LWM2M Server is not allowed */
     FORBIDDEN,
     /** Resource not found */
     NOT_FOUND,
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServerBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServerBuilder.java
index c8069013..d328dd4b 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServerBuilder.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServerBuilder.java
@@ -32,6 +32,8 @@ import org.eclipse.leshan.server.californium.impl.LeshanServer;
 import org.eclipse.leshan.server.impl.FileSecurityStore;
 import org.eclipse.leshan.server.model.LwM2mModelProvider;
 import org.eclipse.leshan.server.model.StandardModelProvider;
+import org.eclipse.leshan.server.security.Authorizer;
+import org.eclipse.leshan.server.security.DefaultAuthorizer;
 import org.eclipse.leshan.server.security.SecurityStore;
 
 /**
@@ -50,6 +52,7 @@ public class LeshanServerBuilder {
     private CaliforniumRegistrationStore registrationStore;
     private SecurityStore securityStore;
     private LwM2mModelProvider modelProvider;
+    private Authorizer authorizer;
 
     private InetSocketAddress localAddress;
     private InetSocketAddress localSecureAddress;
@@ -100,6 +103,11 @@ public class LeshanServerBuilder {
         return this;
     }
 
+    public LeshanServerBuilder setAuthorizer(Authorizer authorizer) {
+        this.authorizer = authorizer;
+        return this;
+    }
+
     public LeshanServerBuilder setObjectModelProvider(LwM2mModelProvider objectModelProvider) {
         this.modelProvider = objectModelProvider;
         return this;
@@ -144,6 +152,8 @@ public class LeshanServerBuilder {
             registrationStore = new InMemoryRegistrationStore();
         if (securityStore == null)
             securityStore = new FileSecurityStore();
+        if (authorizer == null)
+            authorizer = new DefaultAuthorizer(securityStore);
         if (modelProvider == null)
             modelProvider = new StandardModelProvider();
         if (encoder == null)
@@ -151,7 +161,7 @@ public class LeshanServerBuilder {
         if (decoder == null)
             decoder = new DefaultLwM2mNodeDecoder();
 
-        return new LeshanServer(localAddress, localSecureAddress, registrationStore, securityStore, modelProvider,
-                encoder, decoder, publicKey, privateKey, certificateChain, trustedCertificates);
+        return new LeshanServer(localAddress, localSecureAddress, registrationStore, securityStore, authorizer,
+                modelProvider, encoder, decoder, publicKey, privateKey, certificateChain, trustedCertificates);
     }
 }
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LeshanServer.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LeshanServer.java
index ea2dc445..2486895e 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LeshanServer.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LeshanServer.java
@@ -57,6 +57,7 @@ import org.eclipse.leshan.server.observation.ObservationService;
 import org.eclipse.leshan.server.registration.RegistrationHandler;
 import org.eclipse.leshan.server.request.LwM2mRequestSender;
 import org.eclipse.leshan.server.response.ResponseListener;
+import org.eclipse.leshan.server.security.Authorizer;
 import org.eclipse.leshan.server.security.SecurityInfo;
 import org.eclipse.leshan.server.security.SecurityStore;
 import org.eclipse.leshan.util.Validate;
@@ -105,6 +106,7 @@ public class LeshanServer implements LwM2mServer {
      * @param localSecureAddress the address to bind the CoAP server for DTLS connection.
      * @param registrationStore the {@link Registration} store.
      * @param securityStore the {@link SecurityInfo} store.
+     * @param authorizer define which devices is allow to register on this server.
      * @param modelProvider provides the objects description for each client.
      * @param decoder decoder used to decode response payload.
      * @param encoder encode used to encode request payload.
@@ -115,7 +117,7 @@ public class LeshanServer implements LwM2mServer {
      * @param trustedCertificates the trusted certificates used to authenticate client certificates.
      */
     public LeshanServer(InetSocketAddress localAddress, InetSocketAddress localSecureAddress,
-            CaliforniumRegistrationStore registrationStore, SecurityStore securityStore,
+            CaliforniumRegistrationStore registrationStore, SecurityStore securityStore, Authorizer authorizer,
             LwM2mModelProvider modelProvider,
             LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder, PublicKey publicKey, PrivateKey privateKey,
             X509Certificate[] x509CertChain, Certificate[] trustedCertificates) {
@@ -123,6 +125,7 @@ public class LeshanServer implements LwM2mServer {
         Validate.notNull(localSecureAddress, "Secure IP address cannot be null");
         Validate.notNull(registrationStore, "registration store cannot be null");
         Validate.notNull(securityStore, "securityStore cannot be null");
+        Validate.notNull(authorizer, "authorizer cannot be null");
         Validate.notNull(modelProvider, "modelProvider cannot be null");
         Validate.notNull(encoder, "encoder cannot be null");
         Validate.notNull(decoder, "decoder cannot be null");
@@ -190,7 +193,7 @@ public class LeshanServer implements LwM2mServer {
 
         // define /rd resource
         final RegisterResource rdResource = new RegisterResource(
-                new RegistrationHandler(this.registrationService, this.securityStore));
+                new RegistrationHandler(this.registrationService, authorizer));
         coapServer.add(rdResource);
 
         // create sender
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java
index 512b386d..e17fd79e 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java
@@ -29,9 +29,7 @@ import org.eclipse.leshan.server.client.Registration;
 import org.eclipse.leshan.server.client.RegistrationService;
 import org.eclipse.leshan.server.client.RegistrationUpdate;
 import org.eclipse.leshan.server.impl.RegistrationServiceImpl;
-import org.eclipse.leshan.server.security.SecurityCheck;
-import org.eclipse.leshan.server.security.SecurityInfo;
-import org.eclipse.leshan.server.security.SecurityStore;
+import org.eclipse.leshan.server.security.Authorizer;
 import org.eclipse.leshan.util.RandomStringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -44,12 +42,12 @@ public class RegistrationHandler {
 
     private static final Logger LOG = LoggerFactory.getLogger(RegistrationHandler.class);
 
-    private SecurityStore securityStore;
     private RegistrationServiceImpl registrationService;
+    private Authorizer authorizer;
 
-    public RegistrationHandler(RegistrationServiceImpl registrationService, SecurityStore securityStore) {
+    public RegistrationHandler(RegistrationServiceImpl registrationService, Authorizer authorizer) {
         this.registrationService = registrationService;
-        this.securityStore = securityStore;
+        this.authorizer = authorizer;
     }
 
     public RegisterResponse register(Identity sender, RegisterRequest registerRequest, InetSocketAddress serverEndpoint) {
@@ -58,11 +56,6 @@ public class RegistrationHandler {
             return RegisterResponse.badRequest(null);
         }
 
-        // We must check if the client is using the right identity.
-        if (!isAuthorized(registerRequest.getEndpointName(), sender)) {
-            return RegisterResponse.forbidden(null);
-        }
-
         Registration.Builder builder = new Registration.Builder(RegistrationHandler.createRegistrationId(),
                 registerRequest.getEndpointName(), sender.getPeerAddress().getAddress(), sender.getPeerAddress()
                         .getPort(), serverEndpoint);
@@ -74,6 +67,11 @@ public class RegistrationHandler {
 
         Registration registration = builder.build();
 
+        // We must check if the client is using the right identity.
+        if (!authorizer.isAuthorized(registerRequest, registration, sender)) {
+            return RegisterResponse.forbidden(null);
+        }
+
         if (registrationService.registerClient(registration)) {
             LOG.debug("New registered client: {}", registration);
             return RegisterResponse.success(registration.getId());
@@ -93,7 +91,9 @@ public class RegistrationHandler {
         if (registration == null) {
             return UpdateResponse.notFound();
         }
-        if (!isAuthorized(registration.getEndpoint(), sender)) {
+        if (!authorizer.isAuthorized(updateRequest, registration, sender)) {
+            // TODO replace by Forbidden if https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/181 is
+            // closed.
             return UpdateResponse.badRequest("forbidden");
         }
 
@@ -117,7 +117,9 @@ public class RegistrationHandler {
         if (registration == null) {
             return DeregisterResponse.notFound();
         }
-        if (!isAuthorized(registration.getEndpoint(), sender)) {
+        if (!authorizer.isAuthorized(deregisterRequest, registration, sender)) {
+            // TODO replace by Forbidden if https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/181 is
+            // closed.
             return DeregisterResponse.badRequest("forbidden");
         }
 
@@ -133,19 +135,4 @@ public class RegistrationHandler {
     private static String createRegistrationId() {
         return RandomStringUtils.random(10, true, true);
     }
-
-    /**
-     * Return true if the client with the given lightweight M2M endPoint is authorized to communicate with the given
-     * security parameters.
-     * 
-     * @param lwM2mEndPointName the lightweight M2M endPoint name
-     * @param clientIdentity the identity at TLS level
-     * @return true if device get authorization
-     */
-    private boolean isAuthorized(String lwM2mEndPointName, Identity clientIdentity) {
-        // do we have security information for this client?
-        SecurityInfo expectedSecurityInfo = securityStore.getByEndpoint(lwM2mEndPointName);
-        return SecurityCheck.checkSecurityInfo(lwM2mEndPointName, clientIdentity, expectedSecurityInfo);
-    }
-
 }
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/Authorizer.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/Authorizer.java
new file mode 100644
index 00000000..735c40e6
--- /dev/null
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/Authorizer.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *    http://www.eclipse.org/org/documents/edl-v10.html.
+ * 
+ * Contributors:
+ *     Sierra Wireless - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.leshan.server.security;
+
+import org.eclipse.leshan.ResponseCode;
+import org.eclipse.leshan.core.request.Identity;
+import org.eclipse.leshan.core.request.UplinkRequest;
+import org.eclipse.leshan.server.client.Registration;
+
+/**
+ * This class is responsible to authorize up-link request from LWM2M client.
+ */
+public interface Authorizer {
+
+    // TODO remove the BAD_REQUEST code if https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/181 is
+    // closed.
+
+    /**
+     * Return true if this request should be handle by the LWM2M Server. When false is returned the LWM2M server will
+     * stop to handle this request and will respond with a {@link ResponseCode#FORBIDDEN} or
+     * {@link ResponseCode#BAD_REQUEST}.
+     * 
+     * @param request the request received
+     * @param registration the registration linked to the received request.<br>
+     *        For register request this is the registration which will be created<br>
+     *        For update request this is the registration before the update was done.
+     * @param senderIdentity the {@link Identity} used to send the request.
+     * 
+     * @return true if this request is authorized.
+     */
+    boolean isAuthorized(UplinkRequest<?> request, Registration registration, Identity senderIdentity);
+}
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/DefaultAuthorizer.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/DefaultAuthorizer.java
new file mode 100644
index 00000000..b604322b
--- /dev/null
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/DefaultAuthorizer.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *    http://www.eclipse.org/org/documents/edl-v10.html.
+ * 
+ * Contributors:
+ *     Sierra Wireless - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.leshan.server.security;
+
+import org.eclipse.leshan.core.request.Identity;
+import org.eclipse.leshan.core.request.UplinkRequest;
+import org.eclipse.leshan.server.client.Registration;
+
+/**
+ * A default {@link Authorizer} implementation
+ *
+ * It checks in {@link SecurityStore} if there is a corresponding {@link SecurityInfo} for this registration endpoint.
+ * If there is a {@link SecurityInfo} it check the identity is correct, else it checks if the LWM2M client use an
+ * unsecure connection.
+ */
+public class DefaultAuthorizer implements Authorizer {
+
+    private SecurityStore securityStore;
+
+    public DefaultAuthorizer(SecurityStore store) {
+        securityStore = store;
+    }
+
+    @Override
+    public boolean isAuthorized(UplinkRequest<?> request, Registration registration, Identity senderIdentity) {
+
+        // do we have security information for this client?
+        SecurityInfo expectedSecurityInfo = null;
+        if (securityStore != null)
+            expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint());
+        return SecurityCheck.checkSecurityInfo(registration.getEndpoint(), senderIdentity, expectedSecurityInfo);
+    }
+}


Test patch:
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/QueueModeIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/QueueModeIntegrationTestHelper.java
index 2a17b44f..73c1ced7 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/QueueModeIntegrationTestHelper.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/QueueModeIntegrationTestHelper.java
@@ -55,6 +55,7 @@ import org.eclipse.leshan.server.queue.impl.InMemoryMessageStore;
 import org.eclipse.leshan.server.queue.impl.QueuedRequestSender;
 import org.eclipse.leshan.server.registration.RegistrationHandler;
 import org.eclipse.leshan.server.request.LwM2mRequestSender;
+import org.eclipse.leshan.server.security.DefaultAuthorizer;
 import org.eclipse.leshan.server.security.EditableSecurityStore;
 
 /**
@@ -98,7 +99,8 @@ public class QueueModeIntegrationTestHelper extends IntegrationTestHelper {
         coapServer.addEndpoint(noSecureEndpoint);
         coapServer.addEndpoint(secureEndpoint);
 
-        RegisterResource rdResource = new RegisterResource(new RegistrationHandler(registrationService, securityStore));
+        RegisterResource rdResource = new RegisterResource(
+                new RegistrationHandler(registrationService, new DefaultAuthorizer(securityStore)));
         coapServer.add(rdResource);
 
         InMemoryMessageStore inMemoryMessageStore = new InMemoryMessageStore();