SEBA-75: AAA app exposes authentication state machine events.
Change-Id: I9a1fbc0c28579f6b347322d6f7fc58635c1a9c8a
diff --git a/src/main/java/org/opencord/aaa/AaaManager.java b/src/main/java/org/opencord/aaa/AaaManager.java
index c9fddc0..120f14f 100755
--- a/src/main/java/org/opencord/aaa/AaaManager.java
+++ b/src/main/java/org/opencord/aaa/AaaManager.java
@@ -20,6 +20,7 @@
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
import org.onlab.packet.DeserializationException;
import org.onlab.packet.EAP;
import org.onlab.packet.EAPOL;
@@ -30,6 +31,7 @@
import org.onlab.packet.RADIUSAttribute;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
+import org.onosproject.event.AbstractListenerManager;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
@@ -63,20 +65,19 @@
/**
* AAA application for ONOS.
*/
+@Service
@Component(immediate = true)
-public class
-AaaManager {
+public class AaaManager
+ extends AbstractListenerManager<AuthenticationEvent, AuthenticationEventListener>
+ implements AuthenticationService {
+
private static final String APP_NAME = "org.opencord.aaa";
- // for verbose output
private final Logger log = getLogger(getClass());
- // a list of our dependencies :
- // to register with ONOS as an application - described next
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
- // to receive Packet-in events that we'll respond to
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetService;
@@ -98,7 +99,7 @@
protected InetAddress nasIpAddress;
// self MAC address
- protected static String nasMacAddress;
+ protected String nasMacAddress;
// Parsed RADIUS server addresses
protected InetAddress radiusIpAddress;
@@ -109,9 +110,6 @@
// RADIUS server secret
protected String radiusSecret;
- // NAS Identifier
- protected String nasId;
-
// bindings
protected CustomizationInfo customInfo;
@@ -131,7 +129,7 @@
// "socket" or "packet_out"
private String radiusConnectionType;
- // Object for the spcific type of communication with the RADIUS
+ // Object for the specific type of communication with the RADIUS
// server, socket based or packet_out based
RadiusCommunicator impl = null;
@@ -152,6 +150,8 @@
// Listener for config changes
private final InternalConfigListener cfgListener = new InternalConfigListener();
+ private StateMachineDelegate delegate = new InternalStateMachineDelegate();
+
/**
* Builds an EAPOL packet based on the given parameters.
*
@@ -189,6 +189,7 @@
@Activate
public void activate() {
appId = coreService.registerApplication(APP_NAME);
+ eventDispatcher.addSink(AuthenticationEvent.class, listenerRegistry);
netCfgService.addListener(cfgListener);
netCfgService.registerConfigFactory(factory);
customInfo = new CustomizationInfo(subsService, deviceService);
@@ -202,6 +203,7 @@
StateMachine.initializeMaps();
+ StateMachine.setDelegate(delegate);
impl.initializeLocalState(newCfg);
@@ -216,13 +218,13 @@
@Deactivate
public void deactivate() {
impl.withdrawIntercepts();
- // de-register and null our handler
packetService.removeProcessor(processor);
- processor = null;
netCfgService.removeListener(cfgListener);
+ StateMachine.unsetDelegate(delegate);
StateMachine.destroyMaps();
impl.deactivate();
deviceService.removeListener(deviceListener);
+ eventDispatcher.removeSink(AuthenticationEvent.class);
log.info("Stopped");
}
@@ -441,8 +443,8 @@
switch (eapol.getEapolType()) {
case EAPOL.EAPOL_START:
log.debug("EAP packet: EAPOL_START");
- stateMachine.start();
stateMachine.setSupplicantConnectpoint(inPacket.receivedFrom());
+ stateMachine.start();
//send an EAP Request/Identify to the supplicant
EAP eapPayload = new EAP(EAP.REQUEST, stateMachine.identifier(), EAP.ATTR_IDENTITY, null);
@@ -542,6 +544,19 @@
}
/**
+ * Delegate allowing the StateMachine to notify us of events.
+ */
+ private class InternalStateMachineDelegate implements StateMachineDelegate {
+
+ @Override
+ public void notify(AuthenticationEvent authenticationEvent) {
+ log.info("Auth event {} for {}",
+ authenticationEvent.type(), authenticationEvent.subject());
+ post(authenticationEvent);
+ }
+ }
+
+ /**
* Configuration Listener, handles change in configuration.
*/
private class InternalConfigListener implements NetworkConfigListener {
diff --git a/src/main/java/org/opencord/aaa/AaaResetDeviceCommand.java b/src/main/java/org/opencord/aaa/AaaResetDeviceCommand.java
index f5b4e43..6655fec 100644
--- a/src/main/java/org/opencord/aaa/AaaResetDeviceCommand.java
+++ b/src/main/java/org/opencord/aaa/AaaResetDeviceCommand.java
@@ -20,6 +20,9 @@
import org.onosproject.cli.AbstractShellCommand;
import org.onlab.packet.MacAddress;
+/**
+ * Removes a AAA state machine.
+ */
@Command(scope = "onos", name = "aaa-reset-device",
description = "Resets the authentication state machine for a given device")
public class AaaResetDeviceCommand extends AbstractShellCommand {
diff --git a/src/main/java/org/opencord/aaa/AuthenticationEvent.java b/src/main/java/org/opencord/aaa/AuthenticationEvent.java
new file mode 100644
index 0000000..da02ec2
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/AuthenticationEvent.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opencord.aaa;
+
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.net.ConnectPoint;
+
+/**
+ * Event indicating the authentication state of a port has changed.
+ */
+public class AuthenticationEvent extends
+ AbstractEvent<AuthenticationEvent.Type, ConnectPoint> {
+
+ /**
+ * Authentication event type.
+ */
+ public enum Type {
+ /**
+ * Supplicant has started authentication on a port.
+ */
+ STARTED,
+
+ /**
+ * Supplicant has requested authentication on a port.
+ */
+ REQUESTED,
+
+ /**
+ * Authentication request was approved.
+ */
+ APPROVED,
+
+ /**
+ * Authentication request was denied.
+ */
+ DENIED
+ }
+
+ /**
+ * Creates a new authentication event.
+ *
+ * @param type event type
+ * @param connectPoint port
+ */
+ public AuthenticationEvent(Type type, ConnectPoint connectPoint) {
+ super(type, connectPoint);
+ }
+
+}
diff --git a/src/main/java/org/opencord/aaa/AuthenticationEventListener.java b/src/main/java/org/opencord/aaa/AuthenticationEventListener.java
new file mode 100644
index 0000000..87d2e4a
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/AuthenticationEventListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opencord.aaa;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Listener for authentication events.
+ */
+public interface AuthenticationEventListener extends
+ EventListener<AuthenticationEvent> {
+}
diff --git a/src/main/java/org/opencord/aaa/AuthenticationService.java b/src/main/java/org/opencord/aaa/AuthenticationService.java
new file mode 100644
index 0000000..82aabbd
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/AuthenticationService.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opencord.aaa;
+
+import org.onosproject.event.ListenerService;
+
+/**
+ * Service for interacting with authentication module.
+ */
+public interface AuthenticationService extends
+ ListenerService<AuthenticationEvent, AuthenticationEventListener> {
+}
diff --git a/src/main/java/org/opencord/aaa/StateMachine.java b/src/main/java/org/opencord/aaa/StateMachine.java
index e2a24bc..d888c98 100644
--- a/src/main/java/org/opencord/aaa/StateMachine.java
+++ b/src/main/java/org/opencord/aaa/StateMachine.java
@@ -109,6 +109,8 @@
private static Map<String, StateMachine> sessionIdMap;
private static Map<Integer, StateMachine> identifierMap;
+ private static StateMachineDelegate delegate;
+
public static void initializeMaps() {
sessionIdMap = Maps.newConcurrentMap();
identifierMap = Maps.newConcurrentMap();
@@ -120,6 +122,16 @@
identifierMap = null;
}
+ public static void setDelegate(StateMachineDelegate delagate) {
+ StateMachine.delegate = delagate;
+ }
+
+ public static void unsetDelegate(StateMachineDelegate delegate) {
+ if (StateMachine.delegate == delegate) {
+ StateMachine.delegate = null;
+ }
+ }
+
public static Map<String, StateMachine> sessionIdMap() {
return sessionIdMap;
}
@@ -366,6 +378,10 @@
*/
public void start() throws StateMachineException {
states[currentState].start();
+
+ delegate.notify(new AuthenticationEvent(
+ AuthenticationEvent.Type.STARTED, supplicantConnectpoint));
+
//move to the next state
next(TRANSITION_START);
identifier = this.identifier();
@@ -379,6 +395,10 @@
*/
public void requestAccess() throws StateMachineException {
states[currentState].requestAccess();
+
+ delegate.notify(new AuthenticationEvent(
+ AuthenticationEvent.Type.REQUESTED, supplicantConnectpoint));
+
//move to the next state
next(TRANSITION_REQUEST_ACCESS);
}
@@ -394,7 +414,8 @@
//move to the next state
next(TRANSITION_AUTHORIZE_ACCESS);
- // TODO send state machine change event
+ delegate.notify(new AuthenticationEvent(
+ AuthenticationEvent.Type.APPROVED, supplicantConnectpoint));
// Clear mapping
deleteStateMachineMapping(this);
@@ -410,6 +431,10 @@
states[currentState].radiusDenied();
//move to the next state
next(TRANSITION_DENY_ACCESS);
+
+ delegate.notify(new AuthenticationEvent(
+ AuthenticationEvent.Type.DENIED, supplicantConnectpoint));
+
// Clear mappings
deleteStateMachineMapping(this);
}
diff --git a/src/main/java/org/opencord/aaa/StateMachineDelegate.java b/src/main/java/org/opencord/aaa/StateMachineDelegate.java
new file mode 100644
index 0000000..6546d74
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/StateMachineDelegate.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opencord.aaa;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Delegate for authentication state machine.
+ */
+public interface StateMachineDelegate extends StoreDelegate<AuthenticationEvent> {
+}
diff --git a/src/test/java/org/opencord/aaa/AaaManagerTest.java b/src/test/java/org/opencord/aaa/AaaManagerTest.java
index 456c7b6..d615aee 100644
--- a/src/test/java/org/opencord/aaa/AaaManagerTest.java
+++ b/src/test/java/org/opencord/aaa/AaaManagerTest.java
@@ -19,6 +19,7 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.onlab.junit.TestUtils;
import org.onlab.packet.BasePacket;
import org.onlab.packet.DeserializationException;
import org.onlab.packet.EAP;
@@ -27,13 +28,19 @@
import org.onlab.packet.RADIUS;
import org.onlab.packet.RADIUSAttribute;
import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.event.DefaultEventSinkRegistry;
+import org.onosproject.event.Event;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.event.EventSink;
import org.onosproject.net.config.Config;
import org.onosproject.net.config.NetworkConfigRegistryAdapter;
import org.onosproject.net.packet.InboundPacket;
+import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import static com.google.common.base.Preconditions.checkState;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
@@ -82,6 +89,27 @@
}
}
+ public static class TestEventDispatcher extends DefaultEventSinkRegistry
+ implements EventDeliveryService {
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public synchronized void post(Event event) {
+ EventSink sink = getSink(event.getClass());
+ checkState(sink != null, "No sink for event %s", event);
+ sink.process(event);
+ }
+
+ @Override
+ public void setDispatchTimeLimit(long millis) {
+ }
+
+ @Override
+ public long getDispatchTimeLimit() {
+ return 0;
+ }
+ }
+
/**
* Constructs an Ethernet packet containing a RADIUS challenge
* packet.
@@ -111,6 +139,20 @@
return radius;
}
+ public static void injectEventDispatcher(Object manager, EventDeliveryService svc) {
+ Class mc = manager.getClass();
+ for (Field f : mc.getSuperclass().getDeclaredFields()) {
+ if (f.getType().equals(EventDeliveryService.class)) {
+ try {
+ TestUtils.setField(manager, f.getName(), svc);
+ } catch (TestUtils.TestUtilsException e) {
+ throw new IllegalArgumentException("Unable to inject reference", e);
+ }
+ break;
+ }
+ }
+ }
+
/**
* Sets up the services required by the AAA application.
*/
@@ -122,6 +164,7 @@
aaaManager.packetService = new MockPacketService();
aaaManager.deviceService = new TestDeviceService();
aaaManager.subsService = new MockSubService();
+ TestUtils.setField(aaaManager, "eventDispatcher", new TestEventDispatcher());
aaaManager.activate();
}
diff --git a/src/test/java/org/opencord/aaa/StateMachineTest.java b/src/test/java/org/opencord/aaa/StateMachineTest.java
index f12a95f..5b0a62d 100644
--- a/src/test/java/org/opencord/aaa/StateMachineTest.java
+++ b/src/test/java/org/opencord/aaa/StateMachineTest.java
@@ -32,6 +32,7 @@
public void setUp() {
System.out.println("Set Up.");
StateMachine.initializeMaps();
+ StateMachine.setDelegate(e -> { });
stateMachine = new StateMachine("session0");
}