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/UiExtensionManager.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
index 5e36357..9a016cf 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
@@ -50,6 +50,8 @@
 import org.onosproject.ui.UiExtensionService;
 import org.onosproject.ui.UiMessageHandlerFactory;
 import org.onosproject.ui.UiPreferencesService;
+import org.onosproject.ui.UiSessionToken;
+import org.onosproject.ui.UiTokenService;
 import org.onosproject.ui.UiTopo2OverlayFactory;
 import org.onosproject.ui.UiTopoMap;
 import org.onosproject.ui.UiTopoMapFactory;
@@ -62,6 +64,8 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.math.BigInteger;
+import java.security.SecureRandom;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -83,11 +87,13 @@
 @Component(immediate = true)
 @Service
 public class UiExtensionManager
-        implements UiExtensionService, UiPreferencesService, SpriteService {
+        implements UiExtensionService, UiPreferencesService, SpriteService,
+        UiTokenService {
 
     private static final ClassLoader CL = UiExtensionManager.class.getClassLoader();
 
     private static final String ONOS_USER_PREFERENCES = "onos-ui-user-preferences";
+    private static final String ONOS_SESSION_TOKENS = "onos-ui-session-tokens";
     private static final String CORE = "core";
     private static final String GUI_ADDED = "guiAdded";
     private static final String GUI_REMOVED = "guiRemoved";
@@ -120,6 +126,12 @@
     private final MapEventListener<String, ObjectNode> prefsListener =
             new InternalPrefsListener();
 
+    // Session tokens
+    private ConsistentMap<UiSessionToken, String> tokensConsistentMap;
+    private Map<UiSessionToken, String> tokens;
+    private final SessionTokenGenerator tokenGen =
+            new SessionTokenGenerator();
+
     private final ObjectMapper mapper = new ObjectMapper();
 
     private final ExecutorService eventHandlingExecutor =
@@ -216,7 +228,7 @@
                      JsonNodeFactory.class, LinkedHashMap.class,
                      TextNode.class, BooleanNode.class,
                      LongNode.class, DoubleNode.class, ShortNode.class,
-                     IntNode.class, NullNode.class);
+                     IntNode.class, NullNode.class, UiSessionToken.class);
 
         prefsConsistentMap = storageService.<String, ObjectNode>consistentMapBuilder()
                 .withName(ONOS_USER_PREFERENCES)
@@ -225,6 +237,14 @@
                 .build();
         prefsConsistentMap.addListener(prefsListener);
         prefs = prefsConsistentMap.asJavaMap();
+
+        tokensConsistentMap = storageService.<UiSessionToken, String>consistentMapBuilder()
+                .withName(ONOS_SESSION_TOKENS)
+                .withSerializer(serializer)
+                .withRelaxedReadConsistency()
+                .build();
+        tokens = tokensConsistentMap.asJavaMap();
+
         register(core);
         log.info("Started");
     }
@@ -325,6 +345,55 @@
         return key.split(SLASH)[IDX_KEY];
     }
 
+
+    // =====================================================================
+    // UiTokenService
+
+    @Override
+    public UiSessionToken issueToken(String username) {
+        UiSessionToken token = new UiSessionToken(tokenGen.nextSessionId());
+        tokens.put(token, username);
+        log.debug("UiSessionToken issued: {}", token);
+        return token;
+    }
+
+    @Override
+    public void revokeToken(UiSessionToken token) {
+        if (token != null) {
+            tokens.remove(token);
+            log.debug("UiSessionToken revoked: {}", token);
+        }
+    }
+
+    @Override
+    public boolean isTokenValid(UiSessionToken token) {
+        return token != null && tokens.containsKey(token);
+    }
+
+    private final class SessionTokenGenerator {
+        private final SecureRandom random = new SecureRandom();
+
+        /*
+            This works by choosing 130 bits from a cryptographically secure
+            random bit generator, and encoding them in base-32.
+
+            128 bits is considered to be cryptographically strong, but each
+            digit in a base 32 number can encode 5 bits, so 128 is rounded up
+            to the next multiple of 5.
+
+            This encoding is compact and efficient, with 5 random bits per
+            character. Compare this to a random UUID, which only has 3.4 bits
+            per character in standard layout, and only 122 random bits in total.
+
+            Note that SecureRandom objects are expensive to initialize, so
+            we'll want to keep it around and re-use it.
+         */
+
+        private String nextSessionId() {
+            return new BigInteger(130, random).toString(32);
+        }
+    }
+
     // Auxiliary listener to preference map events.
     private class InternalPrefsListener
             implements MapEventListener<String, ObjectNode> {
@@ -340,7 +409,7 @@
 
         private ObjectNode jsonPrefs() {
             ObjectNode json = mapper.createObjectNode();
-            prefs.entrySet().forEach(e -> json.set(keyName(e.getKey()), e.getValue()));
+            prefs.forEach((key, value) -> json.set(keyName(key), value));
             return json;
         }
     }