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 controllers (in the MVC sense) for the web interface
for the client side of the storage plugin.
"""
from base64 import b64decode, b64encode
from datetime import timedelta
from functools import partial
from hashlib import sha256
from typing import List
from treq import content
from treq.client import HTTPClient
from twisted.internet.defer import Deferred, fail, inlineCallbacks, returnValue, succeed
from twisted.internet.task import LoopingCall
from twisted.logger import Logger
from twisted.python.reflect import namedAny
from twisted.python.url import URL
from twisted.web.client import Agent
from zope.interface import Interface, implementer
from ._json import dumps_utf8
from ._stack import less_limited_stack
from .model import Error as model_Error
from .model import Pass
from .model import Pending as model_Pending
from .model import RandomToken
from .model import Redeeming as model_Redeeming
from .model import UnblindedToken
from .model import Unpaid as model_Unpaid
from .model import Voucher
RETRY_INTERVAL = timedelta(milliseconds=1000)
# It would be nice to have frozen exception types but Failure.cleanFailure
# interacts poorly with these.
# https://twistedmatrix.com/trac/ticket/9641
# https://twistedmatrix.com/trac/ticket/9771
@attr.s(auto_attribs=True)
class UnexpectedResponse(Exception):
"""
The issuer responded in an unexpected and unhandled way.
"""
code: int = attr.ib()
body: bytes = attr.ib()
class AlreadySpent(Exception):
"""
An attempt was made to redeem a voucher which has already been redeemed.
The redemption cannot succeed and should not be retried automatically.
"""
class Unpaid(Exception):
"""
An attempt was made to redeem a voucher which has not yet been paid for.
The redemption attempt may be automatically retried at some point.
"""
@attr.s(auto_attribs=True)
class UnrecognizedFailureReason(Exception):
"""
An attempt was made to redeem a voucher and the response contained an unknown reason.
The redemption attempt may be automatically retried at some point.
"""
response: dict = attr.ib()
@attr.s
class RedemptionResult(object):
"""
Contain the results of an attempt to redeem a voucher for ZKAP material.
:ivar unblinded_tokens: The tokens which resulted from the redemption.
:ivar public_key: The public key which the server proved was involved in
the redemption process.
"""
unblinded_tokens: List[UnblindedToken] = attr.ib(
validator=attr.validators.instance_of(list),
)
validator=attr.validators.instance_of(str),
)
class IRedeemer(Interface):
"""
An ``IRedeemer`` can exchange a voucher for one or more passes.
"""
def random_tokens_for_voucher(voucher, counter, count):
"""
Generate a number of random tokens to use in the redemption process for
the given voucher.
:param Voucher voucher: The voucher the tokens will be associated
with.
:param int counter: See ``redeemWithCounter``.
:param int count: The number of random tokens to generate.
:return list[RandomToken]: The generated tokens. Random tokens must
be unique over the lifetime of the Tahoe-LAFS node where this
plugin is being used but the same tokens *may* be generated for
the same voucher. The tokens must be kept secret to preserve the
anonymity property of the system.
"""
def redeemWithCounter(voucher, counter, random_tokens):
Redeem a voucher for unblinded tokens which can be used to construct
passes.
Implementations of this method do not need to be fault tolerant. If a
redemption attempt is interrupted before it completes, it is the
caller's responsibility to call this method again with the same
arguments.
:param Voucher voucher: The voucher to redeem.
:param int counter: The counter to use in this redemption attempt. To
support vouchers which can be redeemed for a larger number of
tokens than is practical to handle at once, one voucher can be
partially redeemed repeatedly until the complete set of tokens has
been received. Each partial redemption must have a distinct
counter value.
:param list[RandomToken] random_tokens: The random tokens to use in
the redemption process.
:return: A ``Deferred`` which fires with a ``RedemptionResult``
instance or which fails with any error to allow a retry to be made
at some future point. It may also fail with an ``AlreadySpent``
error to indicate the redemption server considers the voucher to
have been redeemed already and will not allow it to be redeemed.
def tokens_to_passes(message, unblinded_tokens):
"""
Construct passes from unblinded tokens which are suitable for use with a
given message.
:param bytes message: A valid utf-8-encoded byte sequence which serves
to protect the resulting passes from replay usage. It is
preferable if every use of passes is associated with a unique
message.
:param list[UnblindedToken] unblinded_tokens: Unblinded tokens,
previously returned by a call to this implementation's ``redeem``
method.
:return list[Pass]: Passes constructed from the message and unblinded
tokens. There is one pass in the resulting list for each unblinded
token in ``unblinded_tokens``.
"""
@attr.s
@implementer(IRedeemer)
class IndexedRedeemer(object):
"""
A ``IndexedRedeemer`` delegates redemption to a redeemer chosen to
correspond to the redemption counter given.
"""
_log = Logger()
redeemers = attr.ib()
def random_tokens_for_voucher(self, voucher, counter, count):
return dummy_random_tokens(voucher, counter, count)
Loading
Loading full blame...