OS-1 : insecure UI websocket.
- notes on authentication of UI web socket connection.
- new classes: UiSessionToken, UiTokenService.
- UiExtensionManager now implements UiTokenService.
- UiWebSocket now expects an authentication event from the client
- websocket.js now sends authentication event as first event
- (fix websocket Jasmine test)

Change-Id: I4303c67f57fc618e911be244091f00bcc2823c91
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
index 8bb6244..6c4fc1e 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
@@ -29,6 +29,8 @@
 import org.onosproject.ui.UiExtensionService;
 import org.onosproject.ui.UiMessageHandler;
 import org.onosproject.ui.UiMessageHandlerFactory;
+import org.onosproject.ui.UiSessionToken;
+import org.onosproject.ui.UiTokenService;
 import org.onosproject.ui.UiTopo2OverlayFactory;
 import org.onosproject.ui.UiTopoLayoutService;
 import org.onosproject.ui.UiTopoOverlayFactory;
@@ -57,6 +59,9 @@
     private static final String EVENT = "event";
     private static final String PAYLOAD = "payload";
     private static final String UNKNOWN = "unknown";
+    private static final String AUTHENTICATION = "authentication";
+    private static final String TOKEN = "token";
+    private static final String ERROR = "error";
 
     private static final String ID = "id";
     private static final String IP = "ip";
@@ -87,6 +92,9 @@
     private TopoOverlayCache overlayCache;
     private Topo2OverlayCache overlay2Cache;
 
+    private UiSessionToken sessionToken;
+
+
     /**
      * Creates a new web-socket for serving data to the Web UI.
      *
@@ -102,8 +110,8 @@
         UiTopoLayoutService layoutService = directory.get(UiTopoLayoutService.class);
 
         sharedModel.injectJsonifier(t2json);
-
         topoSession = new UiTopoSession(this, t2json, sharedModel, layoutService);
+        sessionToken = null;
     }
 
     @Override
@@ -192,6 +200,11 @@
 
     @Override
     public synchronized void onClose(int closeCode, String message) {
+        tokenService().revokeToken(sessionToken);
+        log.info("Session token revoked");
+
+        sessionToken = null;
+
         topoSession.destroy();
         destroyHandlersAndOverlays();
         log.info("GUI client disconnected [close-code={}, message={}]",
@@ -210,13 +223,20 @@
         try {
             ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
             String type = message.path(EVENT).asText(UNKNOWN);
-            UiMessageHandler handler = handlers.get(type);
-            if (handler != null) {
-                log.debug("RX message: {}", message);
-                handler.process(message);
+
+            if (sessionToken == null) {
+                authenticate(type, message);
+
             } else {
-                log.warn("No GUI message handler for type {}", type);
+                UiMessageHandler handler = handlers.get(type);
+                if (handler != null) {
+                    log.debug("RX message: {}", message);
+                    handler.process(message);
+                } else {
+                    log.warn("No GUI message handler for type {}", type);
+                }
             }
+
         } catch (Exception e) {
             log.warn("Unable to parse GUI message {} due to {}", data, e);
             log.debug("Boom!!!", e);
@@ -277,6 +297,31 @@
         log.debug("#handlers = {}, #overlays = {}", handlers.size(), overlayCache.size());
     }
 
+    private void authenticate(String type, ObjectNode message) {
+        if (!AUTHENTICATION.equals(type)) {
+            log.warn("Non-Authenticated Web Socket: {}", message);
+            return;
+        }
+
+        String tokstr = message.path(PAYLOAD).path(TOKEN).asText(UNKNOWN);
+        UiSessionToken token = new UiSessionToken(tokstr);
+
+        if (tokenService().isTokenValid(token)) {
+            sessionToken = token;
+            log.info("Session token authenticated");
+            log.debug("WebSocket authenticated: {}", message);
+        } else {
+            log.warn("Invalid Authentication Token: {}", message);
+            sendMessage(ERROR, notAuthorized(token));
+        }
+    }
+
+    private ObjectNode notAuthorized(UiSessionToken token) {
+        return mapper.createObjectNode()
+                .put("message", "invalid authentication token")
+                .put("badToken", token.toString());
+    }
+
     private void registerOverlays(UiExtension ext) {
         UiTopoOverlayFactory overlayFactory = ext.topoOverlayFactory();
         if (overlayFactory != null) {
@@ -350,4 +395,11 @@
         sendMessage(BOOTSTRAP, payload);
     }
 
+    private UiTokenService tokenService() {
+        UiTokenService service = directory.get(UiTokenService.class);
+        if (service == null) {
+            log.error("Unable to reference UiTokenService");
+        }
+        return service;
+    }
 }