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'])
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
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')
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