Newer
Older
# Copyright 2019 PrivateStorage.io, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This module implements models (in the MVC sense) for the client side of
the storage plugin.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from future.utils import PY2
from future.builtins import (
filter,
map,
zip,
ascii,
chr,
hex,
input,
next,
oct,
open,
pow,
round,
super,
bytes,
dict,
list,
object,
range,
str,
max,
min,
) # noqa: F401
from datetime import datetime
from functools import wraps
from json import dumps, loads
from sqlite3 import OperationalError
from sqlite3 import connect as _connect
from aniso8601 import parse_datetime as _parse_datetime
from twisted.logger import Logger
from twisted.python.filepath import FilePath
from zope.interface import Interface, implementer
from ._base64 import urlsafe_b64decode
from .schema import get_schema_upgrades, get_schema_version, run_schema_upgrades
from .storage_common import (
get_configured_pass_value,
required_passes,
)
from .validators import greater_than, has_length, is_base64_encoded
Like ``aniso8601.parse_datetime`` but accept str as well.
s = s.encode("utf-8")
assert isinstance(s, bytes)
if "delimiter" in kw and isinstance(kw["delimiter"], str):
kw["delimiter"] = kw["delimiter"].encode("utf-8")
return _parse_datetime(s, **kw)
class ILeaseMaintenanceObserver(Interface):
"""
An object which is interested in receiving events related to the progress
of lease maintenance activity.
"""
def observe(sizes):
"""
Observe some shares encountered during lease maintenance.
:param list[int] sizes: The sizes of the shares encountered.
"""
def finish():
"""
Observe that a run of lease maintenance has completed.
"""
class StoreOpenError(Exception):
"""
There was a problem opening the underlying data store.
"""
def __init__(self, reason):
self.reason = reason
class NotEnoughTokens(Exception):
"""
An attempt to extract tokens failed because the store does not contain as
many tokens as were requested.
"""
CONFIG_DB_NAME = "privatestorageio-zkapauthz-v1.sqlite3"
def open_and_initialize(path, connect=None):
Open a SQLite3 database for use as a voucher store.
Create the database and populate it with a schema, if it does not already
exist.
:param FilePath path: The location of the SQLite3 database file.
:return: A SQLite3 connection object for the database at the given path.
"""
if connect is None:
connect = _connect
try:
path.parent().makedirs(ignoreExistingDirectory=True)
except OSError as e:
raise StoreOpenError(e)
dbfile = path.asBytesMode().path
try:
conn = connect(
dbfile,
isolation_level="IMMEDIATE",
)
except OperationalError as e:
raise StoreOpenError(e)
# Enforcement of foreign key constraints is off by default. It must be
# enabled on a per-connection basis. This is a helpful feature to ensure
# consistency so we want it enforced and we use it in our schema.
conn.execute("PRAGMA foreign_keys = ON")
with conn:
cursor = conn.cursor()
actual_version = get_schema_version(cursor)
schema_upgrades = list(get_schema_upgrades(actual_version))
run_schema_upgrades(schema_upgrades, cursor)
# Create some tables that only exist (along with their contents) for
# this connection. These are outside of the schema because they are not
# persistent. We can change them any time we like without worrying about
# upgrade logic because we re-create them on every connection.
conn.execute(
"""
-- Track tokens in use by the process holding this connection.
CREATE TEMPORARY TABLE [in-use] (
[unblinded-token] text, -- The base64 encoded unblinded token.
PRIMARY KEY([unblinded-token])
-- A foreign key on unblinded-token to [unblinded-tokens]([token])
-- would be alright - however SQLite3 foreign key constraints
-- can't cross databases (and temporary tables are considered to
-- be in a different database than normal tables).
)
""",
-- Track tokens that we want to remove from the database. Mainly just
-- works around the awkward DB-API interface for dealing with deleting
-- many rows.
CREATE TEMPORARY TABLE [to-discard] (
[unblinded-token] text
)
""",
)
conn.execute(
"""
-- Track tokens that we want to remove from the [in-use] set. Similar
-- to [to-discard].
CREATE TEMPORARY TABLE [to-reset] (
[unblinded-token] text
)
""",
)
Loading
Loading full blame...