Example #1
0
def is_signed_url_valid(user, url):
    """Check whether a signed URL is valid according to the user's signing secret."""
    parsed = url_parse(url)
    params = url_decode(parsed.query)
    try:
        signature = params.pop('token')
    except KeyError:
        return False

    url = url_unparse(
        ('', '', parsed.path, url_encode(sorted(params.items()),
                                         sort=True), parsed.fragment))
    signer = Signer(user.signing_secret, salt='url-signing')
    return signer.verify_signature(url, signature)
Example #2
0
def is_legacy_signed_url_valid(user, url):
    """Check whether a legacy signed URL is valid for a user.

    This util is deprecated and only exists because people may be actively
    using URLs using the old style token. Any new code should use the new
    :func:`signed_url_for_user` and :func:`verify_signed_user_url` utils
    which encode the user id within the signature.
    """
    parsed = url_parse(url)
    params = url_decode(parsed.query)
    try:
        signature = params.pop('token')
    except KeyError:
        return False

    url = url_unparse(
        ('', '', parsed.path, url_encode(params, sort=False), parsed.fragment))
    signer = Signer(user.signing_secret, salt='url-signing')
    return signer.verify_signature(url.encode(), signature)
Example #3
0
def check_user_signature(user_data, signature, fields=None):
    """Check that user data matches the signature."""
    signer = Signer(current_app.config['SECRET_KEY'], salt='newdle-users')
    return signer.verify_signature(
        _get_signature_source_bytes(user_data, fields),
        signature.encode('ascii'))
Example #4
0
class VSDatabase(object):
    MAX_LENGTH = 50

    def __init__(self):
        self._s = NullSigner()
        self._config_defaults = dict()

    def init_app(self, app):
        """
        Initializes the database with the application object.

        :param app: Flask application object
        """
        secret = app.config['SECRET_KEY']
        if secret:
            self._s = Signer(secret)

        self._config_defaults = dict(
            (k.lower(), v) for k, v in app.config['DEFAULTS'].items()
        )

    def initialize(self):
        """
        Function which is supposed to initialize the database
        only once. This can be used for creating SQL schemes.
        """
        pass

    def generate_id(self, alphabet=None):
        """
        Generates a new unique Id, race conditions possible!

        :param alphabet: Alphabet to generate the Id from.
        """
        if alphabet is None:
            alphabet = self.config_get('alphabet')

        length = 3
        id = ''.join(random.sample(alphabet, length))
        while self.has_id(id):
            id = ''.join(random.sample(alphabet, length))
            length = min(length + 1, self.MAX_LENGTH)

        return id

    def config_get(self, key, domain=None):
        """
        Fetches a configuration value from the underlaying database.

        :param key: Key to lookup.
        :param domain: If domain is not set the Flask
            request object will be used to get the current domain.
        """
        key = key.lower()
        if domain is None:
            domain = urlparse(request.url).netloc
        result = self._config_get(domain, key)
        if result is None:
            result = self._config_defaults.get(key)
        return result

    def config_set(self, key, value, domain=None):
        """
        Setsa configuration value.

        :param key: Key to set.
        :param value: Value to set.
        :param domain: If domain is not set the Flask
            request object will be used to get the current domain.
        """
        key = key.lower()
        if domain is None:
            domain = urlparse(request.url).netloc
        self._config_set(domain, key, value)

    def config_delete(self, domain=None):
        """
        Deletes (resets) the configuration for the domain.

        :param domain: If domain is not set the Flask
            request object will be used to get the current domain.
        """
        if domain is None:
            domain = urlparse(request.url).netloc
        self._config_delete(domain)

    def get_domain(self, domain=None):
        """
        Returns the domain or the alias (if set),
        for the current domain.
        This method requires a flask application-context,
        if called without :param:`domain`.

        :param domain: use this domain instead of the current domain
        """
        if domain is None:
            domain = urlparse(request.url).netloc

        alias = self.config_get('alias', domain=domain)
        if alias is not None:
            domain = alias

        return domain

    def get(self, id):
        """
        Fetches the long Url for the given Id.

        :param id: Id to lookup.
        """
        domain = self.get_domain()
        result = self._get(domain, id)
        if result is None:
            raise IdNotFound('Id "{0}" not found'.format(id), 404)

        return want_unicode(result)

    def has_id(self, id):
        """
        Tests if this Id is already used.

        :param id: Id to lookup.
        :return: bool
        """
        try:
            result = self.get(id)
        except IdNotFound:
            return False

        return result is not None

    def create(self, url, id=None, expiry=None):
        """
        Creates a new short URL.

        :param id: Custom Id, if `None` :method:`generate_id` will be used.
        :param expiry: Time in days after which the URL will expire
            (`None` means it will never expire)
        :return: (id, expiry, secret)
        """
        p = urlparse(url)
        if not p.scheme or not p.netloc:
            raise InvalidUrl('Url does not contain scheme and/or netloc', 400)

        if not self.config_get('custom_ids') and id is not None:
            raise InvalidId('Custom Ids are disabled', 400)

        # use default if not set
        # infite < 0 (easier to work with)
        if expiry is None:
            expiry = self.config_get('default_expiry')
        expiry = -1 if expiry is None else expiry

        max_expiry = self.config_get('max_expiry')
        if max_expiry is not None and max_expiry > 0:
            if expiry > 0:
                expiry = min(expiry, max_expiry)
            else:
                expiry = max_expiry

        # infinite = None (return value)
        expiry = datetime.timedelta(days=expiry) if expiry > 0 else None

        domain = self.get_domain()

        if id is None:
            while True:
                id = self.generate_id()
                try:
                    self._create(domain, id, url, expiry=expiry)
                except IdAlreadyExists:
                    # race condition, do it again
                    continue
                break
        else:
            if len(id) > self.MAX_LENGTH:
                raise InvalidId('Id is too long', 400)
            if not all(c in self.config_get('alphabet') for c in id):
                raise InvalidId('Id contains invalid characters', 400)

            # if the key already exists, not our problem
            self._create(domain, id, url, expiry=expiry)

        return (
            id,
            getattr(expiry, 'days', None),
            want_unicode(self._s.get_signature(id))
        )

    def delete(self, id, secret):
        """
        Deletes an short URL.

        :param id: Id to delete.
        :param secret: Secret returned by :method:`create` for the Id.
        """
        domain = self.get_domain()

        if self._s.verify_signature(want_bytes(id), want_bytes(secret)):
            self._delete(domain, id)
            return True

        raise InvalidDeletionSecret('Invalid secret', 400)

    def _config_get(self, domain, key):
        raise NotImplementedError

    def _config_set(self, domain, key, value):
        raise NotImplementedError

    def _config_delete(self, domain):
        raise NotImplementedError

    def _get(self, domain, id):
        raise NotImplementedError

    def _create(self, domain, id, url, expiry=None):
        raise NotImplementedError

    def _delete(self, domain, id):
        raise NotImplementedError