Exemple #1
0
def account_bot_crud(account_id, user_id):
    if flask.request.method in ['GET', 'HEAD']:
        rec = Bot.get(user_id, account_id)
        if not rec:
            return "No such bot", 404
        return json.dumps(rec), 200

    elif flask.request.method == 'PUT':
        bot = Bot.forge_from_input(flask.request.get_json(),
                                   force_account=account_id,
                                   force_id=user_id)
        rowcount = bot.update()
        if not rowcount:
            return "No such bot", 404

        mqtt_publish_changed([
            'accounts/{account_id}/bots'.format(account_id=account_id),
            'accounts/{account_id}/bots/{bot_id}'.format(account_id=account_id,
                                                         bot_id=user_id),
        ])
        return "", 204

    elif flask.request.method == 'DELETE':
        # bot should not be able to delete himself, otherwise they could lock themselves out:
        if int(flask.g.grafolean_data['user_id']) == int(user_id):
            return "Can't delete yourself", 403
        rowcount = Bot.delete(user_id, force_account=account_id)
        if not rowcount:
            return "No such bot", 404
        mqtt_publish_changed([
            'accounts/{account_id}/bots'.format(account_id=account_id),
            'accounts/{account_id}/bots/{bot_id}'.format(account_id=account_id,
                                                         bot_id=user_id),
        ])
        return "", 204
Exemple #2
0
def users_bots():
    """
        ---
        get:
          summary: Get systemwide bots
          tags:
            - Users
          description:
            Returns a list of all systemwide bots (bots which are not tied to a specific account). The list is returned in a single array (no pagination).
          responses:
            200:
              content:
                application/json:
                  schema:
                    type: object
                    properties:
                      list:
                        type: array
                        items:
                          "$ref": '#/definitions/BotGET'
        post:
          summary: Create a systemwide bot
          tags:
            - Users
          description:
            Creates a systemwide bot. By default, a created bot is without permissions, so they must be granted to it before it can do anything useful.

          parameters:
            - name: "body"
              in: body
              description: "Bot data"
              required: true
              schema:
                "$ref": '#/definitions/BotPOST'
          responses:
            201:
              content:
                application/json:
                  schema:
                    "$ref": '#/definitions/BotGET'
    """
    if flask.request.method in ['GET', 'HEAD']:
        rec = Bot.get_list()
        return json.dumps({'list': rec}), 200

    elif flask.request.method == 'POST':
        bot = Bot.forge_from_input(flask.request.get_json())
        user_id, _ = bot.insert()
        rec = Bot.get(user_id, None)
        mqtt_publish_changed([
            'bots',
        ])
        return json.dumps(rec), 201
Exemple #3
0
def account_bot_permissions(account_id, user_id):
    """
        Allows reading and assigning permissions to account bots (bots which are tied to a specific account).
    """
    # make sure the bot really belongs to the account:
    rec = Bot.get(user_id, account_id)
    if not rec:
        return "No such bot", 404

    if flask.request.method in ['GET', 'HEAD']:
        rec = Permission.get_list(user_id)
        return json.dumps({'list': rec}), 200

    elif flask.request.method == 'POST':
        granting_user_id = flask.g.grafolean_data['user_id']
        permission = Permission.forge_from_input(flask.request.get_json(),
                                                 user_id)
        try:
            permission_id = permission.insert(granting_user_id)
            mqtt_publish_changed([
                'persons/{}'.format(permission.user_id),
                'bots/{}'.format(permission.user_id),
            ])
            return json.dumps({
                'user_id': permission.user_id,
                'resource_prefix': permission.resource_prefix,
                'methods': permission.methods,
                'id': permission_id,
            }), 201
        except AccessDeniedError as ex:
            return str(ex), 401
        except psycopg2.IntegrityError:
            return "Invalid parameters", 400
Exemple #4
0
def account_bots(account_id):
    if flask.request.method in ['GET', 'HEAD']:
        rec = Bot.get_list(account_id)
        return json.dumps({'list': rec}), 200

    elif flask.request.method == 'POST':
        bot = Bot.forge_from_input(flask.request.get_json(),
                                   force_account=account_id)
        user_id, _ = bot.insert()
        rec = Bot.get(user_id, account_id)
        mqtt_publish_changed([
            'accounts/{account_id}/bots'.format(account_id=account_id),
            'accounts/{account_id}/bots/{bot_id}'.format(account_id=account_id,
                                                         bot_id=user_id),
        ])
        return json.dumps(rec), 201
Exemple #5
0
def admin_first_post():
    """
        ---
        post:
          summary: Create first admin user
          tags:
            - Admin
          description:
            This endpoint helps with setting up a new installation. It allows us to set up just one initial
            admin access (with name, email and password). Later requests to the same endpoint will fail.
            At the same time a systemwide (ICMP ping) bot is configured and its token shared via file with
            a grafolean-ping-bot Docker container (in default setup).
          parameters:
            - name: "body"
              in: body
              description: "First admin data and credentials"
              required: true
              schema:
                "$ref": '#/definitions/PersonPOST'
          responses:
            201:
              content:
                application/json:
                  schema:
                    type: object
                    properties:
                      id:
                        type: integer
                        description: "User id of created admin"
            401:
              description: System already initialized
    """
    if Auth.first_user_exists():
        return 'System already initialized', 401
    admin = Person.forge_from_input(flask.request.get_json())
    admin_id = admin.insert()
    # make it a superuser:
    permission = Permission(admin_id, None, None)
    permission.insert(None, skip_checks=True)

    # Help users by including a systemwide ping bot in the package by default:
    Bot.ensure_default_systemwide_bots_exist()

    mqtt_publish_changed(['status/info'])
    return json.dumps({
        'id': admin_id,
    }), 201
Exemple #6
0
def profile():
    user_id = flask.g.grafolean_data['user_id']
    user_is_bot = flask.g.grafolean_data['user_is_bot']
    if user_is_bot:
        tied_to_account = Bot.get_tied_to_account(user_id)
        return json.dumps({
            'user_id':
            user_id,
            'user_type':
            'bot',
            'record':
            Bot.get(user_id, tied_to_account=tied_to_account),
        }), 200
    else:
        return json.dumps({
            'user_id': user_id,
            'user_type': 'person',
            'record': Person.get(user_id),
        }), 200
Exemple #7
0
def users_bot_token_get(user_id):
    # make sure the user who is requesting to see the bot token has every permission that this token has, and
    # also that this user can add the bot:
    request_user_permissions = Permission.get_list(
        int(flask.g.grafolean_data['user_id']))
    if not Permission.has_all_permissions(request_user_permissions, user_id):
        return "Not enough permissions to see this bot's token", 401
    if not Permission.can_grant_permission(request_user_permissions, 'bots',
                                           'POST'):
        return "Not enough permissions to see this bot's token - POST to /bots not allowed", 401
    token = Bot.get_token(user_id, None)
    if not token:
        return "No such bot", 404
    return {'token': token}, 200
Exemple #8
0
def account_bot_permission_delete(account_id, user_id, permission_id):
    """ Revoke permission from account bot """
    # make sure the bot really belongs to the account:
    rec = Bot.get(user_id, account_id)
    if not rec:
        return "No such bot", 404

    granting_user_id = flask.g.grafolean_data['user_id']
    try:
        rowcount = Permission.delete(permission_id, user_id, granting_user_id)
    except AccessDeniedError as ex:
        return str(ex), 401
    if not rowcount:
        return "No such permission", 404
    mqtt_publish_changed([
        'persons/{user_id}'.format(user_id=user_id),
        'bots/{user_id}'.format(user_id=user_id),
    ])
    return "", 204
Exemple #9
0
def users_bot_crud(user_id):
    """
        ---
        get:
          summary: Get bot data
          tags:
            - Users
          description:
            Returns bot data.
          parameters:
            - name: user_id
              in: path
              description: "User id"
              required: true
              schema:
                type: integer
          responses:
            200:
              content:
                application/json:
                  schema:
                    "$ref": '#/definitions/BotGET'
            404:
              description: No such bot
        put:
          summary: Update the bot
          tags:
            - Users
          description:
            Updates bot name. Note that all other fields are handled automatically (they can't be changed).
          parameters:
            - name: user_id
              in: path
              description: "User id"
              required: true
              schema:
                type: integer
            - name: "body"
              in: body
              description: "Bot data"
              required: true
              schema:
                "$ref": '#/definitions/BotPOST'
          responses:
            204:
              description: Update successful
            404:
              description: No such bot
        delete:
          summary: Remove the bot
          tags:
            - Users
          description:
            Removes the bot. Also removes its permissions, if any.
          parameters:
            - name: user_id
              in: path
              description: "User id"
              required: true
              schema:
                type: integer
          responses:
            204:
              description: Bot removed successfully
            403:
              description: Can't remove yourself
            404:
              description: No such bot
    """
    if flask.request.method in ['GET', 'HEAD']:
        rec = Bot.get(user_id, None)
        if not rec:
            return "No such bot", 404
        return json.dumps(rec), 200

    elif flask.request.method == 'PUT':
        bot = Bot.forge_from_input(flask.request.get_json(), force_id=user_id)
        rowcount = bot.update()
        if not rowcount:
            return "No such bot", 404
        mqtt_publish_changed([
            'bots/{user_id}'.format(user_id=user_id),
            'bots',
        ])
        return "", 204

    elif flask.request.method == 'DELETE':
        # bot should not be able to delete himself, otherwise they could lock themselves out:
        if int(flask.g.grafolean_data['user_id']) == int(user_id):
            return "Can't delete yourself", 403
        rowcount = Bot.delete(user_id)
        if not rowcount:
            return "No such bot", 404
        mqtt_publish_changed([
            'bots/{user_id}'.format(user_id=user_id),
            'bots',
        ])
        return "", 204
Exemple #10
0
def before_request():

    # http://flask.pocoo.org/docs/1.0/api/#application-globals
    flask.g.grafolean_data = {}

    if not flask.request.endpoint in app.view_functions:
        # Calling /api/admin/migratedb with GET (instead of POST) is a common mistake, so it deserves a warning in the log:
        if flask.request.path == '/api/admin/migratedb' and flask.request.method == 'GET':
            log.warning("Did you want to use POST instead of GET?")
        return "Resource not found", 404

    # Browser might (if frontend and backend are not on the same origin) send a pre-flight OPTIONS request to get the
    # CORS settings. In this case 'Authorization' header will not be set, which could lead to 401 response, which browser
    # doesn't like. So let's just return 200 on all OPTIONS:
    if flask.request.method == 'OPTIONS':
        # we need to set 'Allow' header to notify caller which methods are available:
        methods = set()
        for rule in app.url_map.iter_rules():
            if flask.request.url_rule == rule:
                methods |= rule.methods
        response = flask.make_response('', 200)
        response.headers['Allow'] = ",".join(sorted(methods))
        return response

    if flask.request.method in ['GET', 'HEAD', 'POST']:
        # While it is true that CORS is client-side protection, the rules about preflights allow these 3 types of requests
        # to be sent to the server without OPTIONS preflight - which means that browser will learn about violation too late.
        # To combat this, we still check Origin header and explicitly deny non-whitelisted requests:
        origin_header = flask.request.headers.get('Origin', None)
        if origin_header:  # is it a cross-origin request?
            # still, we sometimes get origin header even if it is not a cross-origin request, so let's double check that we
            # indeed are doing CORS:
            if flask.request.url_root.rstrip('/') != origin_header:
                if origin_header not in CORS_DOMAINS and flask.request.path != '/api/status/info':  # this path is an exception
                    return 'CORS not allowed for this origin', 403

    if dbutils.db is None:
        dbutils.db_connect()
        if dbutils.db is None:
            # oops, DB error... we should return 5xx:
            return 'Service unavailable', 503

    view_func = app.view_functions[flask.request.endpoint]
    # unless we have explicitly used @noauth decorator, do authorization check here:
    if not hasattr(view_func, '_noauth'):
        try:
            user_id = None
            user_is_bot = False
            authorization_header = flask.request.headers.get('Authorization')
            query_params_bot_token = flask.request.args.get('b')
            if authorization_header is not None:
                received_jwt = JWT.forge_from_authorization_header(authorization_header, allow_leeway=0)
                flask.g.grafolean_data['jwt'] = received_jwt
                user_id = received_jwt.data['user_id']
            elif query_params_bot_token is not None:
                user_id = Bot.authenticate_token(query_params_bot_token)
                user_is_bot = True

            if user_id is None:
                log.info("Authentication failed (no such user)")
                return "Access denied", 401

            # check permissions:
            resource = flask.request.path[len('/api/'):]
            resource = resource.rstrip('/')
            is_allowed = Permission.is_access_allowed(
                user_id=user_id,
                resource=resource,
                method=flask.request.method,
            )
            if not is_allowed:
                log.info("Access denied (permissions check failed) {} {} {}".format(user_id, resource, flask.request.method))
                return "Access to resource denied, insufficient permissions", 403

            flask.g.grafolean_data['user_id'] = user_id
            flask.g.grafolean_data['user_is_bot'] = user_is_bot
        except AuthFailedException as ex:
            log.info(f"Authentication failed: {str(ex)}")
            return "Access denied", 401
        except:
            log.exception("Exception while checking access rights")
            return "Could not validate access", 500