Example #1
0
 def test_allowed_methods_of(self):
     self.allowed_list = permit.allowed_methods_of(
         self.narrowedConfig['wat']['methods'])
     print "\n"
     print "Allowed List from Methods Fragment"
     print "Original config fragment: %s" % self.narrowedConfig['wat']['methods']
     print "Result: %s" % self.allowed_list
     self.assertIn('GET', self.allowed_list['offered'])
     self.assertNotIn('PUT', self.allowed_list['offered'])
Example #2
0
def api_dict(config):
    """Accepts a config file, pre-narrowed to user_role.
    Returns a dictionary that can be expanded
    into a readable api.
    """
    result = {res:
              {'meta': meta_for(res), 'urls': []}
              for res in config if res != 'index'
              }
    viewmaps = convert_to_viewmaps(config)
    for resource, res_config in viewmaps.items():
        prefix = (resource is not 'index') and ("/%s/:id" % resource) or ''
        traversals = res_config.get('traversals')
        # First of all, the forward links from here. We're going to make an entry for each in
        # the forward resource's 'urls' key.
        if traversals:
            valid_traversals = [
                t for t
                in traversals
                if t['resource'] in
                helpers.valid_traversals(resource, config)['many']]
            # Each t will be a dictionary such as
            # {'url';'foo','methods':['GET','PUT']}
            for t in valid_traversals:
                # finesse point. We're actually appending these urls to the result for the resource
                # they link to. That is, using the above example, we're appending these urls to
                # result {'foo'['urls']}
                target = t['url']
                for method in t['methods']:
                    url_dict = {'url': '%s/%s' %
                                (prefix, target), 'method': method}
                    target_fields = viewmaps[target]['fields']
                    if method == 'GET':
                        url_dict['fields'] = target_fields['read']
                    elif method == 'POST':
                        url_dict['fields'] = target_fields['write']
                    result[target]['urls'].append(url_dict)
        methods = res_config.get('methods')
        if methods and resource is not "index":
            available_methods = permit.allowed_methods_of(methods)
            for m in available_methods['allowed']:
                url_dict = {'url': '/%s/:id' % resource, 'method': m}
                if m == 'GET':
                    url_dict['fields'] = res_config['fields']['read']
                if m == 'PUT':
                    url_dict['fields'] = res_config['fields']['write']
                result[resource]['urls'].append(url_dict)
    return result
Example #3
0
def handler(request, parent=None, parent_id=None,
            resource=None, resource_id=None):
    """Our job is to thoroughly screen the request so we can hand it
    to our methods freely.
    """
    # Until the day we hook Google's auth into our middleware:
    request.user = CustomUser.objects.all()[0]
    logging.info('User: %s, groups: %s' % (str(request.user), str(request.user.memberships.all())))
    # Are these even in our defined list of resources?
    if resource not in RESOURCES or (parent and parent not in RESOURCES):
        raise Http404

    # Does this parent (if there is one) even exist?
    if parent:
        try:
            parent_inst = model_for(parent).objects.get(pk=parent_id)
        except ObjectDoesNotExist:
            raise Http404
        # We'll see if the parent and child are even in the same family tree.
        if not same_tree(request.user, parent_inst):
            raise Http404
        # We need to see if the user is allowed to GET the parent_inst
        # because we will allow the resource to reveal some
        # information about its parent.

        conf = instance_permissions(request.user, parent_inst)
        if not conf[parent]['methods']['GET'] & 1:
            raise PermissionDenied
    else:
        conf = instance_permissions(request.user)

    # Now let's see if we can go from the parent (or index)
    # to this resource.
    traversals = sorted_traversals(resource=(parent or 'index'), config=conf)
    if not resource in traversals.get('many', []):
        raise PermissionDenied

    opts = {'resource': resource,
            'user': request.user,
            'parent': parent,
            'parent_id': parent_id,
            'config': conf
            }

    if resource_id:
        try:
            inst = model_for(resource).objects.get(pk=resource_id)
        except ObjectDoesNotExist:
            raise Http404
        if not same_tree(request.user, inst):
            raise Http404
        # Time to retrieve the user's permissions for this instance.
        conf = instance_permissions(request.user, inst)
        logging.info('config for resource %s: %s' % (resource, conf[resource]))
        # Now we find out of a user in this role can do this method to this instance.
        allowed = permit.allowed_methods_of(conf[resource]['methods'])['allowed']
        if not request.method in allowed:
            logger.error('Got resource_id, not allowed from method %s, allowed=%s' %
                (request.method, allowed))
            raise PermissionDenied

        if request.method == 'GET':
            opts['instance'] = inst
            opts['config'] = conf
            requested_form = request.GET.get('form')
            # We may be getting a request for a form, for PUTting or DELETEing.
            # If the user couldn't do this method anyway we just ignore the
            # QueryDict.
            if requested_form == 'edit' and 'PUT' in allowed:
                context = methods.get_put_form(**opts)
                template = 'put_form.html'
            if requested_form == 'delete' and 'DELETE' in allowed:
                context = methods.get_delete_form(**opts)
                template = 'delete_form.html'
            else:
                context = methods.get_instance(**opts)
                template = '%s_detail.html' % resource
        if request.method == 'PUT':
            pass
        if request.method == 'DELETE':
            pass 
    else:
        if request.method == 'GET':
            if request.GET.get('form') == 'create':
                context = methods.get_post_form(**opts)
                template = 'post_form.html'
            else:
                context = methods.get_collection(**opts)
                # magic naming again.
                template = '%s_in_%s.html' % (resource, parent or 'index')
        if request.method == 'POST':
            # We'll either get a redirect or a response containing a new form.
            opts['request'] = request
            opts['parent_instance'] = parent and parent_inst or None
            post_result = methods.post_to_collection(**opts)
            # post_result sends a tuple of (True/False, context).
            template = post_result[0] and '%s_in_%s.html' % (resource, parent or 'index')\
                or\
                'post_form.html'
            context = post_result[1]       
    return HttpResponse(render(request, template, context), mimetype='text/html')
Example #4
0
def get_instance(parent=None, parent_id=None, resource=None, instance=None, config=None, user=None, depth=0):
    """ Handles instances. Returns a dictionary of:
        - metadata about the instance;
        - the fields the user is entitled to GET,
        - relations (related resources)

        If the method for any relation is anything but GET
        we don't recurse into it.

        Note that the config has been helpfully pre-tapered down to
        just a single bit for each field and method. So at this level,
        we don't 'know' if the user is owner, group, world and what
        group. Doesn't matter, just do your work, you function.

        Some day, when we're not relying on Django model/instance methods,
        we can pass dictionaries around instead of instances and be, perhaps,
        more efficient with the database.
        Someday.
    """
    allowed = permit.allowed_methods_of(config[resource]["methods"])["allowed"]

    # If we called this directly, we want to remove a redundant 'GET'
    # from the list of methods we'll call to the user.
    if not depth:
        allowed.remove("GET")
    # Even an empty set will return this:
    result = {"meta": {}, "fields": {}, "relations": {}}
    parent_url = parent and "/%s/%s" % (parent, parent_id) or ""

    # We now remake the config from the user.
    config = help.instance_permissions(user, instance)
    result["meta"] = {
        "url": "%s%s" % (parent_url, instance.url),
        "methods": allowed,
        "tagged_with": [resource],
        "resource": resource,
    }

    # Now we sort the config into the kind of dictionary we are looking
    # for:
    viewmap = help.viewmap_of(resource, config)

    # And now, the "I'd like to thank my parents" clause.
    # There are two reasons why we might stuff a link to the parent in this dictionary.
    # One is that this is exactly the resource asked for in the user's
    # request. The other is that the instance's parent is not the parent
    # sent to this function.
    if instance.parent_instance:
        parent_resource = instance.parent_instance._meta.verbose_name_plural
        if (not depth) or (parent_resource != parent):
            result["meta"]["parent"] = {
                "resource": parent_resource,
                "title": str(instance.parent_instance),
                "url": instance.parent_instance.url,
            }
            viewmap["traversals"] = [t for t in viewmap["traversals"] if t["resource"] != parent_resource]
    result["fields"] = {f: getattr(instance, f) for f in viewmap["fields"]["read"]}
    # Now we add the traversables and, if we have more to go, fire off a
    # call for each one:
    forward = help.valid_traversals(resource, config)
    for t in viewmap["traversals"]:
        if "GET" in t["methods"]:
            target_resource = t["resource"]
            is_collection = target_resource in forward["many"].keys()
            if VIEW_TRAVERSE_DEPTH - depth:
                if is_collection:
                    opts = {
                        "parent": resource,
                        "parent_id": instance.pk,
                        "resource": target_resource,
                        "user": user,
                        "config": config,
                        "depth": depth + 1,
                    }
                    relation = get_collection(**opts)
                else:
                    target_instance = getattr(instance, target_resource)
                    opts = {
                        "resource": reverbose(target_resource),
                        "instance": target_instance,
                        "user": user,
                        "config": config,
                        "depth": depth + 1,
                    }
                    relation = get_instance(**opts)
            else:
                # if we have no further loops to do, we'll just put a link to
                # the traversal.
                target = getattr(instance, target_resource)
                if is_collection:
                    relation = {"meta": {"url": target.url(), "title": str(target), "methods": t["methods"]}}
                else:
                    relation = {"meta": {"url": str(getattr(target, "url")), "title": str(target), "methods": ["GET"]}}
            result["relations"][target_resource] = relation
    result["depth left"] = VIEW_TRAVERSE_DEPTH - depth
    return result