• Welcome to the Chevereto user community!

    Here users from all over the world gather around to learn the latest about Chevereto and contribute with ideas to improve the software.

    Please keep in mind:

    • This community is user driven. Be polite with other users.
    • We recommend purchasing a Chevereto license to participate in this community.
    • Purchase a Community Subscription to get even faster ticket response times.
  • Chevereto Support CLST

    Support response

    Support checklist

    • Got a Something went wrong message? Read this guide and provide the actual error. Do not skip this.
    • Confirm that the server meets the System Requirements
    • Check for any available Hotfix - your issue could be already reported/fixed
    • Read documentation - It will be required to Debug and understand Errors for a faster support response

API doesn't work when `guest_uploads` are disabled

bjoern.busch

Chevereto Member
I have updated to 4.2. today in the hope that this would get me around debugging, but now I feel like upload via API is broken, I'm always getting back "Request denied" for both images and videos. If I interpret it correctly, the user isn't determined correctly from the provided API key. I see that here the upload_allowed is being set to false:

https://github.com/chevereto/chevereto/blob/4.2/app/legacy/load/web.php#L557

So the user isn't set. I tried the call via curl and I'm getting the same result:

curl --fail-with-body -X POST \
-H "X-API-Key: chv_SuperSecretKey" \
-H "Content-Type: application/x-www-form-urlencoded" \
https://mygallery.com/api/1/upload?source=https://upload.wikimedia.org/wikipedia/commons/4/41/Sunflower_from_Silesia2.jpg

The key is not the guest key, but from a user account. I recreated it, just in case I messed something up, but no difference.

Any chance that you / someone can give this a try on their instance?
 
Last edited:
Hello, thanks for your concise report on this. I can confirm that the code you mentioned is limiting the API functionality because it restricts the upload process before correctly identifying the user via the API key. I've made some changes to the code that will allow the API to reset these variables based on the detected user. Please give it a try and let me know how it goes!

app/legacy/routes/api.php

PHP:
<?php
/*
 * This file is part of Chevereto.
 *
 * (c) Rodolfo Berrios <rodolfo@chevereto.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
/*
 * This API V1 was introduced in Chevereto V2 and it was carried over to V3.
 *
 * From Chevereto V4 onwards the API versioning follow the major version:
 *
 * - V2 -> API V1
 * - V3 -> API V1
 * - V4 -> API V4 + API V1.1
 */
use Chevereto\Legacy\Classes\Akismet;
use Chevereto\Legacy\Classes\ApiKey;
use Chevereto\Legacy\Classes\Image;
use Chevereto\Legacy\Classes\Settings;
use Chevereto\Legacy\Classes\Upload;
use Chevereto\Legacy\Classes\User;
use Chevereto\Legacy\G\Handler;
use function Chevereto\Legacy\decodeID;
use function Chevereto\Legacy\encodeID;
use function Chevereto\Legacy\G\get_mimetype;
use function Chevereto\Legacy\G\getQsParams;
use function Chevereto\Legacy\G\is_image_url;
use function Chevereto\Legacy\G\is_url;
use function Chevereto\Legacy\G\json_document_output;
use function Chevereto\Legacy\G\json_error;
use function Chevereto\Legacy\G\mime_to_extension;
use function Chevereto\Legacy\G\random_string;
use function Chevereto\Legacy\getSetting;
use function Chevereto\Legacy\getVariable;
use function Chevereto\Vars\env;
use function Chevereto\Vars\files;
use function Chevereto\Vars\request;
use function Chevereto\Vars\server;
return function (Handler $handler) {
    try {
        $user = [];
        $REQUEST = request();
        $FILES = files();
        $SERVER = server();
        $format = $REQUEST['format'] ?? 'json';
        $version = strval($handler->request()[0] ?? '');
        $action = strval($handler->request()[1] ?? '');
        $key = strval($SERVER['HTTP_X_API_KEY'] ?? $REQUEST['key'] ?? '');
        foreach (['version', 'action', 'key'] as $var) {
            if (${$var} === '') {
                throw new Exception("No {$var} provided", 100);
            }
        }
        if (! in_array($version, ['1'], true)) {
            throw new Exception('Invalid API version.', 110);
        }
        $verify = ApiKey::verify($key);
        if ($verify === []) {
            if (! (bool) env()['CHEVERETO_ENABLE_API_GUEST']) {
                throw new Exception('Invalid API key.', 100);
            }
            $apiV1Key = (string) (getSetting('api_v1_key') ?? '');
            if ($apiV1Key === '') {
                throw new Exception("API V1 public key can't be null. Go to /dashboard and set the Guest API key.", 0);
            }
            if (! hash_equals($apiV1Key, $key)) {
                throw new Exception('Invalid API key.', 100);
            }
        } else {
            $user = User::getSingle($verify['user_id']);
        }
        $isAdmin = boolval(($user['is_admin'] ?? false));
        $upload_enabled = $isAdmin ?: getSetting('enable_uploads');
        $upload_allowed = $upload_enabled;
        if ($user === []) {
            if (! getSetting('guest_uploads')
                || getSetting('website_privacy_mode') === 'private'
                || $handler::cond('maintenance')
            ) {
                $upload_allowed = false;
            }
        } elseif (! $isAdmin
            && getSetting('website_mode') === 'personal'
            && getSetting('website_mode_personal_uid') !== ($user['id'] ?? 0)
        ) {
            $upload_allowed = false;
        }
        if ((! (bool) env()['CHEVERETO_ENABLE_LOCAL_STORAGE'])
            && getVariable('storages_active')->nullInt() === 0
        ) {
            $upload_enabled = false;
            $upload_allowed = false;
        }
        if ($user === []
            && $upload_allowed
            && getSetting('upload_max_filesize_mb_guest')
        ) {
            Settings::setValue('upload_max_filesize_mb_bak', getSetting('upload_max_filesize_mb'));
            Settings::setValue('upload_max_filesize_mb', getSetting('upload_max_filesize_mb_guest'));
        }
        $handler::setCond('upload_enabled', $upload_enabled);
        $handler::setCond('upload_allowed', $upload_allowed);
        if (Settings::get('enable_uploads_url') && ! $isAdmin) {
            Settings::setValue('enable_uploads_url', 0);
        }
        if (! $handler::cond('upload_allowed')) {
            throw new Exception(_s('Request denied'), 401);
        }
        $version_to_actions = [
            '1' => ['upload'],
        ];
        if (! in_array($action, $version_to_actions[$version], true)) {
            throw new Exception('Invalid API action', 120);
        }
        $source = $FILES['source']
            ?? $REQUEST['source']
            ?? $REQUEST['image']
            ?? null;
        if ($source === null) {
            throw new Exception('Empty upload source', 130);
        }
        switch (true) {
            case isset($FILES['source'], $FILES['source']['tmp_name']):
                $source = $FILES['source'];
                break;
            case is_image_url($source) || is_url($source):
                if (($SERVER['REQUEST_METHOD'] ?? '') === 'GET') {
                    $sourceQs = urldecode(getQsParams()['source']);
                }
                $source = $sourceQs ?? $source;
                break;
            default:
                if (($SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
                    throw new Exception('Upload using base64 source must be done using POST method.', 130);
                }
                $source = trim(preg_replace('/\s+/', '', $source));
                $base64source = base64_encode(base64_decode($source, true));
                if (! hash_equals($base64source, $source)) {
                    throw new Exception('Invalid base64 string', 120);
                }
                if ($source === '') {
                    throw new Exception('Empty source', 130);
                }
                try {
                    $api_temp_file = Upload::getTempNam();
                } catch (Exception $e) {
                    throw new Exception("Can't get a tempnam", 200);
                }
                $fh = fopen($api_temp_file, 'w');
                stream_filter_append($fh, 'convert.base64-decode', STREAM_FILTER_WRITE);
                fwrite($fh, $source);
                fclose($fh);
                $mimetype = get_mimetype($api_temp_file);
                $source = [
                    'name' => random_string(12) . '.' . mime_to_extension($mimetype),
                    'type' => $mimetype,
                    'tmp_name' => $api_temp_file,
                    'error' => 'UPLOAD_ERR_OK',
                    'size' => filesize($api_temp_file),
                ];
                break;
        }
        $isImgBBSpec = array_key_exists('image', $REQUEST);
        $albumId = $REQUEST['album_id'] ?? null;
        if ($albumId !== null) {
            $albumId = decodeID($albumId);
        }
        $expiration = $REQUEST['expiration'] ?? null;
        if ($expiration !== null && ctype_digit($expiration)) {
            $expiration = (int) $expiration;
        }
        $params = [
            'album_id' => $albumId,
            'category_id' => $REQUEST['category_id'] ?? null,
            'description' => $REQUEST['description'] ?? null,
            'nsfw' => $REQUEST['nsfw'] ?? null,
            'title' => $REQUEST['title'] ?? $REQUEST['name'] ?? null,
            'tags' => $REQUEST['tags'] ?? null,
            'width' => $REQUEST['width'] ?? null,
            'expiration' => $expiration,
        ];
        $params = array_filter($params);
        if (! $handler::cond('content_manager') && getSetting('akismet')) {
            $user_source_db = [
                'user_name' => $user['name'] ?? null,
                'user_username' => $user['username'] ?? null,
                'user_email' => $user['email'] ?? null,
            ];
            Akismet::checkImage($params['title'] ?? null, $params['description'] ?? null, $user_source_db);
        }
        $uploadToWebsite = Image::uploadToWebsite($source, $user, $params);
        $uploaded_id = intval($uploadToWebsite[0]);
        $image = Image::formatArray(Image::getSingle($uploaded_id), true);
        $image['delete_url'] = Image::getDeleteUrl(
            type: $image['type'],
            idEncoded: encodeID($uploaded_id),
            password: $uploadToWebsite[1]
        );
        unset($image['user'], $image['album']);
        if (! $image['is_approved']) {
            unset($image['image']['url'], $image['thumb']['url'], $image['medium']['url'], $image['url'], $image['display_url']);
        }
        $json_array = [];
        $json_array['status_code'] = 200;
        if ($isImgBBSpec) {
            $json_array['status'] = $json_array['status_code'];
            $image['id'] = $image['id_encoded'];
        }
        $json_array['success'] = [
            'message' => 'file uploaded',
            'code' => 200,
        ];
        $json_array[$isImgBBSpec ? 'data' : 'image'] = $image;
        if ($version === '1') {
            switch ($format) {
                default:
                case 'json':
                    json_document_output($json_array);
                    break;
                case 'txt':
                    echo $image['url'];
                    break;
                case 'redirect':
                    if ($json_array['status_code'] === 200) {
                        $redirect_url = $image['path_viewer'];
                        header("Location: {$redirect_url}");
                    } else {
                        exit($json_array['status_code']);
                    }
                    break;
            }
            exit();
        }
        json_document_output($json_array);
    } catch (Exception $e) {
        $json_array = json_error($e);
        if ($version === '1') {
            switch ($format) {
                default:
                case 'json':
                    json_document_output($json_array);
                    break;
                case 'txt':
                case 'redirect':
                    exit($json_array['error']['message']);
            }
        } else {
            json_document_output($json_array);
        }
    }
};
 
ok, the mapping of the API Key is now solved, but I think there is a subsequent issue in https://github.com/chevereto/chevereto/blob/4.2/app/src/Legacy/Classes/Image.php#L675

When I call the API like this:
Code:
curl --location --request POST 'https://mygallery.com/api/1/upload?source=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2F4%2F41%2FSunflower_from_Silesia2.jpg' \
--header 'X-API-Key: chv_neU_SuperSecretKey'

The API Key is properly extracted and mapped to the user, the source parameter is found and it tries to call fetch_url (https://github.com/chevereto/chevereto/blob/4.2/app/src/Legacy/Classes/Image.php#L725) with:

Code:
file: /var/www/html/images/2024/10/30/
url: https://upload.wikimedia.org/wikipedia/commons/4/41/Sunflower_from_Silesia2.jpg

The method then fails somewhere with fopen(\/var\/www\/html\/images\/2024\/10\/30\/): Failed to open stream: Is a directory. I tried creating the folder manually, but that doesn't help. I'm still debugging, but maybe you see the issue immediately.
 
Awesome, I think we have it now! I could now successfully upload a jpg and a mp4 via the API, without specifying a mimetype in the call, but it was automatically detected. Time to pop the champagne!
 
Back
Top