Laravel Minio Temporary URL - laravel

I'm looking for a hacky way to create temporary URLs with Minio
I see on the Laravel docs it says: Generating temporary storage URLs via the temporaryUrl method is not supported when using MinIO.
However from some digging I noticed that I can upload images successfully using:
AWS_ENDPOINT=http://minio:9000
I can't view them because the temporary url is on http://minio:9000/xxx
If I change the AWS endpoint to
AWS_ENDPOINT=http://localhost:9000
The temporary url is on http://localhost:9000/xxx, the signature is validated and the file can be viewed.
The issue exists in this call to make the command. The $command needs to have the host changed but I don't know if I can do that by just passing in an option.
$command = $this->client->getCommand('GetObject', array_merge([
'Bucket' => $this->config['bucket'],
'Key' => $this->prefixer->prefixPath($path),
], $options));
There is also the option to just change the baseUrl by providing a temporary_url in the filesystem config. however, because the URL has changed the signature is invalid.
Is there a way I can update the S3Client to use a different host either by passing an option to the getCommand function or by passing a new S3Client to the AWS adapter to use the correct host?

A very hacky solution I've found is to re-create the AwsS3Adatapter:
if (is_development()) {
$manager = app()->make(FilesystemManager::class);
$adapter = $manager->createS3Driver([
...config("filesystems.disks.s3_private"),
"endpoint" => "http://localhost:9000",
]);
return $adapter->temporaryUrl(
$this->getPathRelativeToRoot(),
now()->addMinutes(30)
);
}

Related

Using Laravel Test 7 and Laravel Passport 9.3 with Personal Access Client gives exception "Trying to get property 'id' of non-object"

I am designing a custom authentication scheme (based on public keys) alongside a stateless API, and decided Passport would fulfill the need for post-authentication requests.
Assuming the authentication succeeds, and the user is authenticated, they would receive a Personal Access Token, and use the token for all further requests. The trouble I'm experiencing (still after much searching through various forums and Stack Overflow) is that when using Laravel's built in testing suite, on the createToken() method, it generates an (admittedly common) exception:
"ErrorException : Trying to get property 'id' of non-object".
I am able to manually create a user through Tinker, and create a token through Tinker. However I'm experiencing problems when attempting to automate this process after authenticating.
Here is the relevant code snippet post-authentication:
Auth::login($user);
$user = Auth::user();
$tokenResult = $user->createToken('Personal Access Token');
$token = $tokenResult->token;
$token->expires_at = Carbon::now()->addWeeks(1);
$token->save();
return response()->json([
"access_token" => $tokenResult->accessToken,
"token_type" => "Bearer",
"expires_at" => Carbon::parse(
$tokenResult->token->expires_at)->toDateTimeString()
],
200);
I've manually called Auth::login on the user, to ensure the user is logged in, and Auth::user() returns the user (not null). Upon executing the third line of code, the exception is thrown with the following mini stack-trace (I can provide a full stack-trace if requested).
laravel\passport\src\PersonalAccessTokenFactory.php:100
laravel\passport\src\PersonalAccessTokenFactory.php:71
laravel\passport\src\HasApiTokens.php:67
app\Http\Controllers\Auth\LoginController.php:97
laravel\framework\src\Illuminate\Routing\Controller.php:54
laravel\framework\src\Illuminate\Routing\ControllerDispatcher.php:45
From running this through debug a few times- even though the class is called and loaded, and it appears the Client is found through ControllerDispatcher -> Client::find(id) and found in ClientRepository, when it gets to PersonalAccessTokenFactory, the $client passed in is null (which explains why the $client->id can't be found, though I have no idea why the $client is null at this point).
protected function createRequest($client, $userId, array $scopes)
{
$secret = Passport::$hashesClientSecrets ? Passport::$personalAccessClientSecret : $client->secret;
return (new ServerRequest)->withParsedBody([
'grant_type' => 'personal_access',
'client_id' => $client->id,
...
}
Things I have done/tried with some guidance from the documentation and other posts:
Manually created a user in Tinker, and created the token through Tinker- this does work.
Ensured the user is logged in before attempting to generate token.
passport:install (and adding the --force option)
Ensured Personal Access Client is generated with passport:client --personal
Ensured the AuthServiceProvider::boot() contains the ClientID and Client Secret (in the .env).
migrate:refresh followed by passport:install --force
Complete removal of Passport, removing all files, keys, migrations, and DB entries, followed with a migrate:refresh and reinstallation of Passport, along with generating an additional personal access client (even though one is generated during passport:install).
I'm not sure where else to look/what else to try at this point, so any help or guidance would be much appreciated!
I eventually discovered the solution. The problem is multi-layered, in part having to do with outdated Laravel documentation in regards to testing and Passport Personal Access Clients.
The first part of the problem had to do with using the trait RefreshDatabase on my unit test. Since this creates a mock database with empty datasets, although the clients themselves exist in the real database and the .env file, when the test is run, the test does not see those clients as existing in the mock database. To solve this problem, you must create a client in the setup function before the test is run.
public function setUp() : void
{
parent::setUp();
$this->createClient(); //Private method->Full code below
}
This solves the issue about having a null client during testing, but starting in Laravel 7, Laravel added a requirement for Personal Access Clients that the id and the client secret has to be kept inside the .env file. When running the test, the test will see the actual client id and secret in the .env, and fail to validate these with the client that was created and stored in the mock database, returning another exception: "Client Authentication Failed".
The solution to this problem is to create a .env.testing file in your main project directory, copying your .env file contents to it and ensuring that the keys below exist with values for either your main created Personal Access Client, or copying the secret from a client generated just for testing (I would advise the latter).
PASSPORT_PERSONAL_ACCESS_CLIENT_ID=1
PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET=unhashed-client-secret-value
Then using the code below, make sure the $clientSecret value is the same as the key value in your .env.testing file.
private function createClient() : void
{
$clientRepository = new ClientRepository();
$client = $clientRepository->createPersonalAccessClient(
null, 'Test Personal Access Client', 'http://localhost'
);
DB::table('oauth_personal_access_clients')->insert([
'client_id' => $client->id,
'created_at' => new DateTime,
'updated_at' => new DateTime,
]);
$clientSecret = 'unhashed-client-secret-value';
$client->setSecretAttribute($clientSecret);
$client->save();
}
This will create a new client, set the attribute secret to the value in the variable and update the mock database secret to contain the same value. Hopefully this helps anyone with the same issue.
Another way to prevent copy/paste source code is to just call artisan command in the setup method.
public function setUp() {
parent::setUp();
$this->artisan('passport:install');
}
original here
Just use the facade
public function setUp() {
parent::setUp();
Artisan::call('passport:install');}

Php laravel Upload file directly to AWS S3 bucket

Can anyone help me how to upload a file into aws S3 bucket using PHP laravel. But the file should directly get uploaded into S3 using pre signed URL.
I will try to answer this question. So, there are two ways to do this:
You send the pre-signed URL to Frontend Client and let them upload the file to S3 directly, and once uploaded they notify your server of the same.
You receive the file directly on the server and upload it to S3, in this case, you won't need any pre-signed URL, as you would have already configured the AWS access inside the project.
Since solution 1 is self-explanatory, I will try to explain the solution 2.
Laravel provides Storage Facade for handling filesystem operations. It follows the philosophy of multiple drivers - Public, Local Disk, Amazon S3, FTP plus option of extending the driver.
Step 1: Configure your .env file with AWS keys, you will need the following values to start using Amazon S3 as the driver:
AWS Key
AWS Secret
AWS Bucket Name
AWS Bucket Region
Step 2: Assuming that you already have the file uploaded to your server. We will now upload the file to S3 from our server.
If you have mentioned s3 as the default disk, following snippet will do the upload for you:
Storage::put('avatars/1', $fileContents);
If you are using multiple disks, you can upload the file by:
Storage::disk('s3')->put('avatars/1', $fileContents);
We are done! Your file is now uploaded to your S3 bucket. Double-check it inside you S3 bucket.
If you wish to learn more about Laravel Storage, click here.
use Storage;
use Config;
$client = Storage::disk('s3')->getDriver()->getAdapter()->getClient();
$bucket = Config::get('filesystems.disks.s3.bucket');
$command = $client->getCommand('PutObject', [
'Bucket' => $bucket,
'Key' => '344772707_360.mp4' // file name in s3 bucket which you want to access
]);
$request = $client->createPresignedRequest($command, '+20 minutes');
// Get the actual presigned-url
return $presignedUrl = (string)$request->getUri();
We can use 'PutObject' to generate a signed-url for uploading files onto S3.
Make sure this package is insalled:
composer require league/flysystem-aws-s3-v3 "^1.0"
Create access credentials on AWS and set these variables in .env file
AWS_ACCESS_KEY_ID=ORJATNRFO7SDSMJESWMW
AWS_SECRET_ACCESS_KEY=xnzuPuatfZu09103/BXorsO4H/xxxxxxxxxx
AWS_DEFAULT_REGION=ap-south-1
AWS_BUCKET=xxxxxxx
AWS_URL=http://xxxxx.s3.ap-south-1.amazonaws.com/
public function uploadToS3(Request $request)
{
$file = $request->file('file');
\Storage::disk('s3')->put(
'path/in/s3/filename.jpg',
file_get_contents($file->getRealPath())
);
}
Create credentials here:

Laravel download from google drive (filesystem) or (hide api key from link)

First way to download:
https://www.googleapis.com/drive/v3/files/1YIH4zfM0P1xa-_mfZNGiIY8qZrIEt-rF/?key=API_KEY&alt=media
Putting my API KEY in the link it gives me ability to download the file directly from my google drive.But I don't want to give my API_KEY to the users.
2.I can also access my drive another way: (using nao-pon/flysystem-google-drive)
Route::get('/download/{rest?}', function ($rest) {
$metadata = Storage::cloud()->getMetadata($rest);
$readStream = Storage::cloud()->getDriver()->readStream($rest);
return response()->stream(function () use ($readStream) {
fpassthru($readStream);
}, 200, [
'Content-Type' => $metadata['mimetype'],
'Content-disposition' => 'attachment; filename="'.$metadata['name'].'"', // force download?
]);
})->where('rest','(.*)');
This way I don't have to use api_key but the server has to download the whole file it's a stream but still that uses server resources.
On the other hand https://www.googleapis.com/drive/v3/files/1YIH4zfM0P1xa-_mfZNGiIY8qZrIEt-rF/?key=API_KEY&alt=media needs Content-Type and File name as it doesn't have on it.And also I have no idea how to hide the api key.
So what do you suggest.Is there any other way not to response()->stream to not download the whole file by stream through server and then send it to the user?Multiple users downloading the files would use all the bandwidth,so the download speed drops so fast.

How to retrieve files from S3 in Laravel Vapor

I'm having a problem loading images in my html dynamically after storing them successfully with Laravel Vapor.
I have followed this documentation provided by laravel vapor to store files, and it works like a charm. I copy my uploaded files from the tmp directory into the root of my S3 bucket and then store the path of that file in my databases images table so that later I can return the file path to my front end and display the image in my browser.
Unfortunately this is always returning a 403 status code from AWS S3.
I could fix this by making my generated S3 bucket public, but that would raise a security issue. I believe this should work out of the box, not sure where I could have gone wrong... any ideas?
I am returning the uploaded image url using the Storage facade.
use Illuminate\Support\Facades\Storage;
return Storage::url($image->path);
Where $image->path is the file path in my S3 bucket.
I'm sure that the storage facade is working correctly because it is returning the correct url with the file's path.
I got the solution to this problem. I contacted laravel vapor support and I was told to set the visibility property for my file to public when I copy it to the permanent location, as stated in Laravel's official documentation here.
So after you upload your file using the js vapor.store method you should copy it to a permanent directory, then set it's visibility to public.
Storage::copy($request->path, str_replace('tmp/', '', $request->path));
Storage::setVisibility(str_replace('tmp/', '', $request->path), 'public');
I also noticed that your can set the visibility of the file directly in the vapor.store method by passing a visibility attribute with the respective value.
vapor.store(file, { visibility: 'public-read' });
As a side note: just 'public' will return a 400 bad request, it must be set to 'public-read'.

How to control access to files at another server in Laravel

I have a host for my Laravel website and another (non-laravel) for stored files. Direct access to my files are blocked completely by default and I want to control access to them by creating temporary links in my Laravel site. I know how to code, just want to know the idea of how to do it (not details).
From the Laravel docs
Temporary URLs For files stored using the s3 or rackspace driver, you
may create a temporary URL to a given file using the temporaryUrl
method. This methods accepts a path and a DateTime instance specifying
when the URL should expire:
$url = Storage::temporaryUrl(
'file.jpg', now()->addMinutes(5)
);
You could also make your own solution by directing all image request through your own server and making sure the file visibility is set to private.
Here is an example of how a controller could return image from your storage
public function get($path)
{
$file = Storage::disk('s3')->get($path);
// Do your temp link solution here
return response($file, 200)->header('Content-Type', 'image/png');
}
What i am using right now is Flysystem provided in laravel.Laravel Flysystem integration use simple drivers for working with local filesystems, Amazon S3 and other some space provide also. So for this doesn't matter whether is a server is laravel server or not.
Even better, it's very simple in this to switch between server by just changing server configuration in API.
As far as I know we can create temporary Url for s3 and rackspace in this also by calling temporaryUrl method. Caching is already in this.
That's the thing.
If your files are uploaded on an AWS S3 server
then,
use Storage;
$file_path = "4/1563454594.mp4";
if( Storage::disk('s3')->exists($file_path) ) {
// link expiration time
$urlExpires = Carbon::now()->addMinutes(1);
try {
$tempUrl = Storage::disk('s3')->temporaryUrl($file_path, $urlExpires);
} catch ( \Exception $e ) {
// Unable to test temporaryUrl, its giving driver dont support it issue.
return response($e->getMessage());
}
}
Your temporary URL will be generated, After given expiration time (1 minute). It will expire.

Resources