http_handlers.py 10.5 KB
Newer Older
Pavel Spirek's avatar
Pavel Spirek committed
1 2 3 4 5 6 7 8
import json
import os
import mimetypes
from collections import OrderedDict
from colorlog import error, warning as warn, info, debug
from urllib.parse import parse_qs

from yangson.schema import NonexistentSchemaNode
Pavel Spirek's avatar
Pavel Spirek committed
9
from yangson.instance import NonexistentInstance, InstanceTypeError
Pavel Spirek's avatar
Pavel Spirek committed
10

Pavel Spirek's avatar
Pavel Spirek committed
11
from .config import CONFIG_HTTP, NACM_ADMINS, API_ROOT_data, NACM_API_ROOT_data
Pavel Spirek's avatar
Pavel Spirek committed
12
from .helpers import CertHelpers
13
from .data import BaseDatastore, Rpc, DataLockError, NacmForbiddenError, InstanceAlreadyPresent
Pavel Spirek's avatar
Pavel Spirek committed
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


def api_root_handler(prot: "H2Protocol", headers: OrderedDict, stream_id: int):
    # Top level api resource (appendix D.1.1)
    response = (
        "{\n"
        "    \"ietf-restconf:restconf\": {\n"
        "        \"data\" : [ null ],\n"
        "        \"operations\" : [ null ]\n"
        "    }\n"
        "}"
    )
    response_headers = (
        (':status', '200'),
        ('content-type', 'application/yang.api+json'),
        ('content-length', len(response)),
        ('server', CONFIG_HTTP["SERVER_NAME"]),
    )
    prot.conn.send_headers(stream_id, response_headers)
    prot.conn.send_data(stream_id, response.encode(), end_stream=True)


def get(prot: "H2Protocol", headers: OrderedDict, stream_id: int, ds, pth):
    username = CertHelpers.get_field(prot.client_cert, "emailAddress")

    rpc1 = Rpc()
    rpc1.username = username
    rpc1.path = pth

    try:
        ds.lock_data(username)
        n = ds.get_node_rpc(rpc1)
        response = json.dumps(n.value, indent=4)
        http_status = "200"
    except DataLockError as e:
        warn(e.msg)
        response = "Internal Server Error"
        http_status = "500"
    except NacmForbiddenError as e:
        warn(e.msg)
        response = "Forbidden"
        http_status = "403"
    except NonexistentSchemaNode:
        warn("NonexistentSchemaNode: " + pth)
        response = "NonexistentSchemaNode"
        http_status = "404"
    except NonexistentInstance:
        warn("NonexistentInstance: " + pth)
        response = "NonexistentInstance"
        http_status = "404"
Pavel Spirek's avatar
Pavel Spirek committed
64 65 66 67
    except InstanceTypeError:
        warn("InstanceTypeError: " + pth)
        response = "InstanceTypeError"
        http_status = "404"
Pavel Spirek's avatar
Pavel Spirek committed
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
    finally:
        ds.unlock_data()

    response += "\n"
    response_headers = (
        (':status', http_status),
        ('content-type', 'application/yang.api+json'),
        ('content-length', len(response)),
        ('server', CONFIG_HTTP["SERVER_NAME"]),
    )

    prot.conn.send_headers(stream_id, response_headers)
    prot.conn.send_data(stream_id, response.encode(), end_stream=True)


83 84 85 86
def create_get_nacm_api(ds: BaseDatastore):
    def get_nacm_api_closure(prot: "H2Protocol", headers: OrderedDict, stream_id: int):
        # NACM api request
        info(("nacm_api_get: " + headers[":path"]))
Pavel Spirek's avatar
Pavel Spirek committed
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
        url_split = headers[":path"].split("?")
        url_path = url_split[0]
        if len(url_split) > 1:
            query_string = parse_qs(url_split[1])
        else:
            query_string = {}

        username = CertHelpers.get_field(prot.client_cert, "emailAddress")
        if username not in NACM_ADMINS:
            warn(username + " not allowed to access NACM data")
            response = "Forbidden"
            http_status = "403"

            response += "\n"
            response_headers = (
                (':status', http_status),
                ('content-type', 'application/yang.api+json'),
                ('content-length', len(response)),
                ('server', CONFIG_HTTP["SERVER_NAME"]),
            )

            prot.conn.send_headers(stream_id, response_headers)
            prot.conn.send_data(stream_id, response.encode(), end_stream=True)
        else:
Pavel Spirek's avatar
Pavel Spirek committed
112
            pth = url_path[len(NACM_API_ROOT_data):] or "/"
113
            get(prot, headers, stream_id, ds.nacm.nacm_ds, pth)
Pavel Spirek's avatar
Pavel Spirek committed
114

115
    return get_nacm_api_closure
Pavel Spirek's avatar
Pavel Spirek committed
116 117


118 119 120 121
def create_get_api(ds: BaseDatastore):
    def get_api_closure(prot: "H2Protocol", headers: OrderedDict, stream_id: int):
        # api request
        info(("api_get: " + headers[":path"]))
Pavel Spirek's avatar
Pavel Spirek committed
122

123 124 125 126 127 128
        url_split = headers[":path"].split("?")
        url_path = url_split[0]
        if len(url_split) > 1:
            query_string = parse_qs(url_split[1])
        else:
            query_string = {}
Pavel Spirek's avatar
Pavel Spirek committed
129

Pavel Spirek's avatar
Pavel Spirek committed
130
        pth = url_path[len(API_ROOT_data):] or "/"
Pavel Spirek's avatar
Pavel Spirek committed
131

132
        get(prot, headers, stream_id, ds, pth)
Pavel Spirek's avatar
Pavel Spirek committed
133

134
    return get_api_closure
Pavel Spirek's avatar
Pavel Spirek committed
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 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185


def get_file(prot: "H2Protocol", headers: OrderedDict, stream_id: int):
    # Ordinary file on filesystem

    url_split = headers[":path"].split("?")
    url_path = url_split[0]

    file_path = os.path.join(CONFIG_HTTP["DOC_ROOT"], url_path[1:].replace("..", "").replace("&", ""))

    if os.path.isdir(file_path):
        file_path = os.path.join(file_path, CONFIG_HTTP["DOC_DEFAULT_NAME"])

    (ctype, encoding) = mimetypes.guess_type(file_path)
    if ctype is None:
        ctype = "application/octet-stream"
    subtype = ctype.split('/', 1)[1]

    try:
        fd = open(file_path, 'rb')
        response = fd.read()
        fd.close()
    except FileNotFoundError:
        warn("Cannot open requested file \"{}\"".format(file_path))
        response_headers = (
            (':status', '404'),
            ('content-length', 0),
            ('server', CONFIG_HTTP["SERVER_NAME"]),
        )
        prot.conn.send_headers(stream_id, response_headers, end_stream=True)
        return

    info("Serving ordinary file {} of type \"{}\"".format(file_path, ctype))
    response_headers = (
        (':status', '200'),
        ('content-type', ctype),
        ('content-length', len(response)),
        ('server', CONFIG_HTTP["SERVER_NAME"]),
    )
    prot.conn.send_headers(stream_id, response_headers)

    def split_arr(arr, chunk_size):
        for i in range(0, len(arr), chunk_size):
            yield arr[i:i + chunk_size]

    for data_chunk in split_arr(response, prot.conn.max_outbound_frame_size):
        prot.conn.send_data(stream_id, data_chunk, end_stream=False)

    prot.conn.send_data(stream_id, bytes(), end_stream=True)


186 187 188 189
def create_put_post_nacm_api(ds: BaseDatastore):
    def put_post_nacm_api_closure(prot: "H2Protocol", headers: OrderedDict, data: bytes, stream_id: int):
        data_str = data.decode("utf-8")
        info("prijato: " + data_str)
Pavel Spirek's avatar
Pavel Spirek committed
190

191 192 193 194
        url_split = headers[":path"].split("?")
        path = url_split[0]
        if len(url_split) > 1:
            query_string = parse_qs(url_split[1])
Pavel Spirek's avatar
Pavel Spirek committed
195
        else:
196 197 198 199 200 201 202
            query_string = {}

        info(("nacm_api_put: " + path))
        info("qs = {}".format(query_string))

        username = CertHelpers.get_field(prot.client_cert, "emailAddress")

Pavel Spirek's avatar
Pavel Spirek committed
203
        pth = path[len(NACM_API_ROOT_data):]
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229

        rpc1 = Rpc()
        rpc1.username = username
        rpc1.path = pth

        json_data = json.loads(data_str)

        try:
            ds.nacm.nacm_ds.lock_data(username)
            if headers[":method"] == "PUT":
                ds.nacm.nacm_ds.put_node_rpc(rpc1, json_data)
            else:
                ins_pos = (query_string.get("insert") or [None])[0]
                ds.nacm.nacm_ds.create_node_rpc(rpc1, json_data, insert=ins_pos)
            response = "Done\n"
            http_status = "200"
        except DataLockError as e:
            warn(e.msg)
            response = "Internal Server Error"
            http_status = "500"
        except NacmForbiddenError as e:
            warn(e.msg)
            response = "Forbidden"
            http_status = "403"
        except NonexistentSchemaNode:
            warn("NonexistentSchemaNode: " + pth)
Pavel Spirek's avatar
Pavel Spirek committed
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
            response = "NonexistentSchemaNode"
            http_status = "404"
        except NonexistentInstance:
            warn("NonexistentInstance: " + pth)
            response = "NonexistentInstance"
            http_status = "404"
        except InstanceAlreadyPresent:
            warn("InstanceAlreadyPresent: " + pth)
            response = "Conflict"
            http_status = "409"
        finally:
            ds.nacm.nacm_ds.unlock_data()
            ds.nacm.update(ds.nacm.nacm_ds.get_data_root().member("ietf-netconf-acm:nacm"))

        response += "\n"
        response = response.encode()
        response_headers = (
            (':status', http_status),
            ('content-type', 'application/yang.api+json'),
            ('content-length', len(response)),
            ('server', CONFIG_HTTP["SERVER_NAME"]),
        )

        prot.conn.send_headers(stream_id, response_headers)
        prot.conn.send_data(stream_id, response, end_stream=True)

    return put_post_nacm_api_closure


def create_nacm_api_delete(ds: BaseDatastore):
    def put_post_nacm_api_closure(prot: "H2Protocol", headers: OrderedDict, stream_id: int):

        url_split = headers[":path"].split("?")
        path = url_split[0]
        if len(url_split) > 1:
            query_string = parse_qs(url_split[1])
        else:
            query_string = {}

        info(("nacm_api_delete: " + path))
        info("qs = {}".format(query_string))

        username = CertHelpers.get_field(prot.client_cert, "emailAddress")

        pth = path[len(NACM_API_ROOT_data):]

        rpc1 = Rpc()
        rpc1.username = username
        rpc1.path = pth

        try:
            ds.nacm.nacm_ds.lock_data(username)
            ds.nacm.nacm_ds.delete_node_rpc(rpc1)
            response = "No Content"
            http_status = "204"
        except DataLockError as e:
            warn(e.msg)
            response = "Internal Server Error"
            http_status = "500"
        except NacmForbiddenError as e:
            warn(e.msg)
            response = "Forbidden"
            http_status = "403"
        except NonexistentSchemaNode:
            warn("NonexistentSchemaNode: " + pth)
295 296 297 298 299 300 301 302 303 304 305 306 307
            response = "NonexistentSchemaNode"
            http_status = "404"
        except NonexistentInstance:
            warn("NonexistentInstance: " + pth)
            response = "NonexistentInstance"
            http_status = "404"
        except InstanceAlreadyPresent:
            warn("InstanceAlreadyPresent: " + pth)
            response = "Conflict"
            http_status = "409"
        finally:
            ds.nacm.nacm_ds.unlock_data()
            ds.nacm.update(ds.nacm.nacm_ds.get_data_root().member("ietf-netconf-acm:nacm"))
Pavel Spirek's avatar
Pavel Spirek committed
308

309 310 311 312 313 314 315 316
        response += "\n"
        response = response.encode()
        response_headers = (
            (':status', http_status),
            ('content-type', 'application/yang.api+json'),
            ('content-length', len(response)),
            ('server', CONFIG_HTTP["SERVER_NAME"]),
        )
Pavel Spirek's avatar
Pavel Spirek committed
317

318 319 320 321
        prot.conn.send_headers(stream_id, response_headers)
        prot.conn.send_data(stream_id, response, end_stream=True)

    return put_post_nacm_api_closure