diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/MultiEndpointsTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/MultiEndpointsTest.java new file mode 100644 index 0000000000..1314c75ddb --- /dev/null +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/MultiEndpointsTest.java @@ -0,0 +1,232 @@ +/******************************************************************************* + * Copyright (c) 2024 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.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.integration.tests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.leshan.integration.tests.util.LeshanProxyBuilder.givenReverseProxyFor; +import static org.eclipse.leshan.integration.tests.util.LeshanTestClientBuilder.givenClientUsing; +import static org.eclipse.leshan.integration.tests.util.LeshanTestServerBuilder.givenServerUsing; +import static org.eclipse.leshan.integration.tests.util.assertion.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.stream.Stream; + +import org.eclipse.leshan.client.servers.LwM2mServer; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.endpoint.Protocol; +import org.eclipse.leshan.core.link.LinkParseException; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.response.SendResponse; +import org.eclipse.leshan.integration.tests.util.Failure; +import org.eclipse.leshan.integration.tests.util.LeshanTestClient; +import org.eclipse.leshan.integration.tests.util.LeshanTestServer; +import org.eclipse.leshan.integration.tests.util.LeshanTestServerBuilder; +import org.eclipse.leshan.integration.tests.util.ReverseProxy; +import org.eclipse.leshan.server.registration.Registration; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class MultiEndpointsTest { + + /*---------------------------------/ + * Parameterized Tests + * -------------------------------*/ + @ParameterizedTest(name = "{0} - Client using {1} - Server using {2}") + @MethodSource("transports") + @Retention(RetentionPolicy.RUNTIME) + private @interface TestAllTransportLayer { + } + + static Stream transports() { + return Stream.of(// + // ProtocolUsed - Client Endpoint Provider - Server Endpoint Provider + arguments(Protocol.COAP, "Californium", "Californium"), // + arguments(Protocol.COAP, "Californium", "java-coap"), // + arguments(Protocol.COAP, "java-coap", "Californium"), // + arguments(Protocol.COAP, "java-coap", "java-coap")); + } + + /*---------------------------------/ + * Set-up and Tear-down Tests + * -------------------------------*/ + + LeshanTestServer server; + LeshanTestClient client; + ReverseProxy proxy; + + public void setupTestFor(LeshanTestServerBuilder serverBuilder, Protocol givenProtocol, + String givenClientEndpointProvider) { + // create and start server + server = serverBuilder.build(); + server.start(); + + // create and start proxy + proxy = givenReverseProxyFor(server, givenProtocol); + proxy.start(); + + /// create and start client + client = givenClientUsing(givenProtocol).with(givenClientEndpointProvider).connectingTo(server).behind(proxy) + .build(); + } + + public LeshanTestServerBuilder givenServerWithTwoEndpoint(Protocol givenProtocol, + String givenServerEndpointProvider) { + return givenServerUsing(givenProtocol).with(givenServerEndpointProvider, givenServerEndpointProvider); + } + + @AfterEach + public void stop() throws InterruptedException { + if (client != null) + client.destroy(false); + if (proxy != null) + proxy.stop(); + if (server != null) + server.destroy(); + } + + /*---------------------------------/ + * Tests + * -------------------------------*/ + + @TestAllTransportLayer + public void register_then_update_on_different_endpoint(Protocol givenProtocol, String givenClientEndpointProvider, + String givenServerEndpointProvider) throws LinkParseException { + + // set-up test + setupTestFor(// + givenServerWithTwoEndpoint(givenProtocol, givenServerEndpointProvider), // + givenProtocol, // + givenClientEndpointProvider); + + // Start it and wait for registration + client.start(); + server.waitForNewRegistrationOf(client); + client.waitForRegistrationTo(server); + + // Check client is well registered + assertThat(client).isRegisteredAt(server); + Registration registration = server.getRegistrationFor(client); + + // Check for update + client.triggerRegistrationUpdate(); + client.waitForUpdateTo(server); + server.waitForUpdateOf(registration); + assertThat(client).isRegisteredAt(server); + + // Send request from client to another server endpoint. + proxy.useNextServerAddress(); + + // Check update failed + client.triggerRegistrationUpdate(); + Failure failure = client.waitForUpdateFailureTo(server); + assertThat(failure).failedWith(ResponseCode.BAD_REQUEST); + } + + @TestAllTransportLayer + public void register_then_deregister_on_different_endpoint(Protocol givenProtocol, + String givenClientEndpointProvider, String givenServerEndpointProvider) throws LinkParseException { + // set-up test + setupTestFor(// + givenServerWithTwoEndpoint(givenProtocol, givenServerEndpointProvider), // + givenProtocol, // + givenClientEndpointProvider); + + // Start it and wait for registration + client.start(); + server.waitForNewRegistrationOf(client); + client.waitForRegistrationTo(server); + + // Check client is well registered + assertThat(client).isRegisteredAt(server); + Registration registration = server.getRegistrationFor(client); + + // Check for update + client.triggerRegistrationUpdate(); + client.waitForUpdateTo(server); + server.waitForUpdateOf(registration); + assertThat(client).isRegisteredAt(server); + + // Send request from client to another server endpoint. + proxy.useNextServerAddress(); + + // Deregister + client.stop(true); + Failure failure = client.waitForDeregistrationFailureTo(server); + assertThat(failure).failedWith(ResponseCode.BAD_REQUEST); + } + + @TestAllTransportLayer + public void register_then_send_on_different_endpoint(Protocol givenProtocol, String givenClientEndpointProvider, + String givenServerEndpointProvider) throws LinkParseException, InterruptedException { + + register_then_send_on_different_endpoint(// + givenServerWithTwoEndpoint(givenProtocol, givenServerEndpointProvider), // + givenProtocol, // + givenClientEndpointProvider); + } + + @TestAllTransportLayer + public void register_then_send_on_different_endpoint_with_update_on_send(Protocol givenProtocol, + String givenClientEndpointProvider, String givenServerEndpointProvider) + throws LinkParseException, InterruptedException { + + register_then_send_on_different_endpoint(// + givenServerWithTwoEndpoint(givenProtocol, givenServerEndpointProvider).withUpdateOnSendOperation(), // + givenProtocol, // + givenClientEndpointProvider); + } + + protected void register_then_send_on_different_endpoint(LeshanTestServerBuilder givenServer, Protocol givenProtocol, + String givenClientEndpointProvider) throws LinkParseException, InterruptedException { + + // set-up test + setupTestFor(// + givenServer, // + givenProtocol, // + givenClientEndpointProvider); + + // Start it and wait for registration + client.start(); + server.waitForNewRegistrationOf(client); + client.waitForRegistrationTo(server); + + // Check client is well registered + assertThat(client).isRegisteredAt(server); + Registration registration = server.getRegistrationFor(client); + + // Check for update + client.triggerRegistrationUpdate(); + client.waitForUpdateTo(server); + server.waitForUpdateOf(registration); + assertThat(client).isRegisteredAt(server); + + // Send request from client to another server endpoint. + proxy.useNextServerAddress(); + + // Send Data + Registration registrationBeforeSend = server.getRegistrationFor(client); + LwM2mServer registeredServer = client.getRegisteredServers().values().iterator().next(); + SendResponse response = client.getSendService().sendData(registeredServer, ContentFormat.SENML_JSON, + Arrays.asList("/3/0/1", "/3/0/2"), 1000); + assertThat(response).hasCode(ResponseCode.BAD_REQUEST); + Registration registrationAfterSend = server.getRegistrationFor(client); + assertThat(registrationAfterSend).isEqualTo(registrationBeforeSend); + } +} diff --git a/leshan-lwm2m-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java b/leshan-lwm2m-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java index 815acc0cc3..d284965584 100644 --- a/leshan-lwm2m-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java +++ b/leshan-lwm2m-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java @@ -106,7 +106,7 @@ public JsonNode jSerialize(Registration r) { o.put("qm", r.getQueueMode()); o.put("ep", r.getEndpoint()); o.put("regId", r.getId()); - o.put("epUri", r.getLastEndpointUsed().toString()); + o.put("epUri", r.getEndpointUri().toString()); ArrayNode links = JsonNodeFactory.instance.arrayNode(); for (Link l : r.getObjectLinks()) { @@ -173,20 +173,17 @@ public byte[] bSerialize(Registration r) { } public Registration deserialize(JsonNode jObj) { - EndpointUri lastEndpointUsed; + EndpointUri endpointUri; try { - lastEndpointUsed = uriHandler.createUri(jObj.get("epUri").asText()); + endpointUri = uriHandler.createUri(jObj.get("epUri").asText()); } catch (IllegalStateException e1) { throw new IllegalStateException( String.format("Unable to deserialize last endpoint used URI %s of registration %s/%s", jObj.get("epUri").asText(), jObj.get("regId").asText(), jObj.get("ep").asText())); } -// TODO handle backward compatibility ? -// Registration.Builder b = new Registration.Builder(jObj.get("regId").asText(), jObj.get("ep").asText(), -// IdentitySerDes.deserialize(jObj.get("identity")), lastEndpointUsed); Registration.Builder b = new Registration.Builder(jObj.get("regId").asText(), jObj.get("ep").asText(), - peerSerDes.deserialize(jObj.get("transportdata")), lastEndpointUsed); + peerSerDes.deserialize(jObj.get("transportdata")), endpointUri); b.bindingMode(BindingMode.parse(jObj.get("bnd").asText())); if (jObj.get("qm") != null) diff --git a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/LeshanServer.java b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/LeshanServer.java index d9a31460f4..2654e27ef6 100644 --- a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/LeshanServer.java +++ b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/LeshanServer.java @@ -163,7 +163,7 @@ public LeshanServer(LwM2mServerEndpointsProvider endpointsProvider, Registration presenceService = createPresenceService(registrationService, awakeTimeProvider, updateRegistrationOnNotification); } - this.sendService = createSendHandler(registrationStore, updateRegistrationOnSend); + this.sendService = createSendHandler(registrationStore, authorizer, updateRegistrationOnSend); // create endpoints ServerEndpointToolbox toolbox = new ServerEndpointToolbox(decoder, encoder, linkParser, @@ -202,8 +202,9 @@ protected PresenceServiceImpl createPresenceService(RegistrationService registra return presenceService; } - protected SendHandler createSendHandler(RegistrationStore registrationStore, boolean updateRegistrationOnSend) { - return new SendHandler(registrationStore, updateRegistrationOnSend); + protected SendHandler createSendHandler(RegistrationStore registrationStore, Authorizer authorizer, + boolean updateRegistrationOnSend) { + return new SendHandler(registrationStore, authorizer, updateRegistrationOnSend); } protected UplinkDeviceManagementRequestReceiver createRequestReceiver(RegistrationHandler registrationHandler, diff --git a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/Registration.java b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/Registration.java index 3373af78ae..03d364281f 100644 --- a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/Registration.java +++ b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/Registration.java @@ -90,7 +90,8 @@ public class Registration { private final Map applicationData; - private final EndpointUri lastEndpointUsed; + // URI of endpoint used for this registration. + private final EndpointUri endpointUri; protected Registration(Builder builder) { @@ -98,7 +99,7 @@ protected Registration(Builder builder) { id = builder.registrationId; clientTransportData = builder.clientTransportData; endpoint = builder.endpoint; - lastEndpointUsed = builder.lastEndpointUsed; + endpointUri = builder.endpointUri; // object links related params objectLinks = builder.objectLinks; @@ -355,20 +356,19 @@ public Map getApplicationData() { /** * @return URI of the server endpoint used by client to register. - *

- * This can be changed in next milestones : https://github.com/eclipse/leshan/issues/1415 */ - public EndpointUri getLastEndpointUsed() { - return lastEndpointUsed; + public EndpointUri getEndpointUri() { + return endpointUri; } @Override public String toString() { return String.format( - "Registration [registrationDate=%s, clientTransportData=%s, lifeTimeInSec=%s, smsNumber=%s, lwM2mVersion=%s, bindingMode=%s, queueMode=%s, endpoint=%s, id=%s, objectLinks=%s, additionalRegistrationAttributes=%s, rootPath=%s, supportedContentFormats=%s, supportedObjects=%s, availableInstances=%s, lastUpdate=%s, applicationData=%s]", + "Registration [registrationDate=%s, clientTransportData=%s, lifeTimeInSec=%s, smsNumber=%s, lwM2mVersion=%s, bindingMode=%s, queueMode=%s, endpoint=%s, id=%s, objectLinks=%s, additionalRegistrationAttributes=%s, rootPath=%s, supportedContentFormats=%s, supportedObjects=%s, availableInstances=%s, lastUpdate=%s, applicationData=%s, endpointUri=%s]", registrationDate, clientTransportData, lifeTimeInSec, smsNumber, lwM2mVersion, bindingMode, queueMode, endpoint, id, Arrays.toString(objectLinks), additionalRegistrationAttributes, rootPath, - supportedContentFormats, supportedObjects, availableInstances, lastUpdate, applicationData); + supportedContentFormats, supportedObjects, availableInstances, lastUpdate, applicationData, + endpointUri); } @Override @@ -390,7 +390,7 @@ public final boolean equals(Object o) { && Objects.equals(supportedObjects, that.supportedObjects) && Objects.equals(availableInstances, that.availableInstances) && Objects.equals(lastUpdate, that.lastUpdate) && Objects.equals(applicationData, that.applicationData) - && Objects.equals(lastEndpointUsed, that.lastEndpointUsed); + && Objects.equals(endpointUri, that.endpointUri); } @Override @@ -398,14 +398,14 @@ public final int hashCode() { return Objects.hash(registrationDate, clientTransportData, lifeTimeInSec, smsNumber, lwM2mVersion, bindingMode, queueMode, endpoint, id, Arrays.hashCode(objectLinks), additionalRegistrationAttributes, rootPath, supportedContentFormats, supportedObjects, availableInstances, lastUpdate, applicationData, - lastEndpointUsed); + endpointUri); } public static class Builder { private final String registrationId; private final String endpoint; private final LwM2mPeer clientTransportData; - private final EndpointUri lastEndpointUsed; + private final EndpointUri endpointUri; private Date registrationDate; private Date lastUpdate; @@ -428,7 +428,7 @@ public Builder(Registration registration) { registrationId = registration.id; clientTransportData = registration.clientTransportData; endpoint = registration.endpoint; - lastEndpointUsed = registration.lastEndpointUsed; + endpointUri = registration.endpointUri; // object links related params objectLinks = registration.objectLinks; @@ -450,18 +450,17 @@ public Builder(Registration registration) { applicationData = registration.applicationData; } - public Builder(String registrationId, String endpoint, LwM2mPeer clientTransportData, - EndpointUri lastEndpointUsed) { + public Builder(String registrationId, String endpoint, LwM2mPeer clientTransportData, EndpointUri endpointUri) { Validate.notNull(registrationId); Validate.notEmpty(endpoint); Validate.notNull(clientTransportData); - Validate.notNull(lastEndpointUsed); + Validate.notNull(endpointUri); this.registrationId = registrationId; this.endpoint = endpoint; this.clientTransportData = clientTransportData; - this.lastEndpointUsed = lastEndpointUsed; + this.endpointUri = endpointUri; } public Builder registrationDate(Date registrationDate) { diff --git a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java index 9e0fc47f52..74bfa1da52 100644 --- a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java +++ b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java @@ -98,7 +98,8 @@ public SendableResponse register(LwM2mPeer sender, RegisterReq Registration registrationToApproved = builder.build(); // We check if the client get authorization. - Authorization authorization = authorizer.isAuthorized(registerRequest, registrationToApproved, sender); + Authorization authorization = authorizer.isAuthorized(registerRequest, registrationToApproved, sender, + endpointUsed); if (authorization.isDeclined()) { return new SendableResponse<>(RegisterResponse.forbidden(null)); } @@ -134,7 +135,8 @@ public void run() { return new SendableResponse<>(RegisterResponse.success(approvedRegistration.getId()), whenSent); } - public SendableResponse update(LwM2mPeer sender, UpdateRequest updateRequest) { + public SendableResponse update(LwM2mPeer sender, UpdateRequest updateRequest, + EndpointUri endpointUsed) { // We check if there is a registration to update Registration currentRegistration = registrationService.getById(updateRequest.getRegistrationId()); @@ -143,7 +145,7 @@ public SendableResponse update(LwM2mPeer sender, UpdateRequest u } // We check if the client get authorization. - Authorization authorization = authorizer.isAuthorized(updateRequest, currentRegistration, sender); + Authorization authorization = authorizer.isAuthorized(updateRequest, currentRegistration, sender, endpointUsed); if (authorization.isDeclined()) { return new SendableResponse<>(UpdateResponse.badRequest("forbidden")); } @@ -183,7 +185,8 @@ public void run() { } } - public SendableResponse deregister(LwM2mPeer sender, DeregisterRequest deregisterRequest) { + public SendableResponse deregister(LwM2mPeer sender, DeregisterRequest deregisterRequest, + EndpointUri endpointUsed) { // We check if there is a registration to remove Registration currentRegistration = registrationService.getById(deregisterRequest.getRegistrationId()); @@ -192,7 +195,8 @@ public SendableResponse deregister(LwM2mPeer sender, Deregis } // We check if the client get authorization. - Authorization authorization = authorizer.isAuthorized(deregisterRequest, currentRegistration, sender); + Authorization authorization = authorizer.isAuthorized(deregisterRequest, currentRegistration, sender, + endpointUsed); if (authorization.isDeclined()) { return new SendableResponse<>(DeregisterResponse.badRequest("forbidden")); } diff --git a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java index 540123bd84..aeceb87010 100644 --- a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java +++ b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java @@ -128,7 +128,7 @@ public Registration update(Registration registration) { Date lastUpdate = new Date(); Registration.Builder builder = new Registration.Builder(registration.getId(), registration.getEndpoint(), - transportData, registration.getLastEndpointUsed()); + transportData, registration.getEndpointUri()); builder.registrationDate(lastUpdate) // unmodifiable data diff --git a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/request/DefaultDownlinkRequestSender.java b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/request/DefaultDownlinkRequestSender.java index 44496fab8d..b535ad1256 100644 --- a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/request/DefaultDownlinkRequestSender.java +++ b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/request/DefaultDownlinkRequestSender.java @@ -153,19 +153,19 @@ public void cancelOngoingRequests(Registration registration) { } protected LwM2mServerEndpoint getEndpoint(Registration registration) { - LwM2mServerEndpoint lastEndpointUsed = endpointsProvider.getEndpoint(registration.getLastEndpointUsed()); + LwM2mServerEndpoint endpointUsed = endpointsProvider.getEndpoint(registration.getEndpointUri()); - if (lastEndpointUsed == null) { + if (endpointUsed == null) { String endpoints = endpointsProvider.getEndpoints().stream().map(endpoint -> endpoint.getURI().toString()) .collect(Collectors.joining("\n")); String message = String.format( - "Client %s register itself to %s endpoint, but it seems there is no available endpoints identified byt this URI.%nAvailable endpoints are : \n%s", - registration.getEndpoint(), registration.getLastEndpointUsed(), endpoints); + "Client %s register itself to %s endpoint, but it seems there is no available endpoints identified by this URI.%nAvailable endpoints are : \n%s", + registration.getEndpoint(), registration.getEndpointUri(), endpoints); throw new IllegalStateException(message); } - return lastEndpointUsed; + return endpointUsed; } } diff --git a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/request/DefaultUplinkRequestReceiver.java b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/request/DefaultUplinkRequestReceiver.java index a95ea939b2..646d864ac2 100644 --- a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/request/DefaultUplinkRequestReceiver.java +++ b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/request/DefaultUplinkRequestReceiver.java @@ -62,34 +62,34 @@ public class RequestHandler implements UplinkDeviceMana private final LwM2mPeer sender; private final ClientProfile senderProfile; - private final EndpointUri endpoint; + private final EndpointUri endpointUri; private SendableResponse response; public RequestHandler(LwM2mPeer sender, ClientProfile clientProfile, EndpointUri serverEndpointUri) { this.sender = sender; this.senderProfile = clientProfile; - this.endpoint = serverEndpointUri; + this.endpointUri = serverEndpointUri; } @Override public void visit(RegisterRequest request) { - response = registrationHandler.register(sender, request, endpoint); + response = registrationHandler.register(sender, request, endpointUri); } @Override public void visit(UpdateRequest request) { - response = registrationHandler.update(sender, request); + response = registrationHandler.update(sender, request, endpointUri); } @Override public void visit(DeregisterRequest request) { - response = registrationHandler.deregister(sender, request); + response = registrationHandler.deregister(sender, request, endpointUri); } @Override public void visit(SendRequest request) { - response = sendHandler.handleSend(sender, senderProfile.getRegistration(), request); + response = sendHandler.handleSend(sender, senderProfile.getRegistration(), request, endpointUri); } @SuppressWarnings("unchecked") diff --git a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/security/Authorizer.java b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/security/Authorizer.java index 44b2253e2f..437215ca13 100644 --- a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/security/Authorizer.java +++ b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/security/Authorizer.java @@ -16,6 +16,7 @@ package org.eclipse.leshan.server.security; import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.endpoint.EndpointUri; import org.eclipse.leshan.core.peer.LwM2mPeer; import org.eclipse.leshan.core.request.UplinkRequest; import org.eclipse.leshan.server.registration.Registration; @@ -42,8 +43,10 @@ public interface Authorizer { * For register request this is the registration which will be created
* For update request this is the registration before the update was done. * @param sender the {@link LwM2mPeer} which sent the request. + * @param endpointUri the endpoint URI which receive the request. * * @return an {@link Authorization} status. */ - Authorization isAuthorized(UplinkRequest request, Registration registration, LwM2mPeer sender); + Authorization isAuthorized(UplinkRequest request, Registration registration, LwM2mPeer sender, + EndpointUri endpointUri); } diff --git a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/security/DefaultAuthorizer.java b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/security/DefaultAuthorizer.java index ef2e04e412..898da06c80 100644 --- a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/security/DefaultAuthorizer.java +++ b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/security/DefaultAuthorizer.java @@ -15,7 +15,12 @@ *******************************************************************************/ package org.eclipse.leshan.server.security; +import org.eclipse.leshan.core.endpoint.EndpointUri; import org.eclipse.leshan.core.peer.LwM2mPeer; +import org.eclipse.leshan.core.request.DeregisterRequest; +import org.eclipse.leshan.core.request.RegisterRequest; +import org.eclipse.leshan.core.request.SendRequest; +import org.eclipse.leshan.core.request.UpdateRequest; import org.eclipse.leshan.core.request.UplinkRequest; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.servers.security.Authorization; @@ -45,17 +50,68 @@ public DefaultAuthorizer(SecurityStore store, SecurityChecker checker) { } @Override - public Authorization isAuthorized(UplinkRequest request, Registration registration, LwM2mPeer sender) { + public Authorization isAuthorized(UplinkRequest request, Registration registration, LwM2mPeer sender, + EndpointUri endpointUri) { - // do we have security information for this client? - SecurityInfo expectedSecurityInfo = null; - if (securityStore != null) - expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint()); + if (!checkEndpointUri(request, registration, sender, endpointUri)) { + return Authorization.declined(); + } + + return checkIdentity(request, registration, sender, endpointUri); + } + + protected boolean checkEndpointUri(UplinkRequest request, Registration registration, LwM2mPeer sender, + EndpointUri endpointUri) { + if (!(request instanceof RegisterRequest)) { + // we do not allow to client to switch to another server endpoint within same registration + if (registration.getEndpointUri().equals(endpointUri)) { + return true; + } else { + return false; + } + } + return true; + } - if (securityChecker.checkSecurityInfo(registration.getEndpoint(), sender, expectedSecurityInfo)) { - return Authorization.approved(); + protected Authorization checkIdentity(UplinkRequest request, Registration registration, LwM2mPeer sender, + EndpointUri endpointUri) { + + if (request instanceof RegisterRequest // + || request instanceof UpdateRequest || request instanceof DeregisterRequest) { + + // TODO we should think what should be the right behavior here and maybe changed it if needed + // Meanwhile, to not change behavior we do not check security on with : + // - updateRegistrationOnSend mode + // - updateRegistrationOnNotification mode + // Those modes are out of specification and not recommended. + if ( + // We use HACK to know if those mode are used + + // means updateRegistrationOnSend is used (because trigger by given SEND request) + request.getCoapRequest() instanceof SendRequest) // + { + return Authorization.approved(); + } + + // For Register, Update, DeregisterRequest we check in security store + // do we have security information for this client? + SecurityInfo expectedSecurityInfo = null; + if (securityStore != null) + expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint()); + + if (securityChecker.checkSecurityInfo(registration.getEndpoint(), sender, expectedSecurityInfo)) { + return Authorization.approved(); + } else { + return Authorization.declined(); + } } else { - return Authorization.declined(); + // for other we just check this is same identity + if (registration.getClientTransportData().getIdentity().equals(sender.getIdentity())) { + return Authorization.approved(); + } else { + return Authorization.declined(); + } } } + } diff --git a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/send/SendHandler.java b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/send/SendHandler.java index db07b0a9ec..c2e3b07e79 100644 --- a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/send/SendHandler.java +++ b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/send/SendHandler.java @@ -19,11 +19,14 @@ import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.endpoint.EndpointUri; import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.peer.LwM2mPeer; import org.eclipse.leshan.core.request.SendRequest; +import org.eclipse.leshan.core.request.UpdateRequest; import org.eclipse.leshan.core.request.exception.InvalidRequestException; import org.eclipse.leshan.core.response.SendResponse; import org.eclipse.leshan.core.response.SendableResponse; @@ -31,6 +34,8 @@ import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.registration.RegistrationUpdate; import org.eclipse.leshan.server.registration.UpdatedRegistration; +import org.eclipse.leshan.server.security.Authorizer; +import org.eclipse.leshan.servers.security.Authorization; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,12 +49,14 @@ public class SendHandler implements SendService { private final Logger LOG = LoggerFactory.getLogger(SendHandler.class); private final RegistrationStore registrationStore; + private final Authorizer authorizer; private final boolean updateRegistrationOnSend; private final List listeners = new CopyOnWriteArrayList<>();; - public SendHandler(RegistrationStore registrationStore, boolean updateRegistrationOnSend) { + public SendHandler(RegistrationStore registrationStore, Authorizer authorizer, boolean updateRegistrationOnSend) { this.registrationStore = registrationStore; + this.authorizer = authorizer; this.updateRegistrationOnSend = updateRegistrationOnSend; } @@ -64,62 +71,87 @@ public void removeListener(SendListener listener) { } public SendableResponse handleSend(LwM2mPeer sender, Registration registration, - final SendRequest request) { + final SendRequest request, EndpointUri serverEndpointUri) { - // try to update registration if needed + // Try to update registration if needed final Registration updatedRegistration; try { - updatedRegistration = updateRegistration(sender, registration); + updatedRegistration = updateRegistration(sender, registration, request, serverEndpointUri); + if (updatedRegistration == null) { + return errorReponse(updatedRegistration, ResponseCode.BAD_REQUEST, "not authorized", null); + } } catch (Exception e) { - String errMsg = "unable to update registration"; - SendableResponse response = new SendableResponse<>(SendResponse.internalServerError(errMsg), - new Runnable() { - @Override - public void run() { - onError(registration, errMsg, e); - } - }); - return response; + return errorReponse(registration, ResponseCode.INTERNAL_SERVER_ERROR, "unable to update registration", e); + } + + // Check if send request is allowed + Authorization authorized = authorizer.isAuthorized(request, registration, sender, serverEndpointUri); + if (authorized.isDeclined()) { + return errorReponse(updatedRegistration, ResponseCode.BAD_REQUEST, "not authorized", null); } - // Send Response to send request on success + // Validate and create Send Response final SendResponse sendResponse = validateSendRequest(updatedRegistration, request); - SendableResponse response = new SendableResponse<>(sendResponse, new Runnable() { - - @Override - public void run() { - if (sendResponse.isSuccess()) { - fireDataReceived(updatedRegistration, request.getTimestampedNodes(), request); - } else { - onError(updatedRegistration, String.format("Invalid Send Request, server returns %s %s", // - sendResponse.getCode().getName(), // - sendResponse.getErrorMessage() != null ? "because" + sendResponse.getErrorMessage() : ""), - new InvalidRequestException( - sendResponse.getErrorMessage() != null ? sendResponse.getErrorMessage() - : "unknown reason")); - } - } - }); + SendableResponse response; + if (sendResponse.isSuccess()) { + response = new SendableResponse<>(sendResponse, new Runnable() { + @Override + public void run() { + if (sendResponse.isSuccess()) { + fireDataReceived(updatedRegistration, request.getTimestampedNodes(), request); + } + } + }); + } else { + response = errorReponse(updatedRegistration, // + sendResponse.getCode(), // + String.format("Invalid Send Request, server returns %s %s", // + sendResponse.getCode().getName(), // + sendResponse.getErrorMessage() != null ? "because" + sendResponse.getErrorMessage() : ""), // + new InvalidRequestException(sendResponse.getErrorMessage() != null ? sendResponse.getErrorMessage() + : "unknown reason")); + } return response; } - private Registration updateRegistration(LwM2mPeer sender, final Registration registration) { - if (updateRegistrationOnSend) { - RegistrationUpdate regUpdate = new RegistrationUpdate(registration.getId(), sender, null, null, null, null, - null, null, null, null, null, null); - UpdatedRegistration updatedRegistration = registrationStore.updateRegistration(regUpdate); - if (updatedRegistration == null || updatedRegistration.getUpdatedRegistration() == null) { - String errorMsg = String.format( - "Unexpected error when receiving Send Request: There is no registration with id %s", - registration.getId()); - LOG.error(errorMsg); - throw new IllegalStateException(errorMsg); - } - return updatedRegistration.getUpdatedRegistration(); + /** + * Update registration if needed and allowed by authorizer. + * + * Note that updating registration on send is out of specification and could be a problem for interoperability. + * + * @return the registration or the updated registration or null if not allowed to update. + */ + protected Registration updateRegistration(LwM2mPeer sender, final Registration registration, + final SendRequest request, EndpointUri endpointUri) { + if (!updateRegistrationOnSend) { + // mode is not activate so we don't update registration + return registration; + + } + + // check if update is allowed + // HACK we create and Update request + // it can be identified because we pass the SEND request as under-layer object) + UpdateRequest updateRequest = new UpdateRequest(registration.getId(), null, null, null, null, null, request); + Authorization authorized = authorizer.isAuthorized(updateRequest, registration, sender, endpointUri); + if (authorized.isDeclined()) { + return null; + } + + // update registration + RegistrationUpdate regUpdate = new RegistrationUpdate(registration.getId(), sender, null, null, null, null, + null, null, null, null, null, null); + UpdatedRegistration updatedRegistration = registrationStore.updateRegistration(regUpdate); + if (updatedRegistration == null || updatedRegistration.getUpdatedRegistration() == null) { + String errorMsg = String.format( + "Unexpected error when receiving Send Request: There is no registration with id %s", + registration.getId()); + LOG.error(errorMsg); + throw new IllegalStateException(errorMsg); } - return registration; + return updatedRegistration.getUpdatedRegistration(); } protected void fireDataReceived(Registration registration, TimestampedLwM2mNodes data, SendRequest request) { @@ -155,4 +187,17 @@ protected SendResponse validateSendRequest(Registration registration, SendReques } return SendResponse.success(); } + + protected SendableResponse errorReponse(Registration registration, ResponseCode code, + String errorMessage, Exception e) { + + SendableResponse response = new SendableResponse<>(new SendResponse(code, errorMessage), + new Runnable() { + @Override + public void run() { + onError(registration, errorMessage, e); + } + }); + return response; + } } diff --git a/leshan-lwm2m-server/src/test/java/org/eclipse/leshan/server/registration/RegistrationHandlerTest.java b/leshan-lwm2m-server/src/test/java/org/eclipse/leshan/server/registration/RegistrationHandlerTest.java index 1a5721d5d7..93734bbde8 100644 --- a/leshan-lwm2m-server/src/test/java/org/eclipse/leshan/server/registration/RegistrationHandlerTest.java +++ b/leshan-lwm2m-server/src/test/java/org/eclipse/leshan/server/registration/RegistrationHandlerTest.java @@ -76,7 +76,8 @@ public void test_application_data_from_authorizer() { authorizer.willReturn(Authorization.approved(updatedAppData)); // handle UPDATE request - registrationHandler.update(givenIdentity(), givenUpdateRequestWithID(registration.getId())); + registrationHandler.update(givenIdentity(), givenUpdateRequestWithID(registration.getId()), + givenServerEndpointUri()); // check result registration = registrationStore.getRegistrationByEndpoint("myEndpoint"); @@ -105,7 +106,8 @@ public void test_update_without_application_data_from_authorizer() { authorizer.willReturn(Authorization.approved()); // handle UPDATE request - registrationHandler.update(givenIdentity(), givenUpdateRequestWithID(registration.getId())); + registrationHandler.update(givenIdentity(), givenUpdateRequestWithID(registration.getId()), + givenServerEndpointUri()); // check result registration = registrationStore.getRegistrationByEndpoint("myEndpoint"); @@ -142,7 +144,8 @@ public void willReturn(Authorization authorization) { } @Override - public Authorization isAuthorized(UplinkRequest request, Registration registration, LwM2mPeer sender) { + public Authorization isAuthorized(UplinkRequest request, Registration registration, LwM2mPeer sender, + EndpointUri endpointUri) { return autorization; } }