Skip to content

Commit

Permalink
Docstring
Browse files Browse the repository at this point in the history
  • Loading branch information
konomae committed Aug 21, 2014
1 parent c149c28 commit 6351846
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 49 deletions.
45 changes: 27 additions & 18 deletions lastpass/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,54 @@
# coding: utf-8


# Base class for all errors, should not be raised
class Error(Exception): pass
class Error(Exception):
"""Base class for all errors, should not be raised"""
pass


#
# Generic errors
#

# Something went wrong with the network
class NetworkError(Error): pass
class NetworkError(Error):
"""Something went wrong with the network"""
pass


# Server responded with something we don't understand
class InvalidResponseError(Error): pass
class InvalidResponseError(Error):
"""Server responded with something we don't understand"""
pass


# Server responded with XML we don't understand
class UnknownResponseSchemaError(Error): pass
class UnknownResponseSchemaError(Error):
"""Server responded with XML we don't understand"""
pass


#
# LastPass returned errors
#

# LastPass error: unknown username
class LastPassUnknownUsernameError(Error): pass
class LastPassUnknownUsernameError(Error):
"""LastPass error: unknown username"""
pass


# LastPass error: invalid password
class LastPassInvalidPasswordError(Error): pass
class LastPassInvalidPasswordError(Error):
"""LastPass error: invalid password"""
pass


# LastPass error: missing or incorrect Google Authenticator code
class LastPassIncorrectGoogleAuthenticatorCodeError(Error): pass
class LastPassIncorrectGoogleAuthenticatorCodeError(Error):
"""LastPass error: missing or incorrect Google Authenticator code"""
pass


# LastPass error: missing or incorrect Yubikey password
class LastPassIncorrectYubikeyPasswordError(Error): pass
class LastPassIncorrectYubikeyPasswordError(Error):
"""LastPass error: missing or incorrect Yubikey password"""
pass


# LastPass error we don't know about
class LastPassUnknownError(Error): pass
class LastPassUnknownError(Error):
"""LastPass error we don't know about"""
pass
1 change: 0 additions & 1 deletion lastpass/fetcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from Crypto.Hash import HMAC, SHA256
from Crypto.Protocol.KDF import PBKDF2
import requests
#from lxml import etree
from xml.etree import ElementTree as etree
from . import blob
from .exceptions import (
Expand Down
54 changes: 29 additions & 25 deletions lastpass/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
]


# Splits the blob into chucks grouped by kind.
def extract_chunks(blob):
"""Splits the blob into chucks grouped by kind."""
chunks = []
stream = BytesIO(blob.bytes)
current_pos = stream.tell()
Expand All @@ -40,13 +40,15 @@ def extract_chunks(blob):
return chunks


# Parses an account chunk, decrypts and creates an Account object.
# May return nil when the chunk does not represent an account.
# All secure notes are ACCTs but not all of them strore account
# information.
#
# TODO: Make a test case that covers secure note account
def parse_ACCT(chunk, encryption_key):
"""
Parses an account chunk, decrypts and creates an Account object.
May return nil when the chunk does not represent an account.
All secure notes are ACCTs but not all of them strore account
information.
"""
# TODO: Make a test case that covers secure note account

io = BytesIO(chunk.payload)
id = read_item(io)
name = decode_aes256_plain_auto(read_item(io), encryption_key)
Expand Down Expand Up @@ -134,8 +136,8 @@ def parse_secure_note_server(notes):
return [url, username, password]


# Reads one chunk from a stream and creates a Chunk object with the data read.
def read_chunk(stream):
"""Reads one chunk from a stream and creates a Chunk object with the data read."""
# LastPass blob chunk is made up of 4-byte ID,
# big endian 4-byte size and payload of that size.
#
Expand All @@ -147,8 +149,8 @@ def read_chunk(stream):
return Chunk(read_id(stream), read_payload(stream, read_size(stream)))


# Reads an item from a stream and returns it as a string of bytes.
def read_item(stream):
"""Reads an item from a stream and returns it as a string of bytes."""
# An item in an itemized chunk is made up of the
# big endian size and the payload of that size.
#
Expand All @@ -159,47 +161,47 @@ def read_item(stream):
return read_payload(stream, read_size(stream))


# Skips an item in a stream.
def skip_item(stream, times=1):
"""Skips an item in a stream."""
for i in range(times):
read_item(stream)


# Reads a chunk ID from a stream.
def read_id(stream):
"""Reads a chunk ID from a stream."""
return stream.read(4)


# Reads a chunk or an item ID.
def read_size(stream):
"""Reads a chunk or an item ID."""
return read_uint32(stream)


# Reads a payload of a given size from a stream.
def read_payload(stream, size):
"""Reads a payload of a given size from a stream."""
return stream.read(size)


# Reads an unsigned 32 bit integer from a stream.
def read_uint32(stream):
"""Reads an unsigned 32 bit integer from a stream."""
return struct.unpack('>I', stream.read(4))[0]


# Decodes a hex encoded string into raw bytes.
def decode_hex(data):
"""Decodes a hex encoded string into raw bytes."""
try:
return codecs.decode(data, 'hex_codec')
except binascii.Error:
raise TypeError()


# Decodes a base64 encoded string into raw bytes.
def decode_base64(data):
"""Decodes a base64 encoded string into raw bytes."""
return b64decode(data)


# Guesses AES cipher (EBC or CBD) from the length of the plain data.
def decode_aes256_plain_auto(data, encryption_key):
"""Guesses AES cipher (EBC or CBD) from the length of the plain data."""
assert isinstance(data, bytes)
length = len(data)

Expand All @@ -211,8 +213,8 @@ def decode_aes256_plain_auto(data, encryption_key):
return decode_aes256_ecb_plain(data, encryption_key)


# Guesses AES cipher (EBC or CBD) from the length of the base64 encoded data.
def decode_aes256_base64_auto(data, encryption_key):
"""Guesses AES cipher (EBC or CBD) from the length of the base64 encoded data."""
assert isinstance(data, bytes)
length = len(data)

Expand All @@ -224,21 +226,21 @@ def decode_aes256_base64_auto(data, encryption_key):
return decode_aes256_ecb_base64(data, encryption_key)


# Decrypts AES-256 ECB bytes.
def decode_aes256_ecb_plain(data, encryption_key):
"""Decrypts AES-256 ECB bytes."""
if not data:
return b''
else:
return decode_aes256('ecb', '', data, encryption_key)


# Decrypts base64 encoded AES-256 ECB bytes.
def decode_aes256_ecb_base64(data, encryption_key):
"""Decrypts base64 encoded AES-256 ECB bytes."""
return decode_aes256_ecb_plain(decode_base64(data), encryption_key)


# Decrypts AES-256 CBC bytes.
def decode_aes256_cbc_plain(data, encryption_key):
"""Decrypts AES-256 CBC bytes."""
if not data:
return b''
else:
Expand All @@ -248,8 +250,8 @@ def decode_aes256_cbc_plain(data, encryption_key):
return decode_aes256('cbc', data[1:17], data[17:], encryption_key)


# Decrypts base64 encoded AES-256 CBC bytes.
def decode_aes256_cbc_base64(data, encryption_key):
"""Decrypts base64 encoded AES-256 CBC bytes."""
if not data:
return b''
else:
Expand All @@ -264,10 +266,12 @@ def decode_aes256_cbc_base64(data, encryption_key):
encryption_key)


# Decrypt AES-256 bytes.
# Allowed ciphers are: :ecb, :cbc.
# If for :ecb iv is not used and should be set to "".
def decode_aes256(cipher, iv, data, encryption_key):
"""
Decrypt AES-256 bytes.
Allowed ciphers are: :ecb, :cbc.
If for :ecb iv is not used and should be set to "".
"""
if cipher == 'cbc':
aes_mode = AES.MODE_CBC
elif cipher == 'ecb':
Expand Down
10 changes: 5 additions & 5 deletions lastpass/vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,29 @@


class Vault(object):
# Fetches a blob from the server and creates a vault
@classmethod
def open_remote(cls, username, password, multifactor_password=None):
"""Fetches a blob from the server and creates a vault"""
return cls.open(cls.fetch_blob(username, password, multifactor_password), username, password)

# Creates a vault from a locally stored blob
@classmethod
def open_local(cls, blob_filename, username, password):
"""Creates a vault from a locally stored blob"""
# TODO: read the blob here
pass

# Creates a vault from a blob object
@classmethod
def open(cls, blob, username, password):
"""Creates a vault from a blob object"""
return cls(blob, blob.encryption_key(username, password))

# Just fetches the blob, could be used to store it locally
@classmethod
def fetch_blob(cls, username, password, multifactor_password=None):
"""Just fetches the blob, could be used to store it locally"""
return fetcher.fetch(fetcher.login(username, password, multifactor_password))

# This more of an internal method, use one of the static constructors instead
def __init__(self, blob, encryption_key):
"""This more of an internal method, use one of the static constructors instead"""
self.accounts = []

key = encryption_key
Expand Down

0 comments on commit 6351846

Please sign in to comment.