Skip to content

Commit

Permalink
Add device management methods
Browse files Browse the repository at this point in the history
  • Loading branch information
jm-mailosaur committed May 12, 2022
1 parent d781a68 commit 36db6e7
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 9 deletions.
28 changes: 20 additions & 8 deletions mailosaur/mailosaur_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
from .operations.analysis_operations import AnalysisOperations
from .operations.files_operations import FilesOperations
from .operations.usage_operations import UsageOperations
from .operations.devices_operations import DevicesOperations
from .models.mailosaur_exception import MailosaurException


class MailosaurClient(object):
""" Main class to access Mailosaur.com api. """

Expand All @@ -30,20 +32,30 @@ def __init__(self, api_key, base_url="https://mailosaur.com/"):
if base_url is None:
base_url = "https://mailosaur.com/"

self.servers = ServersOperations(session, base_url, self.handle_http_error)
self.messages = MessagesOperations(session, base_url, self.handle_http_error)
self.analysis = AnalysisOperations(session, base_url, self.handle_http_error)
self.servers = ServersOperations(
session, base_url, self.handle_http_error)
self.messages = MessagesOperations(
session, base_url, self.handle_http_error)
self.analysis = AnalysisOperations(
session, base_url, self.handle_http_error)
self.files = FilesOperations(session, base_url, self.handle_http_error)
self.usage = UsageOperations(session, base_url, self.handle_http_error)
self.devices = DevicesOperations(
session, base_url, self.handle_http_error)

def handle_http_error(self, response):
if response.status_code == 400:
raise MailosaurException("Request had one or more invalid parameters.", "invalid_request", response.status_code, response.text)
raise MailosaurException("Request had one or more invalid parameters.",
"invalid_request", response.status_code, response.text)
elif response.status_code == 401:
raise MailosaurException("Authentication failed, check your API key.", "authentication_error", response.status_code, response.text)
raise MailosaurException("Authentication failed, check your API key.",
"authentication_error", response.status_code, response.text)
elif response.status_code == 403:
raise MailosaurException("Insufficient permission to perform that task.", "permission_error", response.status_code, response.text)
raise MailosaurException("Insufficient permission to perform that task.",
"permission_error", response.status_code, response.text)
elif response.status_code == 404:
raise MailosaurException("Request did not find any matching resources.", "invalid_request", response.status_code, response.text)
raise MailosaurException("Request did not find any matching resources.",
"invalid_request", response.status_code, response.text)
else:
raise MailosaurException("An API error occurred, see httpResponse for further information.", "api_error", response.status_code, response.text)
raise MailosaurException("An API error occurred, see httpResponse for further information.",
"api_error", response.status_code, response.text)
10 changes: 9 additions & 1 deletion mailosaur/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
from .usage_account_limit import UsageAccountLimit
from .usage_transaction_list_result import UsageTransactionListResult
from .usage_transaction import UsageTransaction
from .device import Device
from .device_list_result import DeviceListResult
from .device_create_options import DeviceCreateOptions
from .otp_result import OtpResult

__all__ = [
'SpamAssassinRule',
Expand Down Expand Up @@ -51,5 +55,9 @@
'UsageAccountLimits',
'UsageAccountLimit',
'UsageTransactionListResult',
'UsageTransaction'
'UsageTransaction',
'Device',
'DeviceListResult',
'DeviceCreateOptions',
'OtpResult'
]
15 changes: 15 additions & 0 deletions mailosaur/models/device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Device(object):
"""Device.
:param id: Unique identifier for the device.
:type id: str
:param name: The name of the device.
:type name: str
"""

def __init__(self, data=None):
if data is None:
data = {}

self.id = data.get('id', None)
self.name = data.get('name', None)
21 changes: 21 additions & 0 deletions mailosaur/models/device_create_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class DeviceCreateOptions(object):
"""DeviceCreateOptions.
:param name: A name used to identify the device.
:type name: str
:param shared_secret: The base32-encoded shared secret for this device.
:type shared_secret: str
"""

def __init__(self, data=None):
if data is None:
data = {}

self.name = data.get('name', None)
self.shared_secret = data.get('shared_secret', None)

def to_json(self):
return {
'name': self.name,
'sharedSecret': self.shared_secret
}
12 changes: 12 additions & 0 deletions mailosaur/models/device_list_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class DeviceListResult(object):
"""The result of a device listing request.
:param items: The individual devices forming the result.
:type items: list[~mailosaur.models.Device]
"""

def __init__(self, data=None):
if data is None:
data = {}

self.items = data.get('items', None)
18 changes: 18 additions & 0 deletions mailosaur/models/otp_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import dateutil.parser


class OtpResult(object):
"""OtpResult.
:param code: The current one-time password.
:type code: str
:param expires: The expiry date/time of the current one-time password.
:type expires: datetime
"""

def __init__(self, data=None):
if data is None:
data = {}

self.code = data.get('code', None)
self.expires = dateutil.parser.parse(data.get('expires', None))
2 changes: 2 additions & 0 deletions mailosaur/operations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
from mailosaur.operations.files_operations import FilesOperations
from mailosaur.operations.messages_operations import MessagesOperations
from mailosaur.operations.servers_operations import ServersOperations
from mailosaur.operations.devices_operations import DevicesOperations

__all__ = [
'AnalysisOperations',
'FilesOperations',
'MessagesOperations',
'ServersOperations',
'DevicesOperations',
]
125 changes: 125 additions & 0 deletions mailosaur/operations/devices_operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import os
import random
import string

from mailosaur.models.otp_result import OtpResult
from ..models import DeviceListResult
from ..models import Device
from ..models import OtpResult
from ..models import MailosaurException


class DevicesOperations(object):
"""DevicesOperations operations.
"""

def __init__(self, session, base_url, handle_http_error):
self.session = session
self.base_url = base_url
self.handle_http_error = handle_http_error

def generate_email_address(self, server):
host = os.getenv('MAILOSAUR_SMTP_HOST', 'mailosaur.net')
randomString = ''.join(random.choice(
string.ascii_uppercase + string.digits) for _ in range(10))
return "%s@%s.%s" % (randomString, server, host)

def list(self):
"""List all devices.
Returns a list of your virtual security devices.
:return: DeviceListResult
:rtype: ~mailosaur.models.DeviceListResult
:raises:
:class:`MailosaurException<mailosaur.models.MailosaurException>`
"""
url = "%sapi/devices" % (self.base_url)
response = self.session.get(url)

if response.status_code not in [200]:
self.handle_http_error(response)
return

data = response.json()

return DeviceListResult(data)

def create(self, device_create_options):
"""Create a device.
Creates a new virtual security device and returns it.
:param device_create_options:
:type device_create_options: ~mailosaur.models.DeviceCreateOptions
:return: Device
:rtype: ~mailosaur.models.Device
:raises:
:class:`MailosaurException<mailosaur.models.MailosaurException>`
"""
url = "%sapi/devices" % (self.base_url)
response = self.session.post(url, json=device_create_options.to_json())

if response.status_code not in [200]:
self.handle_http_error(response)
return

data = response.json()

return Device(data)

def otp(self, query):
"""Retrieves the current one-time password for a saved device, or given base32-encoded shared secret.
Retrieves the detail for a single server. Simply supply the unique
identifier for the required server.
:param query: Either the unique identifier of the device, or a base32-encoded shared secret.
:type query: str
:return: OtpResult
:rtype: ~mailosaur.models.OtpResult
:raises:
:class:`MailosaurException<mailosaur.models.MailosaurException>`
"""
if "-" in query:
url = "%sapi/devices/%s/otp" % (self.base_url, query)
response = self.session.get(url)

if response.status_code not in [200]:
self.handle_http_error(response)
return

data = response.json()

return OtpResult(data)

url = "%sapi/devices/otp" % (self.base_url)
response = self.session.post(url, json={'sharedSecret': query})

if response.status_code not in [200]:
self.handle_http_error(response)
return

data = response.json()

return OtpResult(data)

def delete(
self, id):
"""Delete a device.
Permanently delete a virtual security device. This operation cannot be undone.
:param id: The identifier of the device to be deleted.
:type id: str
:return: None
:rtype: None
:raises:
:class:`MailosaurException<mailosaur.models.MailosaurException>`
"""
url = "%sapi/devices/%s" % (self.base_url, id)
response = self.session.delete(url)

if response.status_code not in [204]:
self.handle_http_error(response)
return
47 changes: 47 additions & 0 deletions tests/devices_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import os
import numbers
from unittest import TestCase
from mailosaur import MailosaurClient
from mailosaur.models import DeviceCreateOptions, MailosaurException


class DevicesTest(TestCase):
@classmethod
def setUpClass(self):
api_key = os.getenv('MAILOSAUR_API_KEY')
base_url = os.getenv('MAILOSAUR_BASE_URL')

if api_key is None:
raise Exception(
"Missing necessary environment variables - refer to README.md")

self.client = MailosaurClient(api_key, base_url)

def test_crud(self):
device_name = "My test"
shared_secret = "ONSWG4TFOQYTEMY="

# Create a new device
options = DeviceCreateOptions()
options.name = device_name
options.shared_secret = shared_secret

created_device = self.client.devices.create(options)
self.assertIsNotNone(created_device.id)
self.assertEqual(device_name, created_device.name)

# Retrieve an otp via device ID
otp_result = self.client.devices.otp(created_device.id)
self.assertEqual(6, len(otp_result.code))

list_result = self.client.devices.list()
self.assertEqual(1, len(list_result.items))
self.client.devices.delete(created_device.id)
list_result = self.client.devices.list()
self.assertEqual(0, len(list_result.items))

def test_otp_via_shared_secret(self):
shared_secret = "ONSWG4TFOQYTEMY="

otp_result = self.client.devices.otp(shared_secret)
self.assertEqual(6, len(otp_result.code))

0 comments on commit 36db6e7

Please sign in to comment.