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;
}
}