Bring code up to PEP8 standard

master
Tanner Collin 7 years ago
parent c7309c4136
commit cf73e5ab21
  1. 42
      api.py
  2. 91
      crypt.py
  3. 48
      itemmanager.py
  4. 81
      sn_fuse.py
  5. 38
      standardnotes_fs.py

@ -1,4 +1,4 @@
import json, requests, time import requests
from crypt import EncryptionHelper from crypt import EncryptionHelper
@ -18,47 +18,51 @@ class RESTAPI:
url = self.base_url + route url = self.base_url + route
return requests.post(url, json=data, headers=self.headers).json() return requests.post(url, json=data, headers=self.headers).json()
def addHeader(self, header): def add_header(self, header):
self.headers.update(header) self.headers.update(header)
class StandardNotesAPI: class StandardNotesAPI:
encryption_helper = EncryptionHelper() encryption_helper = EncryptionHelper()
sync_token = None sync_token = None
def getAuthParamsForEmail(self): def get_auth_params_for_email(self):
return self.api.get('/auth/params', dict(email=self.username)) return self.api.get('/auth/params', dict(email=self.username))
def genKeys(self, password): def gen_keys(self, password):
pw_info = self.getAuthParamsForEmail() pw_info = self.get_auth_params_for_email()
if 'error' in pw_info: if 'error' in pw_info:
raise SNAPIException(pw_info['error']['message']) raise SNAPIException(pw_info['error']['message'])
return self.encryption_helper.pure_generatePasswordAndKey(password, pw_info['pw_salt'], pw_info['pw_cost']) return self.encryption_helper.pure_generate_password_and_key(
password, pw_info['pw_salt'], pw_info['pw_cost'])
def signIn(self, keys): def sign_in(self, keys):
self.keys = keys self.keys = keys
res = self.api.post('/auth/sign_in', dict(email=self.username, password=self.keys['pw'])) res = self.api.post('/auth/sign_in', dict(email=self.username,
password=self.keys['pw']))
if 'error' in res: if 'error' in res:
raise SNAPIException(res['error']['message']) raise SNAPIException(res['error']['message'])
self.api.addHeader(dict(Authorization='Bearer ' + res['token'])) self.api.add_header(dict(Authorization='Bearer ' + res['token']))
def sync(self, dirty_items): def sync(self, dirty_items):
items = self.handleDirtyItems(dirty_items) items = self.handle_dirty_items(dirty_items)
response = self.api.post('/items/sync', dict(sync_token=self.sync_token, items=items)) response = self.api.post('/items/sync', dict(sync_token=self.sync_token,
items=items))
self.sync_token = response['sync_token'] self.sync_token = response['sync_token']
return self.handleResponseItems(response) return self.handle_response_items(response)
def handleDirtyItems(self, dirty_items): def handle_dirty_items(self, dirty_items):
items = self.encryption_helper.encryptDirtyItems(dirty_items, self.keys) items = self.encryption_helper.encrypt_dirty_items(
dirty_items, self.keys)
return items return items
def handleResponseItems(self, response): def handle_response_items(self, response):
response_items = self.encryption_helper.decryptResponseItems(response['retrieved_items'], self.keys) response_items = self.encryption_helper.decrypt_response_items(
saved_items = self.encryption_helper.decryptResponseItems(response['saved_items'], self.keys) response['retrieved_items'], self.keys)
saved_items = self.encryption_helper.decrypt_response_items(
response['saved_items'], self.keys)
return dict(response_items=response_items, saved_items=saved_items) return dict(response_items=response_items, saved_items=saved_items)
def __init__(self, base_url, username): def __init__(self, base_url, username):

@ -1,14 +1,28 @@
import hashlib, hmac, json from base64 import b64decode, b64encode
from base64 import b64encode, b64decode
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from Crypto.Cipher import AES
from Crypto.Random import random
from copy import deepcopy from copy import deepcopy
import hashlib
import hmac
import json
import sys import sys
from Crypto.Cipher import AES
from Crypto.Random import random
BITS_PER_HEX_DIGIT = 4
PASS_KEY_LEN = 96
AES_KEY_LEN = 256
AES_BLK_SIZE = 16
AES_STR_KEY_LEN = AES_KEY_LEN // BITS_PER_HEX_DIGIT
AES_IV_LEN = 128
AES_STR_IV_LEN = AES_IV_LEN // BITS_PER_HEX_DIGIT
class EncryptionHelper: class EncryptionHelper:
def pure_generatePasswordAndKey(self, password, pw_salt, pw_cost): def pure_generate_password_and_key(self, password, pw_salt, pw_cost):
output = hashlib.pbkdf2_hmac('sha512', password.encode(), pw_salt.encode(), pw_cost, dklen=96) output = hashlib.pbkdf2_hmac(
'sha512', password.encode(), pw_salt.encode(), pw_cost,
dklen=PASS_KEY_LEN)
output = hexlify(output).decode() output = hexlify(output).decode()
output_length = len(output) output_length = len(output)
@ -19,36 +33,39 @@ class EncryptionHelper:
return dict(pw=pw, mk=mk, ak=ak) return dict(pw=pw, mk=mk, ak=ak)
def encryptDirtyItems(self, dirty_items, keys): def encrypt_dirty_items(self, dirty_items, keys):
return [self.pure_encryptItem(item, keys) for item in dirty_items] return [self.pure_encrypt_item(item, keys) for item in dirty_items]
def decryptResponseItems(self, response_items, keys): def decrypt_response_items(self, response_items, keys):
return [self.pure_decryptItem(item, keys) for item in response_items] return [self.pure_decrypt_item(item, keys) for item in response_items]
def pure_encryptItem(self, item, keys): def pure_encrypt_item(self, item, keys):
uuid = item['uuid'] uuid = item['uuid']
content = json.dumps(item['content']) content = json.dumps(item['content'])
item_key = hex(random.getrandbits(512)) # all this is to follow the Standard Notes spec
item_key = item_key[2:].rjust(128, '0') # remove '0x', pad to 128 item_key = hex(random.getrandbits(AES_KEY_LEN * 2))
item_key_length = len(item_key) # remove '0x', pad with 0's, then split in half
item_ek = item_key[:item_key_length//2] item_key = item_key[2:].rjust(AES_STR_KEY_LEN * 2, '0')
item_ak = item_key[item_key_length//2:] item_ek = item_key[:AES_STR_KEY_LEN]
item_ak = item_key[AES_STR_KEY_LEN:]
enc_item = deepcopy(item) enc_item = deepcopy(item)
enc_item['content'] = self.pure_encryptString002(content, item_ek, item_ak, uuid) enc_item['content'] = self.pure_encrypt_string_002(
enc_item['enc_item_key'] = self.pure_encryptString002(item_key, keys['mk'], keys['ak'], uuid) content, item_ek, item_ak, uuid)
enc_item['enc_item_key'] = self.pure_encrypt_string_002(
item_key, keys['mk'], keys['ak'], uuid)
return enc_item return enc_item
def pure_decryptItem(self, item, keys): def pure_decrypt_item(self, item, keys):
if item['deleted']:
return item
uuid = item['uuid'] uuid = item['uuid']
content = item['content'] content = item['content']
enc_item_key = item['enc_item_key'] enc_item_key = item['enc_item_key']
if not content:
return item
if content[:3] == '001': if content[:3] == '001':
print('Old encryption protocol detected. This version is not ' print('Old encryption protocol detected. This version is not '
'supported by standardnotes-fs. Please resync all of ' 'supported by standardnotes-fs. Please resync all of '
@ -56,12 +73,14 @@ class EncryptionHelper:
'https://standardnotes.org/help/resync') 'https://standardnotes.org/help/resync')
sys.exit(1) sys.exit(1)
elif content[:3] == '002': elif content[:3] == '002':
item_key = self.pure_decryptString002(enc_item_key, keys['mk'], keys['ak'], uuid) item_key = self.pure_decrypt_string_002(
enc_item_key, keys['mk'], keys['ak'], uuid)
item_key_length = len(item_key) item_key_length = len(item_key)
item_ek = item_key[:item_key_length//2] item_ek = item_key[:item_key_length//2]
item_ak = item_key[item_key_length//2:] item_ak = item_key[item_key_length//2:]
dec_content = self.pure_decryptString002(content, item_ek, item_ak, uuid) dec_content = self.pure_decrypt_string_002(
content, item_ek, item_ak, uuid)
else: else:
print('Invalid protocol version. This could indicate tampering or ' print('Invalid protocol version. This could indicate tampering or '
'that something is wrong with the server. Exiting.') 'that something is wrong with the server. Exiting.')
@ -72,31 +91,30 @@ class EncryptionHelper:
return dec_item return dec_item
def pure_encryptString002(self, string_to_encrypt, encryption_key, auth_key, uuid): def pure_encrypt_string_002(self, string_to_encrypt, encryption_key,
IV = hex(random.getrandbits(128)) auth_key, uuid):
IV = IV[2:].rjust(32, '0') # remove '0x', pad to 32 IV = hex(random.getrandbits(AES_IV_LEN))
IV = IV[2:].rjust(AES_STR_IV_LEN, '0') # remove '0x', pad with 0's
cipher = AES.new(unhexlify(encryption_key), AES.MODE_CBC, unhexlify(IV)) cipher = AES.new(unhexlify(encryption_key), AES.MODE_CBC, unhexlify(IV))
pt = string_to_encrypt.encode() pt = string_to_encrypt.encode()
pad = 16 - len(pt) % 16 pad = AES_BLK_SIZE - len(pt) % AES_BLK_SIZE
padded_pt = pt + pad * bytes([pad]) padded_pt = pt + pad * bytes([pad])
ciphertext = b64encode(cipher.encrypt(padded_pt)).decode() ciphertext = b64encode(cipher.encrypt(padded_pt)).decode()
string_to_auth = ':'.join(['002', uuid, IV, ciphertext]) string_to_auth = ':'.join(['002', uuid, IV, ciphertext])
auth_hash = hmac.new(unhexlify(auth_key), string_to_auth.encode(), 'sha256').digest() auth_hash = hmac.new(
unhexlify(auth_key), string_to_auth.encode(), 'sha256').digest()
auth_hash = hexlify(auth_hash).decode() auth_hash = hexlify(auth_hash).decode()
result = ':'.join(['002', auth_hash, uuid, IV, ciphertext]) result = ':'.join(['002', auth_hash, uuid, IV, ciphertext])
return result return result
def pure_decryptString002(self, string_to_decrypt, encryption_key, auth_key, uuid): def pure_decrypt_string_002(self, string_to_decrypt, encryption_key,
auth_key, uuid):
components = string_to_decrypt.split(':') components = string_to_decrypt.split(':')
version = components[0] version, auth_hash, local_uuid, IV, ciphertext = components
auth_hash = components[1]
local_uuid = components[2]
IV = components[3]
ciphertext = components[4]
if local_uuid != uuid: if local_uuid != uuid:
print('UUID does not match. This could indicate tampering or ' print('UUID does not match. This could indicate tampering or '
@ -104,7 +122,8 @@ class EncryptionHelper:
sys.exit(1) sys.exit(1)
string_to_auth = ':'.join([version, uuid, IV, ciphertext]) string_to_auth = ':'.join([version, uuid, IV, ciphertext])
local_auth_hash = hmac.new(unhexlify(auth_key), string_to_auth.encode(), 'sha256').digest() local_auth_hash = hmac.new(
unhexlify(auth_key), string_to_auth.encode(), 'sha256').digest()
local_auth_hash = hexlify(local_auth_hash).decode() local_auth_hash = hexlify(local_auth_hash).decode()
if local_auth_hash != auth_hash: if local_auth_hash != auth_hash:

@ -1,10 +1,11 @@
from api import StandardNotesAPI
from uuid import uuid1 from uuid import uuid1
from api import StandardNotesAPI
class ItemManager: class ItemManager:
items = {} items = {}
def mapResponseItemsToLocalItems(self, response_items, metadata_only=False): def map_items(self, response_items, metadata_only=False):
DATA_KEYS = ['content', 'enc_item_key', 'auth_hash'] DATA_KEYS = ['content', 'enc_item_key', 'auth_hash']
for response_item in response_items: for response_item in response_items:
@ -25,7 +26,7 @@ class ItemManager:
continue continue
self.items[uuid][key] = value self.items[uuid][key] = value
def syncItems(self): def sync_items(self):
dirty_items = [item for uuid, item in self.items.items() if item['dirty']] dirty_items = [item for uuid, item in self.items.items() if item['dirty']]
# remove keys (note: this removes them from self.items as well) # remove keys (note: this removes them from self.items as well)
@ -34,12 +35,13 @@ class ItemManager:
item.pop('updated_at', None) item.pop('updated_at', None)
response = self.sn_api.sync(dirty_items) response = self.sn_api.sync(dirty_items)
self.mapResponseItemsToLocalItems(response['response_items']) self.map_items(response['response_items'])
self.mapResponseItemsToLocalItems(response['saved_items'], metadata_only=True) self.map_items(response['saved_items'], metadata_only=True)
def getNotes(self): def get_notes(self):
notes = {} notes = {}
sorted_items = sorted(self.items.items(), key=lambda x: x[1]['created_at']) sorted_items = sorted(
self.items.items(), key=lambda x: x[1]['created_at'])
for uuid, item in sorted_items: for uuid, item in sorted_items:
if item['content_type'] == 'Note': if item['content_type'] == 'Note':
@ -55,49 +57,45 @@ class ItemManager:
# remove title duplicates by adding a number to the end # remove title duplicates by adding a number to the end
count = 0 count = 0
while True: while True:
title = note['title'] + ('' if not count else ' ' + str(count + 1)) title = note['title'] + ('' if not count else
' ' + str(count + 1))
if title in notes: if title in notes:
count += 1 count += 1
else: else:
break break
notes[title] = dict(text=text, notes[title] = dict(
created=item['created_at'], text=text, created=item['created_at'],
modified=item.get('updated_at', item['created_at']), modified=item.get('updated_at', item['created_at']),
uuid=item['uuid']) uuid=item['uuid'])
return notes return notes
def touchNote(self, uuid): def touch_note(self, uuid):
item = self.items[uuid] item = self.items[uuid]
item['dirty'] = True item['dirty'] = True
def writeNote(self, uuid, text): def write_note(self, uuid, text):
item = self.items[uuid] item = self.items[uuid]
item['content']['text'] = text.decode() # convert back to string item['content']['text'] = text.decode() # convert back to string
item['dirty'] = True item['dirty'] = True
def createNote(self, name, time): def create_note(self, name, time):
uuid = str(uuid1()) uuid = str(uuid1())
content = dict(title=name, text='', references=[]) content = dict(title=name, text='', references=[])
self.items[uuid] = dict(content_type='Note', self.items[uuid] = dict(content_type='Note', dirty=True, auth_hash=None,
dirty=True, uuid=uuid, created_at=time, updated_at=time,
auth_hash=None, enc_item_key='', content=content)
uuid=uuid,
created_at=time, def rename_note(self, uuid, new_note_name):
updated_at=time,
enc_item_key='',
content=content)
def renameNote(self, uuid, new_note_name):
item = self.items[uuid] item = self.items[uuid]
item['content']['title'] = new_note_name item['content']['title'] = new_note_name
item['dirty'] = True item['dirty'] = True
def deleteNote(self, uuid): def delete_note(self, uuid):
item = self.items[uuid] item = self.items[uuid]
item['deleted'] = True item['deleted'] = True
item['dirty'] = True item['dirty'] = True
def __init__(self, sn_api): def __init__(self, sn_api):
self.sn_api = sn_api self.sn_api = sn_api
self.syncItems() self.sync_items()

@ -1,69 +1,68 @@
from datetime import datetime
import errno import errno
import iso8601
import logging import logging
import os import os
import time
from stat import S_IFDIR, S_IFREG
from sys import argv, exit
from datetime import datetime
from pathlib import PurePath from pathlib import PurePath
from threading import Thread, Event from stat import S_IFDIR, S_IFREG
from threading import Event, Thread
from time import sleep
from fuse import FUSE, FuseOSError, Operations, LoggingMixIn from fuse import FuseOSError, LoggingMixIn, Operations
from itemmanager import ItemManager import iso8601
from requests.exceptions import ConnectionError from requests.exceptions import ConnectionError
from itemmanager import ItemManager
class StandardNotesFUSE(LoggingMixIn, Operations): class StandardNotesFUSE(LoggingMixIn, Operations):
def __init__(self, sn_api, sync_sec, path='.'): def __init__(self, sn_api, sync_sec, path='.'):
self.item_manager = ItemManager(sn_api) self.item_manager = ItemManager(sn_api)
self.notes = self.item_manager.getNotes() self.notes = self.item_manager.get_notes()
self.uid = os.getuid() self.uid = os.getuid()
self.gid = os.getgid() self.gid = os.getgid()
now = datetime.now().timestamp() now = datetime.now().timestamp()
self.dir_stat = dict(st_mode=(S_IFDIR | 0o755), st_ctime=now, self.dir_stat = dict(st_mode=(S_IFDIR | 0o755), st_ctime=now,
st_mtime=now, st_atime=now, st_nlink=2, st_mtime=now, st_atime=now, st_nlink=2,
st_uid=self.uid, st_gid=self.gid) st_uid=self.uid, st_gid=self.gid)
self.note_stat = dict(st_mode=(S_IFREG | 0o644), st_ctime=now, self.note_stat = dict(st_mode=(S_IFREG | 0o644), st_ctime=now,
st_mtime=now, st_atime=now, st_nlink=1, st_mtime=now, st_atime=now, st_nlink=1,
st_uid=self.uid, st_gid=self.gid) st_uid=self.uid, st_gid=self.gid)
self.sync_sec = sync_sec self.sync_sec = sync_sec
self.run_sync = Event() self.run_sync = Event()
self.stop_sync = Event() self.stop_sync = Event()
self.sync_thread = Thread(target=self._syncThread) self.sync_thread = Thread(target=self._sync_thread)
def init(self, path): def init(self, path):
self.sync_thread.start() self.sync_thread.start()
def destroy(self, path): def destroy(self, path):
self._syncNow() self._sync_now()
logging.info('Stopping sync thread.') logging.info('Stopping sync thread.')
self.stop_sync.set() self.stop_sync.set()
self.sync_thread.join() self.sync_thread.join()
return 0 return 0
def _syncThread(self): def _sync_thread(self):
while not self.stop_sync.is_set(): while not self.stop_sync.is_set():
self.run_sync.clear() self.run_sync.clear()
manually_synced = self.run_sync.wait(timeout=self.sync_sec) manually_synced = self.run_sync.wait(timeout=self.sync_sec)
if not manually_synced: logging.info('Auto-syncing items...') if not manually_synced: logging.info('Auto-syncing items...')
time.sleep(0.1) # fixes race condition of quick create() then write() sleep(0.1) # fixes race condition of quick create() then write()
try: try:
self.item_manager.syncItems() self.item_manager.sync_items()
except ConnectionError: except ConnectionError:
logging.error('Unable to connect to sync server. Retrying...') logging.error('Unable to connect to sync server. Retrying...')
def _syncNow(self): def _sync_now(self):
self.run_sync.set() self.run_sync.set()
def _pathToNote(self, path): def _path_to_note(self, path):
pp = PurePath(path) pp = PurePath(path)
note_name = pp.parts[1] note_name = pp.parts[1]
self.notes = self.item_manager.getNotes() self.notes = self.item_manager.get_notes()
note = self.notes[note_name] note = self.notes[note_name]
return note, note['uuid'] return note, note['uuid']
@ -72,7 +71,7 @@ class StandardNotesFUSE(LoggingMixIn, Operations):
return self.dir_stat return self.dir_stat
try: try:
note, uuid = self._pathToNote(path) note, uuid = self._path_to_note(path)
st = self.note_stat st = self.note_stat
st['st_size'] = len(note['text']) st['st_size'] = len(note['text'])
st['st_ctime'] = iso8601.parse_date(note['created']).timestamp() st['st_ctime'] = iso8601.parse_date(note['created']).timestamp()
@ -89,27 +88,27 @@ class StandardNotesFUSE(LoggingMixIn, Operations):
return dirents return dirents
def read(self, path, size, offset, fh): def read(self, path, size, offset, fh):
note, uuid = self._pathToNote(path) note, uuid = self._path_to_note(path)
return note['text'][offset : offset + size] return note['text'][offset : offset + size]
def truncate(self, path, length, fh=None): def truncate(self, path, length, fh=None):
note, uuid = self._pathToNote(path) note, uuid = self._path_to_note(path)
text = note['text'][:length] text = note['text'][:length]
self.item_manager.writeNote(uuid, text) self.item_manager.write_note(uuid, text)
self._syncNow() self._sync_now()
return 0 return 0
def write(self, path, data, offset, fh): def write(self, path, data, offset, fh):
note, uuid = self._pathToNote(path) note, uuid = self._path_to_note(path)
text = note['text'][:offset] + data text = note['text'][:offset] + data
try: try:
self.item_manager.writeNote(uuid, text) self.item_manager.write_note(uuid, text)
except UnicodeError: except UnicodeError:
logging.error('Unable to parse non-unicode data.') logging.error('Unable to parse non-unicode data.')
raise FuseOSError(errno.EIO) raise FuseOSError(errno.EIO)
self._syncNow() self._sync_now()
return len(data) return len(data)
def create(self, path, mode): def create(self, path, mode):
@ -123,14 +122,14 @@ class StandardNotesFUSE(LoggingMixIn, Operations):
now = datetime.utcnow().isoformat()[:-3] + 'Z' # hack now = datetime.utcnow().isoformat()[:-3] + 'Z' # hack
self.item_manager.createNote(note_name, now) self.item_manager.create_note(note_name, now)
self._syncNow() self._sync_now()
return 0 return 0
def unlink(self, path): def unlink(self, path):
note, uuid = self._pathToNote(path) note, uuid = self._path_to_note(path)
self.item_manager.deleteNote(uuid) self.item_manager.delete_note(uuid)
self._syncNow() self._sync_now()
return 0 return 0
def mkdir(self, path, mode): def mkdir(self, path, mode):
@ -138,17 +137,17 @@ class StandardNotesFUSE(LoggingMixIn, Operations):
raise FuseOSError(errno.EPERM) raise FuseOSError(errno.EPERM)
def utimens(self, path, times=None): def utimens(self, path, times=None):
note, uuid = self._pathToNote(path) note, uuid = self._path_to_note(path)
self.item_manager.touchNote(uuid) self.item_manager.touch_note(uuid)
self._syncNow() self._sync_now()
return 0 return 0
def rename(self, old, new): def rename(self, old, new):
note, uuid = self._pathToNote(old) note, uuid = self._path_to_note(old)
new_path_parts = new.split('/') new_path_parts = new.split('/')
new_note_name = new_path_parts[1] new_note_name = new_path_parts[1]
self.item_manager.renameNote(uuid, new_note_name) self.item_manager.rename_note(uuid, new_note_name)
self._syncNow() self._sync_now()
return 0 return 0
def chmod(self, path, mode): def chmod(self, path, mode):

@ -1,18 +1,18 @@
import appdirs
import argparse import argparse
from configparser import ConfigParser
from getpass import getpass
import logging import logging
import os import os
import pathlib import pathlib
import sys import sys
from configparser import ConfigParser import appdirs
from getpass import getpass
from api import StandardNotesAPI, SNAPIException
from sn_fuse import StandardNotesFUSE
from fuse import FUSE from fuse import FUSE
from requests.exceptions import ConnectionError, MissingSchema from requests.exceptions import ConnectionError, MissingSchema
from api import SNAPIException, StandardNotesAPI
from sn_fuse import StandardNotesFUSE
OFFICIAL_SERVER_URL = 'https://sync.standardnotes.org' OFFICIAL_SERVER_URL = 'https://sync.standardnotes.org'
DEFAULT_SYNC_SEC = 30 DEFAULT_SYNC_SEC = 30
MINIMUM_SYNC_SEC = 5 MINIMUM_SYNC_SEC = 5
@ -38,7 +38,8 @@ def parse_options():
parser.add_argument('--foreground', action='store_true', parser.add_argument('--foreground', action='store_true',
help='run standardnotes-fs in the foreground') help='run standardnotes-fs in the foreground')
parser.add_argument('--sync-sec', type=int, default=DEFAULT_SYNC_SEC, parser.add_argument('--sync-sec', type=int, default=DEFAULT_SYNC_SEC,
help='how many seconds between each sync. Default: 10') help='how many seconds between each sync. Default: '
''+str(DEFAULT_SYNC_SEC))
parser.add_argument('--sync-url', parser.add_argument('--sync-url',
help='URL of Standard File sync server. Defaults to:\n' help='URL of Standard File sync server. Defaults to:\n'
''+OFFICIAL_SERVER_URL) ''+OFFICIAL_SERVER_URL)
@ -123,25 +124,25 @@ def main():
log_msg = 'Using sync URL "%s".' log_msg = 'Using sync URL "%s".'
logging.info(log_msg % sync_url) logging.info(log_msg % sync_url)
if config.has_option('user', 'username') \ if (config.has_option('user', 'username')
and config.has_section('keys') \ and config.has_section('keys')
and not args.username \ and not args.username
and not args.password: and not args.password):
username = config.get('user', 'username') username = config.get('user', 'username')
keys = dict(config.items('keys')) keys = dict(config.items('keys'))
else: else:
username = args.username if args.username else \ username = (args.username if args.username else
input('Please enter your Standard Notes username: ') input('Please enter your Standard Notes username: '))
password = args.password if args.password else \ password = (args.password if args.password else
getpass('Please enter your password (hidden): ') getpass('Please enter your password (hidden): '))
# log the user in # log the user in
try: try:
sn_api = StandardNotesAPI(sync_url, username) sn_api = StandardNotesAPI(sync_url, username)
if not keys: if not keys:
keys = sn_api.genKeys(password) keys = sn_api.gen_keys(password)
del password del password
sn_api.signIn(keys) sn_api.sign_in(keys)
log_msg = 'Successfully logged into account "%s".' log_msg = 'Successfully logged into account "%s".'
logging.info(log_msg % username) logging.info(log_msg % username)
login_success = True login_success = True
@ -164,10 +165,9 @@ def main():
keys=keys)) keys=keys))
config.write(f) config.write(f)
log_msg = 'Config written to file "%s".' log_msg = 'Config written to file "%s".'
logging.info(log_msg % str(config_file))
else: else:
log_msg = 'Clearing config file "%s".' log_msg = 'Clearing config file "%s".'
logging.info(log_msg % config_file) logging.info(log_msg % config_file)
config_file.chmod(0o600) config_file.chmod(0o600)
except OSError: except OSError:
log_msg = 'Unable to write config file "%s".' log_msg = 'Unable to write config file "%s".'

Loading…
Cancel
Save