Example #1
0
 def recv_connect(self, version=None, support=None, session=None):
     """DDP connect handler."""
     del session  # Meteor doesn't even use this!
     if self.connection is not None:
         raise MeteorError(
             400, 'Session already established.',
             self.connection.connection_id,
         )
     elif None in (version, support) or version not in self.versions:
         self.reply('failed', version=self.versions[0])
     elif version not in support:
         raise MeteorError(400, 'Client version/support mismatch.')
     else:
         from dddp.models import Connection
         cur = connection.cursor()
         cur.execute('SELECT pg_backend_pid()')
         (backend_pid,) = cur.fetchone()
         this.version = version
         this.support = support
         self.connection = Connection.objects.create(
             server_addr='%d:%s' % (
                 backend_pid,
                 self.ws.handler.socket.getsockname(),
             ),
             remote_addr=self.remote_addr,
             version=version,
         )
         self.pgworker.connections[self.connection.pk] = self
         atexit.register(self.on_close, 'Shutting down.')
         self.reply('connected', session=self.connection.connection_id)
Example #2
0
 def get_username(self, user):
     """Retrieve username from user selector."""
     if isinstance(user, basestring):
         return user
     elif isinstance(user, dict) and len(user) == 1:
         [(key, val)] = user.items()
         if key == 'username' or (key == self.user_model.USERNAME_FIELD):
             # username provided directly
             return val
         elif key in ('email', 'emails.address'):
             email_field = getattr(self.user_model, 'EMAIL_FIELD', 'email')
             if self.user_model.USERNAME_FIELD == email_field:
                 return val  # email is username
             # find username by email
             return self.user_model.objects.values_list(
                 self.user_model.USERNAME_FIELD, flat=True,
             ).get(**{email_field: val})
         elif key in ('id', 'pk'):
             # find username by primary key (ID)
             return self.user_model.objects.values_list(
                 self.user_model.USERNAME_FIELD, flat=True,
             ).get(
                 pk=val,
             )
         else:
             raise MeteorError(400, 'Invalid user lookup: %r' % key)
     else:
         raise MeteorError(400, 'Invalid user expression: %r' % user)
Example #3
0
def validate_kwargs(func, kwargs):
    """Validate arguments to be supplied to func."""
    func_name = func.__name__
    argspec = inspect.getargspec(func)
    all_args = argspec.args[:]
    defaults = list(argspec.defaults or [])

    # ignore implicit 'self' argument
    if inspect.ismethod(func) and all_args[:1] == ['self']:
        all_args[:1] = []

    # don't require arguments that have defaults
    if defaults:
        required = all_args[:-len(defaults)]
    else:
        required = all_args[:]

    # translate 'foo_' to avoid reserved names like 'id'
    trans = {
        arg: arg.endswith('_') and arg[:-1] or arg
        for arg
        in all_args
    }
    for key in list(kwargs):
        key_adj = '%s_' % key
        if key_adj in all_args:
            kwargs[key_adj] = kwargs.pop(key)

    # figure out what we're missing
    supplied = sorted(kwargs)
    missing = [
        trans.get(arg, arg) for arg in required
        if arg not in supplied
    ]
    if missing:
        raise MeteorError(
            400,
            func.err,
            'Missing required arguments to %s: %s' % (
                func_name,
                ' '.join(missing),
            ),
        )

    # figure out what is extra
    extra = [
        arg for arg in supplied
        if arg not in all_args
    ]
    if extra:
        raise MeteorError(
            400,
            func.err,
            'Unknown arguments to %s: %s' % (func_name, ' '.join(extra)),
        )
Example #4
0
 def method(self, method, params, id_):
     """Invoke a method."""
     try:
         handler = self.api_path_map()[method]
     except KeyError:
         raise MeteorError(404, 'Method not found', method)
     try:
         inspect.getcallargs(handler, *params)
     except TypeError as err:
         raise MeteorError(400, '%s' % err)
     result = handler(*params)
     msg = {'msg': 'result', 'id': id_}
     if result is not None:
         msg['result'] = result
     this.send(msg)
Example #5
0
    def dispatch(self, msg, kwargs):
        """Dispatch msg to appropriate recv_foo handler."""
        # enforce calling 'connect' first
        if self.connection is None and msg != 'connect':
            self.reply('error', reason='Must connect first')
            return
        if msg == 'method':
            if (
                'method' not in kwargs
            ) or (
                'id' not in kwargs
            ):
                self.reply(
                    'error', error=400, reason='Malformed method invocation',
                )
                return

        # lookup method handler
        try:
            handler = getattr(self, 'recv_%s' % msg)
        except (AttributeError, UnicodeEncodeError):
            raise MeteorError(404, 'Method not found')

        # validate handler arguments
        validate_kwargs(handler, kwargs)

        # dispatch to handler
        handler(**kwargs)
Example #6
0
 def get_password(password):
     """Return password in plain-text from string/dict."""
     if isinstance(password, basestring):
         # regular Django authentication - plaintext password... but you're
         # using HTTPS (SSL) anyway so it's protected anyway, right?
         return password
     else:
         # Meteor is trying to be smart by doing client side hashing of the
         # password so that passwords are "...not sent in plain text over
         # the wire".  This behaviour doesn't make HTTP any more secure -
         # it just gives a false sense of security as replay attacks and
         # code-injection are both still viable attack vectors for the
         # malicious MITM.  Also as no salt is used with hashing, the
         # passwords are vulnerable to rainbow-table lookups anyway.
         #
         # If you're doing security, do it right from the very outset.  For
         # web services that means using SSL and not relying on half-baked
         # security concepts put together by people with no security
         # background.
         #
         # We protest loudly to anyone who cares to listen in the server
         # logs until upstream developers see the light and drop the
         # password hashing mis-feature.
         raise MeteorError(
             426,
             "Outmoded password hashing: "
             "https://github.com/meteor/meteor/issues/4363",
             upgrade='meteor add tysonclugg:accounts-secure',
         )
Example #7
0
 def auth_failed(**credentials):
     """Consistent fail so we don't provide attackers with valuable info."""
     if credentials:
         user_login_failed.send_robust(
             sender=__name__,
             credentials=auth._clean_credentials(credentials),
         )
     raise MeteorError(403, 'Authentication failed.')
Example #8
0
 def check_secure():
     """Check request, return False if using SSL or local connection."""
     if this.request.is_secure():
         return True  # using SSL
     elif this.request.META['REMOTE_ADDR'] in [
             'localhost',
             '127.0.0.1',
     ]:
         return True  # localhost
     raise MeteorError(403, 'Authentication refused without SSL.')
Example #9
0
 def do_sub(self, id_, name, silent, *params):
     """Subscribe the current thread to the specified publication."""
     try:
         pub = self.get_pub_by_name(name)
     except KeyError:
         if not silent:
             raise MeteorError(404, 'Subscription not found')
         return
     sub, created = Subscription.objects.get_or_create(
         connection_id=this.ws.connection.pk,
         sub_id=id_,
         user_id=getattr(this, 'user_id', None),
         defaults={
             'publication': pub.name,
             'params_ejson': ejson.dumps(params),
         },
     )
     this.subs.setdefault(sub.publication, set()).add(sub.pk)
     if not created:
         if not silent:
             this.send({'msg': 'ready', 'subs': [id_]})
         return
     # re-read from DB so we can get transaction ID (xmin)
     sub = Subscription.objects.extra(**XMIN).get(pk=sub.pk)
     for col, qs in self.sub_unique_objects(
             sub,
             params,
             pub,
             xmin__lte=sub.xmin,
     ):
         sub.collections.create(
             model_name=model_name(qs.model),
             collection_name=col.name,
         )
         if isinstance(col.model._meta.pk, AleaIdField):
             meteor_ids = None
         elif len([
                 field for field in col.model._meta.local_fields
                 if (isinstance(field, AleaIdField)) and (
                     field.unique) and (not field.null)
         ]) == 1:
             meteor_ids = None
         else:
             meteor_ids = get_meteor_ids(
                 qs.model,
                 qs.values_list('pk', flat=True),
             )
         for obj in qs.select_related():
             payload = col.obj_change_as_msg(obj, ADDED, meteor_ids)
             this.send(payload)
     if not silent:
         this.send({'msg': 'ready', 'subs': [id_]})
Example #10
0
    def update(self, selector, update, options=None):
        """Update user data."""
        # we're ignoring the `options` argument at this time
        del options
        user = get_object(
            self.model, selector['_id'],
            pk=this.user_id,
        )
        profile_update = self.deserialize_profile(
            update['$set'], key_prefix='profile.', pop=True,
        )
        if len(update['$set']) != 0:
            raise MeteorError(400, 'Invalid update fields: %r')

        for key, val in profile_update.items():
            setattr(user, key, val)
        user.save()
Example #11
0
    def deserialize_profile(profile, key_prefix='', pop=False):
        """De-serialize user profile fields into concrete model fields."""
        result = {}
        if pop:
            getter = profile.pop
        else:
            getter = profile.get

        def prefixed(name):
            """Return name prefixed by `key_prefix`."""
            return '%s%s' % (key_prefix, name)

        for key in profile.keys():
            val = getter(key)
            if key == prefixed('name'):
                result['full_name'] = val
            else:
                raise MeteorError(400, 'Bad profile key: %r' % key)
        return result