Passed
Push — master ( 81cb0a...05d49c )
by Mingyu
01:08
created

app.views.json_required()   D

Complexity

Conditions 8

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 28
rs 4
c 0
b 0
f 0
cc 8
nop 1
1
from functools import wraps
2
import gzip
3
import ujson
4
import time
5
6
from flask import Response, abort, after_this_request, g, request
7
from flask_jwt_extended import jwt_required, get_jwt_identity
8
from flask_restful import Resource
9
10
11
def after_request(response):
12
    """
13
    Set header - X-Content-Type-Options=nosniff, X-Frame-Options=deny before response
14
    """
15
    response.headers['X-Content-Type-Options'] = 'nosniff'
16
    response.headers['X-Frame-Options'] = 'deny'
17
18
    return response
19
20
21
def exception_handler(e):
22
    # TODO
23
24
    return '', 500
25
26
27
def gzipped(fn):
28
    """
29
    View decorator for gzip compress the response body
30
    """
31
    @wraps(fn)
32
    def wrapper(*args, **kwargs):
33
        @after_this_request
34
        def zipper(response):
35
            if 'gzip' not in request.headers.get('Accept-Encoding', '')\
36
                    or not 200 <= response.status_code < 300\
37
                    or 'Content-Encoding' in response.headers:
38
                # 1. Accept-Encoding에 gzip이 포함되어 있지 않거나
39
                # 2. 200번대의 status code로 response하지 않거나
40
                # 3. response header에 이미 Content-Encoding이 명시되어 있는 경우
41
                return response
42
43
            response.data = gzip.compress(response.data)
44
            response.headers['Content-Encoding'] = 'gzip'
45
            response.headers['Vary'] = 'Accept-Encoding'
46
            response.headers['Content-Length'] = len(response.data)
47
48
            return response
49
        return fn(*args, **kwargs)
50
    return wrapper
51
52
53
def auth_required(model):
54
    def decorator(fn):
55
        """
56
        View decorator for access control
57
        """
58
        @wraps(fn)
59
        @jwt_required
60
        def wrapper(*args, **kwargs):
61
            user = model.objects(id=get_jwt_identity()).first()
62
            if not user:
63
                abort(403)
64
65
            g.user = user
66
67
            return fn(*args, **kwargs)
68
        return wrapper
69
    return decorator
70
71
72
def json_required(required_keys):
73
    """
74
    View decorator for JSON validation.
75
76
    - If content-type is not application/json : returns status code 406
77
    - If required_keys are not exist on request.json : returns status code 400
78
79
    Args:
80
        required_keys (dict): Required keys on requested JSON payload
81
    """
82
    def decorator(fn):
83
        if fn.__name__ == 'get':
84
            print('[WARN] JSON with GET method? on "{}()"'.format(fn.__qualname__))
85
86
        @wraps(fn)
87
        def wrapper(*args, **kwargs):
88
            if not request.is_json:
89
                abort(406)
90
91
            for key, typ in required_keys.items():
92
                if key not in request.json or not isinstance(request.json[key], typ):
93
                    abort(400)
94
                if typ is str and not request.json[key]:
95
                    abort(400)
96
97
            return fn(*args, **kwargs)
98
        return wrapper
99
    return decorator
100
101
102
class BaseResource(Resource):
103
    """
104
    BaseResource with some helper functions based flask_restful.Resource
105
    """
106
    def __init__(self):
107
        self.now = time.strftime('%Y-%m-%d %H:%M:%S')
108
109
    @classmethod
110
    def unicode_safe_json_dumps(cls, data, status_code=200, **kwargs) -> Response:
111
        """
112
        Helper function which processes json response with unicode using ujson
113
114
        Args:
115
            data (dict or list): Data for dump to JSON
116
            status_code (int): Status code for response
117
        """
118
        return Response(
119
            ujson.dumps(data, ensure_ascii=False),
120
            status_code,
121
            content_type='application/json; charset=utf8',
122
            **kwargs
123
        )
124
125
126
class Router:
127
    """
128
    REST resource routing helper class like standard flask 3-rd party libraries
129
    """
130
    def __init__(self, app=None):
131
        if app is not None:
132
            self.init_app(app)
133
134
    def init_app(self, app):
135
        """
136
        Routes resources. Use app.register_blueprint() aggressively
137
        """
138
        app.after_request(after_request)
139
        app.register_error_handler(Exception, exception_handler)
140
141
        from app.views import sample
142
        app.register_blueprint(sample.api.blueprint)
143