aboutsummaryrefslogtreecommitdiff
path: root/api/hierarchy.py
blob: 7b60f8dcb0cec9a20438f084fa09b88fbcf2065d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
from flask import request
from auth.login_token import token_login
from db import cursor
from util import all_def, all_notdef
import valid

ranks = ["none", "user", "moderator", "admin", "bot"]


def util_two_id(type="user"):
    '''
    type?: "user" | "game"
    ! only used internally !
    func(token_id?: str, explicit_id?: str)

    This decorator doesn't check for hierarchy constraints, but does
    make sure that token_id or explicit_id are valid user_id's
    '''
    def decorator(func):
        def wrapper():
            token_id = None
            explicit_id = None

            token = request.cookies.get("token") or ""
            if token: token_id = token_login(token)

            data = request.get_json()
            if data: explicit_id = data.get("id")

            # if there's an explicit_id, validate it using `type`
            if explicit_id and \
               not valid.validate(explicit_id, type):
                explicit_id = None

            return func(token_id, explicit_id)

        wrapper.__name__ = func.__name__
        return wrapper

    return decorator


def two_person(func):
    '''
    endpoint should have two parameters:
    endpoint(user_1_id: str, user_2_id: str)

    no authentication, just runs endpoint() if both token_id and
    explicit_id are present from @util_two_id.
    '''
    @util_two_id("user")
    def wrapper(token_id, explicit_id):
        if not all_def([token_id, explicit_id]):
            return "", 400

        return func(token_id, explicit_id)

    wrapper.__name__ = func.__name__
    return wrapper


def one_person(func):
    '''
    endpoint should have two parameters:
    endpoint(user_id: str, viewer?: str)

    uses json data id with token_login id as fallback
    doesn't check for authentication
    expects that func takes these arguments: (user_id, viewer?)
    '''
    @util_two_id("user")
    def wrapper(token_id, explicit_id):
        if all_notdef([token_id, explicit_id]):
            return "", 400

        return func(explicit_id or token_id, token_id)

    wrapper.__name__ = func.__name__
    return wrapper


def game_id_with_viewer(func):
    '''
    endpoint should have two parameters:
    endpoint(game_id: str, viewer?: str)
    '''
    @util_two_id("game")
    def wrapper(token_id, game_id):
        if all_notdef([token_id, game_id]):
            return "", 400

        return func(game_id, token_id)

    wrapper.__name__ = func.__name__
    return wrapper


def auth_required(level):
    '''
    level = "none" | "user" | "moderator" | "admin" | "bot"
    endpoint should have one parameter for the user_id of the request author:
    endpoint(user_id: str) # `user_id` can only be `None` when `level == "none"`

    @auth_required function decorator (use after @flask.Blueprint.route() decorator)
    This decorator only runs endpoint() if token_id from
    @util_two_id is not None and passes hierarchy constraints
    '''
    def decorator(func):
        @util_two_id("user")
        def wrapper(token_id, explicit_id):
            if not token_id:
                if level == ranks[0]:
                    return func(None)
                else:
                    return "", 400

            user_rank_text = cursor.execute(
                "select type from users where user_id = ?", [token_id]
            ).fetchone()[0]

            required_rank = ranks.index(level)
            user_rank = ranks.index(user_rank_text)
            if required_rank > user_rank: return "", 403

            return func(token_id)

        wrapper.__name__ = func.__name__
        return wrapper

    return decorator


def io_auth_required(level):
    '''
    level = "none" | "user" | "moderator" | "admin" | "bot"
    endpoint should have two parameters:
    endpoint(data: socket.io.data, user_id: str) # `user_id` can only be `None` when `level == "none"`

    uses the @auth_required decorator, but is only for use with
    @io.on decorators
    '''
    def decorator(func):
        # data is the original @io.on data
        def wrapper(data={}):

            token = request.cookies.get("token") or ""
            user_id = token_login(token)

            if not user_id:
                if level == ranks[0]:
                    return func(data, None)
                else:
                    return

            return func(data, user_id)

        wrapper.__name__ = func.__name__
        return wrapper

    return decorator