Passed
Push — master ( f86286...6277dc )
by Mingyu
01:18
created

app.views.webhook_handler()   A

Complexity

Conditions 2

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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