Skip to content

Commit 9e95051

Browse files
Merge pull request #13 from RWTH-i5-IDSG/master
merge
2 parents 2505f28 + 10d0794 commit 9e95051

File tree

5 files changed

+165
-97
lines changed

5 files changed

+165
-97
lines changed

src/main/java/de/rwth/idsg/steve/config/OcppConfiguration.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ public class OcppConfiguration {
4747
@Autowired private ocpp.cs._2015._10.CentralSystemService ocpp16Server;
4848

4949
@Autowired
50-
@Qualifier("FromAddressInterceptor")
51-
private PhaseInterceptor<Message> fromAddressInterceptor;
50+
@Qualifier("MessageHeaderInterceptor")
51+
private PhaseInterceptor<Message> messageHeaderInterceptor;
5252

5353
@PostConstruct
5454
public void init() {
55-
List<Interceptor<? extends Message>> interceptors = asList(new MessageIdInterceptor(), fromAddressInterceptor);
55+
List<Interceptor<? extends Message>> interceptors = asList(new MessageIdInterceptor(), messageHeaderInterceptor);
5656
List<Feature> logging = singletonList(LoggingFeatureProxy.INSTANCE.get());
5757

5858
createOcppService(ocpp12Server, "/CentralSystemServiceOCPP12", interceptors, logging);

src/main/java/de/rwth/idsg/steve/ocpp/soap/FromAddressInterceptor.java

Lines changed: 0 additions & 93 deletions
This file was deleted.
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package de.rwth.idsg.steve.ocpp.soap;
2+
3+
import de.rwth.idsg.steve.ocpp.OcppProtocol;
4+
import de.rwth.idsg.steve.repository.ChargePointRepository;
5+
import de.rwth.idsg.steve.repository.OcppServerRepository;
6+
import de.rwth.idsg.steve.repository.impl.ChargePointRepositoryImpl;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.apache.cxf.binding.soap.Soap12;
9+
import org.apache.cxf.binding.soap.SoapFault;
10+
import org.apache.cxf.interceptor.Fault;
11+
import org.apache.cxf.message.Message;
12+
import org.apache.cxf.message.MessageContentsList;
13+
import org.apache.cxf.phase.AbstractPhaseInterceptor;
14+
import org.apache.cxf.phase.Phase;
15+
import org.apache.cxf.service.model.MessageInfo;
16+
import org.apache.cxf.service.model.MessagePartInfo;
17+
import org.apache.cxf.ws.addressing.AddressingProperties;
18+
import org.apache.cxf.ws.addressing.ContextUtils;
19+
import org.apache.cxf.ws.addressing.EndpointReferenceType;
20+
import org.springframework.beans.factory.annotation.Autowired;
21+
import org.springframework.stereotype.Component;
22+
23+
import javax.xml.namespace.QName;
24+
import java.util.concurrent.ScheduledExecutorService;
25+
26+
import static org.apache.cxf.ws.addressing.JAXWSAConstants.ADDRESSING_PROPERTIES_INBOUND;
27+
28+
/**
29+
* 1. Checks the registration status of a station for operations other than BootNotification.
30+
*
31+
* 2. Intercepts incoming OCPP messages to update the endpoint address ("From" field of the WS-A header) in DB.
32+
* And the absence of the field is not a deal breaker anymore. But, as a side effect, the user will not be able
33+
* to send commands to the charging station, since the DB call to list the charge points will filter it out. See
34+
* {@link ChargePointRepositoryImpl#getChargePointSelect(OcppProtocol)}.
35+
*
36+
* @author Sevket Goekay <goekay@dbis.rwth-aachen.de>
37+
* @since 15.06.2015
38+
*/
39+
@Slf4j
40+
@Component("MessageHeaderInterceptor")
41+
public class MessageHeaderInterceptor extends AbstractPhaseInterceptor<Message> {
42+
43+
@Autowired private OcppServerRepository ocppServerRepository;
44+
@Autowired private ChargePointRepository chargePointRepository;
45+
@Autowired private ScheduledExecutorService executorService;
46+
47+
private static final String BOOT_OPERATION_NAME = "BootNotification";
48+
private static final String CHARGEBOX_ID_HEADER = "ChargeBoxIdentity";
49+
50+
public MessageHeaderInterceptor() {
51+
super(Phase.PRE_INVOKE);
52+
}
53+
54+
@Override
55+
public void handleMessage(Message message) throws Fault {
56+
String chargeBoxId = getChargeBoxId(message);
57+
58+
// -------------------------------------------------------------------------
59+
// 1. check registration for operations other than BootNotification
60+
// -------------------------------------------------------------------------
61+
62+
QName opName = message.getExchange().getBindingOperationInfo().getOperationInfo().getName();
63+
64+
if (!BOOT_OPERATION_NAME.equals(opName.getLocalPart())) {
65+
if (!chargePointRepository.isRegistered(chargeBoxId)) {
66+
throw createAuthFault(opName);
67+
}
68+
}
69+
70+
// -------------------------------------------------------------------------
71+
// 2. update endpoint
72+
// -------------------------------------------------------------------------
73+
74+
executorService.execute(() -> {
75+
try {
76+
String endpointAddress = getEndpointAddress(message);
77+
if (endpointAddress != null) {
78+
ocppServerRepository.updateEndpointAddress(chargeBoxId, endpointAddress);
79+
}
80+
} catch (Exception e) {
81+
log.error("Exception occurred", e);
82+
}
83+
});
84+
}
85+
86+
private String getChargeBoxId(Message message) {
87+
MessageContentsList lst = MessageContentsList.getContentsList(message);
88+
if (lst != null) {
89+
MessageInfo mi = (MessageInfo) message.get("org.apache.cxf.service.model.MessageInfo");
90+
for (MessagePartInfo mpi : mi.getMessageParts()) {
91+
if (CHARGEBOX_ID_HEADER.equals(mpi.getName().getLocalPart())) {
92+
return (String) lst.get(mpi);
93+
}
94+
}
95+
}
96+
// should not happen
97+
throw createSpecFault(message.getExchange().getBindingOperationInfo().getOperationInfo().getName());
98+
}
99+
100+
private String getEndpointAddress(Message message) {
101+
AddressingProperties addressProp = (AddressingProperties) message.get(ADDRESSING_PROPERTIES_INBOUND);
102+
if (addressProp == null) {
103+
return null;
104+
}
105+
106+
EndpointReferenceType from = addressProp.getFrom();
107+
if (ContextUtils.isGenericAddress(from)) {
108+
return null;
109+
} else {
110+
return from.getAddress().getValue();
111+
}
112+
}
113+
114+
private static SoapFault createAuthFault(QName qName) {
115+
// as defined by OCPP spec
116+
String message = "Sender failed authentication or is not authorized to use the requested operation.";
117+
SoapFault sf = new SoapFault(message, Soap12.getInstance().getSender());
118+
sf.addSubCode(new QName(qName.getNamespaceURI(), "SecurityError"));
119+
return sf;
120+
}
121+
122+
private static SoapFault createSpecFault(QName qName) {
123+
// as defined by OCPP spec
124+
String message = "Sender's message does not comply with protocol specification.";
125+
SoapFault sf = new SoapFault(message, Soap12.getInstance().getSender());
126+
sf.addSubCode(new QName(qName.getNamespaceURI(), "ProtocolError"));
127+
return sf;
128+
}
129+
}

src/test/java/de/rwth/idsg/steve/ApplicationTest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import org.junit.BeforeClass;
1515
import org.junit.Test;
1616

17+
import javax.xml.ws.soap.SOAPFaultException;
18+
1719
import static de.rwth.idsg.steve.utils.Helpers.getForOcpp12;
1820
import static de.rwth.idsg.steve.utils.Helpers.getForOcpp15;
1921
import static de.rwth.idsg.steve.utils.Helpers.getForOcpp16;
@@ -88,7 +90,10 @@ public void testOcpp15() {
8890
Assert.assertEquals(ocpp.cs._2012._06.AuthorizationStatus.ACCEPTED, auth.getIdTagInfo().getStatus());
8991
}
9092

91-
@Test
93+
/**
94+
* SOAPFaultException because we are sending an AuthorizeRequest from an random/unknown station.
95+
*/
96+
@Test(expected = SOAPFaultException.class)
9297
public void testOcpp16() {
9398
ocpp.cs._2015._10.CentralSystemService client = getForOcpp16(path);
9499

src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package de.rwth.idsg.steve;
22

3+
import de.rwth.idsg.steve.ocpp.OcppProtocol;
4+
import de.rwth.idsg.steve.ocpp.soap.MessageHeaderInterceptor;
5+
import de.rwth.idsg.steve.ocpp.ws.OcppWebSocketUpgrader;
36
import de.rwth.idsg.steve.repository.ReservationStatus;
47
import de.rwth.idsg.steve.repository.dto.ChargePoint;
58
import de.rwth.idsg.steve.repository.dto.ConnectorStatus;
69
import de.rwth.idsg.steve.repository.dto.Reservation;
710
import de.rwth.idsg.steve.repository.dto.Transaction;
811
import de.rwth.idsg.steve.repository.dto.TransactionDetails;
12+
import de.rwth.idsg.steve.service.CentralSystemService16_Service;
913
import de.rwth.idsg.steve.utils.__DatabasePreparer__;
1014
import jooq.steve.db.tables.records.TransactionRecord;
1115
import lombok.extern.slf4j.Slf4j;
@@ -38,6 +42,7 @@
3842
import org.junit.BeforeClass;
3943
import org.junit.Test;
4044

45+
import javax.xml.ws.soap.SOAPFaultException;
4146
import java.util.Arrays;
4247
import java.util.List;
4348

@@ -97,6 +102,28 @@ public void testUnregisteredCP() {
97102
Assert.assertNotEquals(RegistrationStatus.ACCEPTED, boot.getStatus());
98103
}
99104

105+
/**
106+
* Reason: We started to check registration status by intercepting every SOAP message other than BootNotification
107+
* in {@link MessageHeaderInterceptor} and throw exception if station is not registered and auto-register is
108+
* disabled (and therefore early-exit the processing pipeline of the message).
109+
*
110+
* In case of BootNotification, the expected behaviour is to set RegistrationStatus.REJECTED in response, as done
111+
* by {@link CentralSystemService16_Service#bootNotification(BootNotificationRequest, String, OcppProtocol)}.
112+
* Therefore, no exception. This case is tested by {@link OperationalTestSoapOCPP16#testUnregisteredCP()} already.
113+
*
114+
* WS/JSON stations cannot connect at all if they are not registered, as ensured by {@link OcppWebSocketUpgrader}.
115+
*/
116+
@Test(expected = SOAPFaultException.class)
117+
public void testUnregisteredCPWithInterceptor() {
118+
Assert.assertFalse(SteveConfiguration.CONFIG.getOcpp().isAutoRegisterUnknownStations());
119+
120+
CentralSystemService client = getForOcpp16(path);
121+
122+
client.authorize(
123+
new AuthorizeRequest().withIdTag(REGISTERED_OCPP_TAG),
124+
getRandomString());
125+
}
126+
100127
@Test
101128
public void testRegisteredCP() {
102129
CentralSystemService client = getForOcpp16(path);

0 commit comments

Comments
 (0)