Javascript implementation of Union of Blessings

MultiPart: 1/3
Change-Id: Ifd68cadf17ac4a9d7e34076d503b1e48721acf8e
diff --git a/src/gen-vdl/v.io/x/ref/services/wspr/internal/app/index.js b/src/gen-vdl/v.io/x/ref/services/wspr/internal/app/index.js
index 072765b..0d99bb0 100644
--- a/src/gen-vdl/v.io/x/ref/services/wspr/internal/app/index.js
+++ b/src/gen-vdl/v.io/x/ref/services/wspr/internal/app/index.js
@@ -22,7 +22,6 @@
 
 // Types:
 var _type1 = new vdl.Type();
-var _type10 = new vdl.Type();
 var _type2 = new vdl.Type();
 var _type3 = new vdl.Type();
 var _type4 = new vdl.Type();
@@ -41,9 +40,6 @@
 _type1.kind = vdl.kind.LIST;
 _type1.name = "";
 _type1.elem = _typeRpcCallOption;
-_type10.kind = vdl.kind.LIST;
-_type10.name = "";
-_type10.elem = new security.WireBlessings()._type;
 _type2.kind = vdl.kind.LIST;
 _type2.name = "";
 _type2.elem = new security.BlessingPattern()._type;
@@ -90,7 +86,6 @@
 _typeRpcServerOption.name = "v.io/x/ref/services/wspr/internal/app.RpcServerOption";
 _typeRpcServerOption.fields = [{name: "IsLeaf", type: vdl.types.BOOL}, {name: "ServesMountTable", type: vdl.types.BOOL}];
 _type1.freeze();
-_type10.freeze();
 _type2.freeze();
 _type3.freeze();
 _type4.freeze();
@@ -214,11 +209,6 @@
 Controller.prototype.signature = function(ctx, serverCall, name) {
   throw new Error('Method Signature not implemented');
 };
-    
-      
-Controller.prototype.unionOfBlessings = function(ctx, serverCall, toJoin) {
-  throw new Error('Method UnionOfBlessings not implemented');
-};
      
 
     
@@ -564,27 +554,6 @@
     outStream: null,
     tags: []
   },
-    
-      
-    {
-    name: 'UnionOfBlessings',
-    doc: "// UnionOfBlessings returns a Blessings object that carries the union of the provided blessings.",
-    inArgs: [{
-      name: 'toJoin',
-      doc: "",
-      type: _type10
-    },
-    ],
-    outArgs: [{
-      name: '',
-      doc: "",
-      type: new principal.BlessingsId()._type
-    },
-    ],
-    inStream: null,
-    outStream: null,
-    tags: []
-  },
      
   ]
 };
diff --git a/src/security/blessings-util.js b/src/security/blessings-util.js
index 9f6ce4d..d876ac1 100644
--- a/src/security/blessings-util.js
+++ b/src/security/blessings-util.js
@@ -2,7 +2,16 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-var runtimeFromContext = require('../../src/runtime/runtime-from-context');
+var Blessings = require('../../src/security/blessings');
+var makeError = require('../../src/verror/make-errors');
+var actions = require('../../src/verror/actions');
+
+var InvalidUnionError = makeError('v.io/v23/security.errInvalidUnion',
+  actions.NO_RETRY, {
+    'en':
+     '{1:}{2:} cannot create union of blessings bound to different public keys',
+  }, []);
+
 
 /**
  * @fileoverview Blessings related utilities that don't belong on the
@@ -27,21 +36,58 @@
 * provided blessings.
 * @param {module:vanadium.context.Context} ctx The context.
 * @param {...string} blessingsList The blessings to join
-* @param {module:vanadium.security~blessingsCb} cb An optional
-* callback that will return the blessing.
-* @return {Promise<module:vanadium.security~Blessings>} A promise that will
-* be resolved with the blessing.
+* @return {module:vanadium.security~Blessings} A blessing object consisting
+* of the union of the input.
 * @memberof module:vanadium.security
 */
-function unionOfBlessings(ctx /*, blessingsA, blessingsB, ..., cb*/) {
-   var args = Array.prototype.slice.call(arguments);
-   args.shift(); // remove ctx
-   var cb;
-   if (args.length > 0 && typeof args[args.length - 1] === 'function') {
-     cb = args.pop();
-   }
-   var blessingsList = args;
+function unionOfBlessings(ctx /*, blessingsA, blessingsB, ...*/) {
+  var blessingsList = Array.prototype.slice.call(arguments, 1);
 
-   var runtime = runtimeFromContext(ctx);
-   return runtime._controller.unionOfBlessings(ctx, blessingsList, cb);
+  blessingsList = blessingsList.filter(function(blessings) {
+    return !!blessings;
+  });
+
+  switch(blessingsList.length) {
+    case 0:
+      return null;
+    case 1:
+      return blessingsList[0];
+  }
+
+  var firstKey = blessingsList[0].publicKey;
+  var chains = [];
+  for (var i = 0; i < blessingsList.length; i++) {
+    var blessings = blessingsList[i];
+    if (JSON.stringify(blessings.publicKey) !== JSON.stringify(firstKey)) {
+      throw new InvalidUnionError();
+    }
+    chains = chains.concat(blessings.chains);
+  }
+
+  // Sort for prettier and more consistent output.
+  chains = chains.sort(chainSorter);
+
+  return new Blessings({
+    publicKey: firstKey,
+    certificateChains: chains
+  });
+}
+
+// Provide some stability by sorting the chain list.
+// The chains are first ordered by length, followed by the the result
+// of comparing the first differing extension.
+function chainSorter(a, b) {
+  if (a.length !== b.length) {
+    return a.length > b.length;
+  }
+
+  for (var i = 0; i < a.length; i++) {
+    var aext = a[i].extension;
+    var bext = b[i].extension;
+    if (aext !== bext) {
+      return aext > bext;
+    }
+  }
+
+  return false;
 }
diff --git a/test/integration/test-blessings-util.js b/test/integration/test-blessings-util.js
index 9a98e0c..48d1a5a 100644
--- a/test/integration/test-blessings-util.js
+++ b/test/integration/test-blessings-util.js
@@ -7,7 +7,21 @@
 var config = require('./default-config');
 var security = vanadium.security;
 
-test('Test union of blessings (promise case)', function(t) {
+function validateUnionedBlessings(t, blessings) {
+  t.equal(blessings.chains.length, 2, 'Should have 2 chains');
+  t.equal(blessings.chains[0].length, 1, 'First chain has 1 cert');
+  t.equal(blessings.chains[1].length, 1, 'Second chain has 1 cert');
+  t.equal(blessings.chains[0][0].extension, 'blessedname1',
+    'Get first extension on first chain');
+  t.equal(blessings.chains[1][0].extension, 'blessedname2',
+    'Get second extension on second chain');
+  t.deepEqual(blessings.chains[0][0].publicKey, blessings.publicKey,
+    'First public key matches blessing key');
+  t.deepEqual(blessings.chains[1][0].publicKey, blessings.publicKey,
+    'Second public key matches blessing key');
+}
+
+test('Test union of blessings', function(t) {
   vanadium.init(config, function(err, runtime) {
     if (err) {
       t.end(err);
@@ -22,10 +36,7 @@
       });
     })
     .then(function(unionedBlessings) {
-      t.ok(unionedBlessings.toString().indexOf('blessedname1') >= 0,
-          'first blessing in union');
-      t.ok(unionedBlessings.toString().indexOf('blessedname2') >= 0,
-          'second blessing in union');
+      validateUnionedBlessings(t, unionedBlessings);
       runtime.close(t.end);
     }).catch(function(err) {
       runtime.close();
@@ -34,40 +45,31 @@
   });
 });
 
-test('Test union of blessings (callback case)', function(t) {
+test('Test union of blessings with differing public keys', function(t) {
   vanadium.init(config, function(err, runtime) {
     if (err) {
       t.end(err);
     }
 
-    runtime.principal.blessSelf(runtime.getContext(), 'blessedname1',
-      function(err, blessings1) {
-      if (err) {
-        t.error(err);
-        runtime.close(t.end);
-        return;
-      }
-      runtime.principal.blessSelf(runtime.getContext(), 'blessedname2',
-        function(err, blessings2) {
-        if (err) {
-          t.error(err);
-          runtime.close(t.end);
-          return;
-        }
-        security.unionOfBlessings(runtime.getContext(), blessings1,
-          blessings2, function(err, unionedBlessings) {
-          if (err) {
-            t.error(err);
-            runtime.close(t.end);
-            return;
-          }
-          t.ok(unionedBlessings.toString().indexOf('blessedname1') >= 0,
-              'first blessing in union');
-          t.ok(unionedBlessings.toString().indexOf('blessedname2') >= 0,
-              'second blessing in union');
-            runtime.close(t.end);
-        });
+    runtime.principal.blessSelf(runtime.getContext(), 'blessedname1')
+    .then(function(blessings1) {
+      return runtime.principal.blessSelf(runtime.getContext(), 'blessedname2')
+      .then(function(blessings2) {
+        // modify the public key so it doesn't match
+        blessings2.chains[0][0].publicKey[0] -= 1;
+        return security.unionOfBlessings(
+          runtime.getContext(), blessings1, blessings2);
       });
+    })
+    .then(function(unionedBlessings) {
+      runtime.close();
+      t.end('Should have failed due to public keys not matching');
+    }).catch(function(err) {
+      t.ok(err instanceof Error, 'Got error');
+      t.ok(err.toString().indexOf('cannot create union of blessings ' +
+        'bound to different public keys') !== -1,
+        'Should get message about keys differing');
+      runtime.close(t.end);
     });
   });
 });