Skip to content

Instantly share code, notes, and snippets.

@turtlemonvh
Last active June 19, 2019 14:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save turtlemonvh/229cd5ffaa5b9486e481e233519c4863 to your computer and use it in GitHub Desktop.
Save turtlemonvh/229cd5ffaa5b9486e481e233519c4863 to your computer and use it in GitHub Desktop.
Custom device profile persistor implementations for the Ionic Python SDK
# -*- coding: utf-8 -*-
from __future__ import division, print_function
"""
Install the Ionic python sdk with: `pip install ionicsdk`
"""
import binascii
import calendar
import datetime
import json
import os
import ionicsdk
## Profile helpers
def load_from_dict(d):
"""
Create a device profile from a dictionary
"""
creationtimestampsecs = int(d.get('creationTimestamp', -1))
if creationtimestampsecs <= 0:
# This is a bug in the sdk; 0 results in an exception
# Set to current time in this case
creationtimestampsecs = calendar.timegm(datetime.datetime.utcnow().timetuple())
profargs = dict(
name=d.get('name', d['deviceId']),
deviceid=d['deviceId'],
keyspace=d['deviceId'].split(".")[0],
server=d['server'],
creationtimestampsecs=creationtimestampsecs,
aesCdIdcProfileKey=binascii.unhexlify(d['aesCdIdcKey']),
aesCdEiProfileKey=binascii.unhexlify(d['aesCdEiKey']),
)
return ionicsdk.DeviceProfile(**profargs)
## Custom exceptions
class IonicLoadProfilesNoPersistorException(Exception):
"""persistor must be passed to enable loading profiles
"""
pass
class IonicSaveProfilesNoPersistorException(Exception):
"""persistor must be passed to enable saving profiles
"""
pass
class IonicPersistorBadCaseClassException(Exception):
"""profilepersistor must be an instance of the DeviceProfilePersistorBase class from ionic_sdk_ext
"""
pass
## Customized persistors
class DeviceProfilePersistorBase(object):
"""
A base class that is used in the modified agent to make custom persistors simpler.
"""
def loadprofiles(self, *args, **kwargs):
"""
Should return a list of DeviceProfile objects.
"""
raise NotImplementedError
def saveprofiles(self, profiles, *args, **kwargs):
"""
Should raise an exception on failure.
"""
raise NotImplementedError
class PlainTextPersistor(DeviceProfilePersistorBase):
"""
An example subclass.
Just like the normal plain text persistor except
* it creates the path you ask for on init
* it doesn't include any facility to marking a profile as active
The default path is '~/.ionicsecurity/profiles.pt'
"""
def __init__(self, path=None):
if path is None:
path = os.path.expanduser("~/.ionicsecurity/profiles.pt")
self.path = path
# Create directory if DNE
dirname = os.path.dirname(self.path)
if not os.path.exists(dirname):
os.makedirs(dirname)
# Create file if DNE
if not os.path.exists(self.path):
with open(self.path, "w+") as pp:
json.dump([], pp)
def loadprofiles(self, *args, **kwargs):
loaded_profiles = []
with open(self.path) as pp:
for p in json.load(pp):
loaded_profiles.append(load_from_dict(p))
return loaded_profiles
def saveprofiles(self, profiles, *args, **kwargs):
with open(self.path, "w+") as pp:
json.dump(profiles, pp)
## Agent subclass which accepts custom persistors derived from DeviceProfilePersistorBase
class Agent(ionicsdk.Agent):
"""
A version of the Ionic Agent with all persistor functionality changed to use subclasses of the persistor base class defined in this module.
We have to subclass all persistor related methods because the code calls methods on ctypes directly to
work with profiles, making overriding functionality in pure python challenging.
"""
def __init__(self, agentconfig = None, profilepersistor = None, loadprofiles = True):
# Call base constructor with no persistor and with loadprofiles set to false
ionicsdk.Agent.__init__(self, agentconfig, None, False)
if profilepersistor:
if not isinstance(profilepersistor, DeviceProfilePersistorBase):
# Assert that the persistor is subclassed from this type
raise IonicPersistorBadCaseClassException()
# Set it as a property to make it accessible
# The user can also set this property manually after creating the agent
self.persistor = profilepersistor
# Load the profiles if requested and set an active profile
if loadprofiles:
if not self.persistor:
raise IonicLoadProfilesNoPersistorException()
self.loadprofiles(self.persistor)
profiles = self.getallprofiles()
if len(profiles) > 0:
self.setactiveprofile(profiles[0])
def loadprofiles(self, persistor=None, *args, **kwargs):
"""
Load profiles from the custom persistor.
Note that this method will not do anything to set a profile as active.
"""
persistor = persistor or self.persistor
if persistor is None:
raise IonicLoadProfilesNoPersistorException()
if not isinstance(persistor, DeviceProfilePersistorBase):
raise IonicPersistorBadCaseClassException()
for deviceprofile in persistor.loadprofiles(*args, **kwargs):
self.addprofile(deviceprofile)
def saveprofiles(self, persistor=None, *args, **kwargs):
"""
Save profiles into the custom persistor.
"""
persistor = persistor or self.persistor
if persistor is None:
raise IonicSaveProfilesNoPersistorException()
if not isinstance(persistor, DeviceProfilePersistorBase):
raise IonicPersistorBadCaseClassException()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment