github.com/xmidt-org/webpa-common@v1.11.9/device/metadata.go (about)

     1  package device
     2  
     3  import (
     4  	"sync"
     5  	"sync/atomic"
     6  
     7  	"github.com/segmentio/ksuid"
     8  	"github.com/spf13/cast"
     9  )
    10  
    11  // Reserved metadata keys
    12  const (
    13  	JWTClaimsKey = "jwt-claims"
    14  	SessionIDKey = "session-id"
    15  )
    16  
    17  // Top level JWTClaim keys
    18  const (
    19  	PartnerIDClaimKey = "partner-id"
    20  	TrustClaimKey     = "trust"
    21  )
    22  
    23  // Default values
    24  const (
    25  	UnknownPartner = "unknown"
    26  )
    27  
    28  var reservedMetadataKeys = map[string]bool{
    29  	JWTClaimsKey: true, SessionIDKey: true,
    30  }
    31  
    32  func init() {
    33  	ksuid.SetRand(ksuid.FastRander)
    34  }
    35  
    36  // Metadata contains information such as security credentials
    37  // related to a device. Read operations are optimized with a
    38  // copy-on-write strategy. Client code must further synchronize concurrent
    39  // writers to avoid stale data.
    40  // Metadata uses an atomic.Value internally and thus it should not be copied
    41  // after creation.
    42  type Metadata struct {
    43  	v    atomic.Value
    44  	once sync.Once
    45  }
    46  
    47  // SessionID returns the UUID associated with a device's current connection
    48  // to the cluster if one has been set. The zero value is returned as default.
    49  func (m *Metadata) SessionID() (sessionID string) {
    50  	sessionID, _ = m.loadData()[SessionIDKey].(string)
    51  	return
    52  }
    53  
    54  // SetSessionID sets the UUID associated the device's current connection to the cluster.
    55  // It uses sync.Once to ensure the sessionID is unchanged through the metadata's lifecycle.
    56  func (m *Metadata) SetSessionID(sessionID string) {
    57  	m.once.Do(func() {
    58  		m.copyAndStore(SessionIDKey, sessionID)
    59  	})
    60  }
    61  
    62  // Load returns the value associated with the given key in the metadata map.
    63  // It is not recommended modifying values returned by reference.
    64  func (m *Metadata) Load(key string) interface{} {
    65  	return m.loadData()[key]
    66  }
    67  
    68  // Store updates the key value mapping in the device metadata map.
    69  // A boolean result is given indicating whether the operation was successful.
    70  // Operations will fail for reserved keys.
    71  // To avoid updating keys with stale data/value, client code will need to
    72  // synchronize the entire transaction of reading, copying, modifying and
    73  // writing back the value.
    74  func (m *Metadata) Store(key string, value interface{}) bool {
    75  	if reservedMetadataKeys[key] {
    76  		return false
    77  	}
    78  	m.copyAndStore(key, value)
    79  	return true
    80  }
    81  
    82  // SetClaims updates the claims associated with the device that's
    83  // owner of the metadata.
    84  // To avoid updating the claims with stale data, client code will need to
    85  // synchronize the entire transaction of reading, copying, modifying and
    86  // writing back the value.
    87  func (m *Metadata) SetClaims(claims map[string]interface{}) {
    88  	m.copyAndStore(JWTClaimsKey, deepCopyMap(claims))
    89  }
    90  
    91  // Claims returns the claims attached to a device. The returned map
    92  // should not be modified to avoid any race conditions. To update the claims,
    93  // take a look at the ClaimsCopy() function
    94  func (m *Metadata) Claims() (claims map[string]interface{}) {
    95  	claims, _ = m.loadData()[JWTClaimsKey].(map[string]interface{})
    96  	return
    97  }
    98  
    99  // ClaimsCopy returns a deep copy of the claims. Use this, along with the
   100  // SetClaims() method to update the claims.
   101  func (m *Metadata) ClaimsCopy() map[string]interface{} {
   102  	return deepCopyMap(m.Claims())
   103  }
   104  
   105  // TrustClaim returns the device's trust level claim.
   106  // By Default, a device is untrusted (trust = 0).
   107  func (m *Metadata) TrustClaim() int {
   108  	return cast.ToInt(m.Claims()[TrustClaimKey])
   109  }
   110  
   111  // PartnerIDClaim returns the partner ID claim.
   112  // If no claim is found, the value defaults to "unknown"
   113  func (m *Metadata) PartnerIDClaim() string {
   114  	partnerID, ok := m.Claims()[PartnerIDClaimKey].(string)
   115  	if !ok {
   116  		return UnknownPartner
   117  	}
   118  	return partnerID
   119  }
   120  
   121  func (m *Metadata) loadData() (data map[string]interface{}) {
   122  	data, _ = m.v.Load().(map[string]interface{})
   123  	return
   124  }
   125  
   126  func (m *Metadata) storeData(data map[string]interface{}) {
   127  	m.v.Store(data)
   128  }
   129  
   130  func (m *Metadata) copyAndStore(key string, val interface{}) {
   131  	data := deepCopyMap(m.loadData())
   132  	data[key] = val
   133  	m.storeData(data)
   134  }
   135  
   136  func deepCopyMap(m map[string]interface{}) map[string]interface{} {
   137  	deepCopy := make(map[string]interface{})
   138  	for key, val := range m {
   139  		switch val.(type) {
   140  		case map[interface{}]interface{}:
   141  			val = cast.ToStringMap(val)
   142  			deepCopy[key] = deepCopyMap(val.(map[string]interface{}))
   143  		case map[string]interface{}:
   144  			deepCopy[key] = deepCopyMap(val.(map[string]interface{}))
   145  		default:
   146  			deepCopy[key] = val
   147  		}
   148  
   149  	}
   150  	return deepCopy
   151  }