Use Extended Attributes (XATTRs) for Access Grants

      +

      How to set access grants using extended attributes (xattrs).
      Here we introduce the concept of XATTRS for access grants and their role in assuring secure access control within Sync Gateway.

      Related Topics: Concepts | How-to | Sync Function | Use XATTRs for Access Grants

      Introduction

      Access grant information such as Channels and Roles can be derived or specified as a property within the document body. In this case, the document content itself is used to govern access and routing.

      Alternatively, a more secure option is to store this information in user XATTRs for specifying channels and roles.

      Why use XATTRS

      XATTRs can be used to hold data used for document routing and access control [1]. When retrieved by the Sync Function, this data can be used to drive access grants. This approach has a few benefits:

      • It provide an added level of security, users can no longer identify the channels and users a document is available to by reading its contents, because the information is in metadata that is inaccessible to them

      • Separation of concerns. By separating access grant metadata from document contents, changes to access grants will not create a new document revision that is subsequently pushed to a client

      Sync Gateway exposes a single user-definable XATTR for this purpose. Learn how to configure it in Configuration and how to use it in Setting and Use XATTRs in a Sync Function.

      Configuration

      Name the XATTR (see: user_xattr_key) to be used for channel routing by defining it using the Admin REST API’s Database Configuration — see: Example 1.

      The actual value of this XATTR can be anything that enables the Sync Function to make an appropriate access grant. Its data type can be string, array, object — any valid JSON that meets the required use case.

      Example 1. Define the User Extended Attribute Key

      This example uses the Admin REST API to specify the required XATTR name as channelXattr on the database hotels.

      • CURL

      • HTTP

      curl -X POST 'http://localhost:4985/hotels/_config' \
      --header 'Accept: application/json' \
      --header 'Content-Type: application/json' \
      --data-raw '{
          "user_xattr_key": "channelXattr" (1)
          }
      }'
      POST /hotels/_config HTTP/1.1
      Host: http://localhost:4985
      Accept: application/json
      Content-Type: application/json
      Content-Length: 999
      
      {
        “user_xattr_key”: “channelXattr” (1)
      }
      1 Here channelXattr is set as the name of the XATTR designated to hold channel routing information.

      Setting

      You can set and maintain the value of the XATTR using a Couchbase Server SDK API. You cannot set it using the Sync Gateway REST API.

      For an example of setting the value of the XATTR using the C# SDK, see Example 2, this can be easily translated to any of the available SDK languages. See Example 3 for an example of the metadata model.

      Example 2. Set XATTR using Couchbase Server SDK
      using System;
      using System.Threading.Tasks;
      using Couchbase;
      using Couchbase.KeyValue; (1)
      
      
      namespace examples
      {
      
          class Program
          {
              static async Task Main(string[] args)
              {
                  // Set scope - cluster, bucket and collection
                  var cluster =
                          await Cluster.ConnectAsync(
                                          "couchbase://localhost",
                                          "Administrator",
                                          "password");
      
      
      
                  var bucket = await cluster.BucketAsync("travel-sample");
                  var collection = bucket.DefaultCollection();
      
                  // Set required  user_xattr_key name and value
                  var our_user_xattr_key_name = "channelXattr"; (2)
                  String[] channelXattrValue =
                      {"channel1","channel3", "useradmin" }; (3)
      
                  var ourDocumentType = "hotel";
                  var documentKey = "";
      
                  // Find our documents and get their ids
                  var queryResult =
                     await cluster.QueryAsync<dynamic>(
                         "select meta().id from `travel-sample`.`_default`.`_default` h where h.type = $1",
                              new Couchbase.Query.QueryOptions().Parameter(ourDocumentType)); (4)
                  await foreach (var row in queryResult)
                  {
                      documentKey = row.id;
                      Console.WriteLine("Working with document id: {0} ",
                                          documentKey);
      
                      // Check if the document has an existing
                      // user_xattr_key and update or insert new value
                      var result =
                          await collection.LookupInAsync(
                                  documentKey,
                                  specs => specs.Exists(
                                      path: our_user_xattr_key_name,
                                      isXattr: true)
                                  ); (5)
      
                      if (result.Exists(0))
                      {
                          // Update xattr for retrieved Id
                          await collection.MutateInAsync(
                                  documentKey,
                                  specs => specs.Upsert(
                                      path: our_user_xattr_key_name, (6)
                                      value: channelXattrValue, (7)
                                      isXattr: true)); (8)
      
                          Console.Write("Updated Existing user_xattr_key: {0} to this value: {1}\n",
                              our_user_xattr_key_name,
                              string.Join(", ", channelXattrValue));
      
                      }
                      else
                      {
                          // Insert xattr for retrieved id
                          await collection.MutateInAsync(
                                  documentKey,
                                  specs => specs.Insert(
                                      path: our_user_xattr_key_name, (9)
                                      value: channelXattrValue, (10)
                                      isXattr: true)); (11)
      
                          Console.Write("Inserted New user_xattr_key: {0} with value {1}\n",
                              our_user_xattr_key_name,
                              string.Join(", ", channelXattrValue));
      
                      }
      
                  }
                  Console.WriteLine("Completed Changes\n");
              }
          }
      }
      1 This is required to make the MutateInSpec class available, providing access to sub-documents, of which metadata is a special class
      2 This string’s value is what we want this document’s XATTR to be called
      3 This array contains the channels we want to include as the XATTR value
      4 Here we get all documents that we want to set the XATTR on (type = 'hotel' in this instance)
      5 Check if the XATTR has been defined yet
      6 Update the XATTR — specify the item to update
      7 Update the XATTR — set the required value
      8 Update the XATTR — specify the item is an XATTR
      9 Insert the XATTR — specify the item to add (channelXattr)
      10 Insert the XATTR — set the required value using channelXattrValue
      11 Insert the XATTR — specify the item is an XATTR

      Running the code produces the following output:

      Working with document id: 1000
      Updated Existing user_xattr_key:
        channelXattr to this value: channel1, channel3, useradmin
      
      Working with document id: 1001
      Inserted New user_xattr_key:
        channelXattr with this value: channel1, channel3, useradmin
      
      Completed Changes
      Example 3. Metadata on Couchbase Server document
      {
        "meta": { (1)
          "id": "1000",
          "rev": "7-1680c88cbce700000000000002000006",
          "expiration": 0,
          "flags": 33554438,
          "type": "json"
        },
        "xattrs": { (2)
          "channelXattr": [ (3)
            "channel1",
            "channel3",
            "useradmin"
          ]
        }
      }
      1 This is the Fixed (or System) metadata
      2 This is the User metadata, where you can define extended attributes
      3 Here channelXattr is the name of the designated xattr holding the channel routing information to be passed to the Sync Function. You will set the value of the xattr using the SDK API when the document is created and-or updated.

      For more on Couchbase Server metadata and extended attributes — see Couchbase Server topics: Metadata | Extended Attributes

      Use XATTRs in a Sync Function

      The designated XATTR is exposed to the Sync Function as an additional argument meta.xattrs.<xattr name>

      Example 4. Sync Function Arguments
      function (doc, oldDoc, meta) { (1)
      
        if (meta.xattrs.channelXattr === undefined) (2)
          {
            console.log("no user_xattr_key defined")
            channel(null)
          } else {
            channel(meta.xattrs.channelXattr) (3)
      
      
      
          }
      
        // Further processing as required ../
      1 The meta parameter exposes the user defined user_xattr_key if defined. The item takes the name configured for the database
      2 Access the meta parameter object to check an xattr exists on this document
      3 Use the content of the xattr to define the channels setting for this document

      See: Sync Function topic for more information.