1
|
|
|
# Copyright (c) 2019 Alexander Todorov <[email protected]> |
2
|
|
|
|
3
|
|
|
# Licensed under the GPL 2.0: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html |
4
|
|
|
|
5
|
|
|
import astroid |
6
|
|
|
|
7
|
|
|
from pylint import interfaces |
8
|
|
|
from pylint import checkers |
9
|
|
|
|
10
|
|
|
|
11
|
|
|
class MissingPermissionsChecker(checkers.BaseChecker): |
12
|
|
|
""" |
13
|
|
|
Will inspect functions and classes inside a views.py module for the |
14
|
|
|
presence of permissions decorator. May generate lots of false negatives |
15
|
|
|
but we're ok with that. Better inspect than forget to add permission |
16
|
|
|
decorator! |
17
|
|
|
""" |
18
|
|
|
inside_views_module = False |
19
|
|
|
|
20
|
|
|
__implements__ = (interfaces.IAstroidChecker,) |
21
|
|
|
|
22
|
|
|
name = 'nested-definition-checker' |
23
|
|
|
|
24
|
|
|
msgs = {'R4511': ("View is missing @permission_required decorator!", |
25
|
|
|
'missing-permission-required', |
26
|
|
|
"All views must require permissions!")} |
27
|
|
|
|
28
|
|
|
def visit_module(self, node): |
29
|
|
|
self.inside_views_module = node.name.endswith('views') |
30
|
|
|
|
31
|
|
|
def visit_functiondef(self, node): |
32
|
|
|
if not self.inside_views_module: |
33
|
|
|
return |
34
|
|
|
|
35
|
|
|
arg0 = None |
36
|
|
|
if node.args.args: |
37
|
|
|
arg0 = node.args.args[0] |
38
|
|
|
|
39
|
|
|
if arg0 and arg0.name != 'request': |
40
|
|
|
return |
41
|
|
|
# this function is a confirmed view so start checking |
42
|
|
|
self._check_for_missing_decorator(node) |
43
|
|
|
|
44
|
|
|
def visit_classdef(self, node): |
45
|
|
|
if not self.inside_views_module: |
46
|
|
|
return |
47
|
|
|
|
48
|
|
|
# class based views always inherit from something |
49
|
|
|
# tip: we can be more precise what base classes are allowed in order |
50
|
|
|
# to identify cbv more correctly! Leaving it like that for now and will |
51
|
|
|
# revisit later if we start to see many false negatives! |
52
|
|
|
if not node.bases: |
53
|
|
|
return |
54
|
|
|
|
55
|
|
|
# for now we check all classes in views.py modules |
56
|
|
|
# until we learn how to recognize class based views |
57
|
|
|
self._check_for_missing_decorator(node) |
58
|
|
|
|
59
|
|
|
def _check_for_missing_decorator(self, node): |
60
|
|
|
if not node.decorators: |
61
|
|
|
self.add_message('missing-permission-required', node=node) |
62
|
|
|
return |
63
|
|
|
|
64
|
|
|
found_permissions_required = False |
65
|
|
|
for decorator in node.decorators.nodes: |
66
|
|
|
if isinstance(decorator, astroid.Call): |
67
|
|
|
if decorator.func.name == 'permission_required': |
68
|
|
|
found_permissions_required = True |
69
|
|
|
break |
70
|
|
|
elif decorator.func.name == 'method_decorator' and \ |
71
|
|
|
decorator.args[0].func.name == 'permission_required': |
72
|
|
|
found_permissions_required = True |
73
|
|
|
break |
74
|
|
|
|
75
|
|
|
if not found_permissions_required: |
76
|
|
|
self.add_message('missing-permission-required', node=node) |
77
|
|
|
|