I am currently trying to let Symfonys Validator Component handle the validation of uploaded files, which works perfectly fine for normal files. However, if files are above a certain size they are uploaded as chunks, which are then merged and then validated. Both ways to upload are validated by the same function, which basically just looks like this:
public function validateFile(UploadedFile $uploadedFile): ConstraintViolationList {
return $this->validator->validate(
$uploadedFile,
[
new FileConstraints([
'maxSize' => '1000M',
]),
]
);
}
But somehow, the merged uploads trigger a violation, which, unfortunately, is quite uninformative to me:
Symfony\Component\Validator\ConstraintViolation {#658 ▼
-message: "The file could not be uploaded."
-messageTemplate: "The file could not be uploaded."
-parameters: []
-plural: null
-root: Symfony\Component\HttpFoundation\File\UploadedFile {#647 ▶}
-propertyPath: ""
-invalidValue: Symfony\Component\HttpFoundation\File\UploadedFile {#647 ▶}
-constraint: Symfony\Component\Validator\Constraints\File {#649 ▶}
-code: "0"
-cause: null
}
The logs are clean, no errors, only INFO regarding matched routes and deprecated stuff aswell as DEBUG regarding authentificastion tokens and such.
If I dump'n'die the UploadedObjects the only difference is that the chunked & merged one has executable: true and that its not stored in tmp.
Can someone here explain to me what causes this violation and what has to be done to prevent it or point me to some documentation regarding that?
EDIT: The upload of chunks and the merging seems to work perfectly fine - uploaded images can be viewed, text docs/pdfs can be read etc. Also used all the other code for quite a while now with different validation, just wanted to make everything a bit more pro and sorted by using the existing Validator infrastructure. To provide additional info regarding the uploaded objects, here the dd output, starting with regular file upload:
Symfony\Component\HttpFoundation\File\UploadedFile {#20 ▼
-test: false
-originalName: "foo.jpg"
-mimeType: "image/jpeg"
-error: 0
path: "/tmp"
filename: "phpEu7Xmw"
basename: "phpEu7Xmw"
pathname: "/tmp/phpEu7Xmw"
extension: ""
realPath: "/tmp/phpEu7Xmw"
aTime: 2021-05-27 10:47:56
mTime: 2021-05-27 10:47:54
cTime: 2021-05-27 10:47:54
inode: 1048589
size: 539474
perms: 0100600
owner: 1000
group: 1000
type: "file"
writable: true
readable: true
executable: false
file: true
dir: false
link: false
}
For chunked upload:
Symfony\Component\HttpFoundation\File\UploadedFile {#647 ▼
-test: false
-originalName: "foo.jpg"
-mimeType: "image/jpeg"
-error: 0
path: "/home/vagrant/MyProject/var/uploads"
filename: "foo.jpg"
basename: "foo.jpg"
pathname: "/home/vagrant/MyProject/var/uploads/foo.jpg"
extension: "jpg"
realPath: "/home/vagrant/MyProject/var/uploads/foo.jpg"
aTime: 2021-05-27 10:43:58
mTime: 2021-05-27 10:43:58
cTime: 2021-05-27 10:43:58
inode: 8154
size: 539474
perms: 0100777
owner: 1000
group: 1000
type: "file"
writable: true
readable: true
executable: true
file: true
dir: false
link: false
}
When the File constraint receives an UploadedFile instance, it triggers a call to isValid, which in turn calls is_uploaded_file:
Returns true if the file named by filename was uploaded via HTTP POST.
This is useful to help ensure that a malicious user hasn't tried to
trick the script into working on files upon which it should not be
working
After reassembling the chunks into a new file this check no longer passes and the constraint fails.
You could use your last file fragment to reassemble the original file or you could return a File from your function. File is not subject to that check, and the constraint will accept it along with UploadedFile.
When creating your UploadedFile object programatically use the 'test mode'. I use this with the VichUploaderBundle and the use of test mode is documented here.
new Count([
'min' => 1,
'minMessage' => 'Please select a file to upload'
]),
I think the NotBlank constraint is the problem here, I don't think it should be used on UploadedFile.
Try using only the File and Count constraints (to make sure there is a minimum of 1 file attached).
Related
I'm using Codeception/WP Browser to write tests for Wordpress. I had a method on a class that was making an outside http request whenever it was being loaded up and this was incorrect behavior. It's only supposed to make that request on a certain page, and on others it is not. I have rewritten the code so its fixed, but I dont know how to go about testing it. I've tried loading up the acceptance, functional and wpunit tester helpers but none of them seem to have anything that lets me grab a response from an outside http request on page load. Can anyone help?
Ive tried using the different modules, but I cant seem to find the magic combination or I am just lost.
heres some of my acceptance code that isnt doing it
<?php
// $ codecept run acceptance exampleExpectLoginByAdminCest
class expectLoginByAdminCest {
public function _before( AcceptanceTester $I ) {
}
public function _after( AcceptanceTester $I ) {
}
public function expectsAdminToLogin( AcceptanceTester $I ) {
// ARRANGE
$I->wantTo( 'log in as an Admin' );
$I->amGoingTo( 'log in as Admin' );
// ACT
$I->loginAsAdmin();
$I->amOnPage( "/wp-admin/admin.php?page=advisor-dashboard&course=8927" );
// ASSERT
// tokens shouldnt be available so bad response
$I->seeResponseCodeIs(401);
}
}
Heres a copy of my acceptance config
# Suite for acceptance tests.
actor: AcceptanceTester
modules:
enabled:
- WPDb
- WPWebDriver
- \Helper\Acceptance
config:
WPDb:
dsn: 'mysql:host=%TEST_SITE_DB_HOST%;dbname=%TEST_SITE_DB_NAME%'
user: '%TEST_SITE_DB_USER%'
password: '%TEST_SITE_DB_PASSWORD%'
dump: 'tests/_data/rwa-dump.sql'
#import the dump before the tests; this means the test site database will be repopulated before the tests.
populate: true
# re-import the dump between tests; this means the test site database will be repopulated between the tests.
cleanup: true
waitlock: 10
url: '%TEST_SITE_WP_URL%'
urlReplacement: true #replace the hardcoded dump URL with the one above
tablePrefix: '%TEST_SITE_TABLE_PREFIX%'
WPWebDriver:
url: '%CHROMEDRIVER_WP_URL%'
adminUsername: 'admin'
adminPassword: 'admin'
adminPath: '/wp-admin'
browser: chrome
host: %CHROMEDRIVER_HOST%
port: %CHROMEDRIVER_PORT%
capabilities:
# Used in more recent releases of Selenium.
"goog:chromeOptions":
args: ["--no-sandbox", "--disable-gpu", "--user-agent=wp-browser"]
w3c: false
# Support the old format for back-compatibility purposes.
"chromeOptions":
args: ["--no-sandbox", "--disable-gpu", "--user-agent=wp-browser"]
w3c: false
The api call should fail, and get a 401 because no token should be available to authenticate, but its getting a 200
In order to validate the installation of WordPress instances, we are writing Python unit tests. One of the test should perform the following action: upload an image to WordPress.
In order to do that, I am using the Requests library.
When I inspect the form within /wp-admin/media-new.php page through Firebug (form information, I get the following information):
Form
Id: file-form
Name
Method: post
Action: http://localhost:8000/wp-admin/media-new.php
Elements
id: plupload-browse-button
type: button
value: Select Files
id:async-upload
name: async-upload
type: file
label: Upload
id:html-upload
name: html-upload
type: submit
value: Upload
id: post_id
name: post_id
type: hidden
value: 0
id: _wpnonce
name: _wpnonce
type: hidden
value: c0fc3b80bb
id: file-form
name: _wp_http_referer
type: hidden
value: /wp-admin/media-new.php
I believe that the _wpnonce is a unique value generated for each session. Therefore, before trying to upload the file, I get the media-new.php page and grab the _wpnonce in the form (hence the variable in my code).
My code is the following:
with open('1.jpg', 'rb') as f:
upload_data = {'post_id': '0',
'_wp_http_referer': '/wp-admin/media-new.php',
'_wpnonce': wp_nonce,
'action': 'upload_attachement',
'name': '1.jpg',
'async-upload': f,
'html-upload': 'Upload'}
upload_result = session.post('http://localhost:8000/wp-admin/media-new.php', upload_data)
The code runs fine and the upload_result.status_code equals 200.
However, the image never shows up in the media gallery of WordPress.
I believe this a simple error, but I can't figure out what I'm missing.
Thanks in advance for the help.
If you want to post files you should use the files parameter. Also the '_wpnonce' value is not enough to get authenticated, you need to have cookies.
url = 'http://localhost:8000/wp-admin/media-new.php'
data = {
'post_id': '0',
'_wp_http_referer': '/wp-admin/media-new.php',
'_wpnonce': wp_nonce,
'action': 'upload_attachement',
'html-upload': 'Upload'
}
files = {'async-upload':('1.jpg', open('1.jpg', 'rb'))}
headers = {'Cookie': my_cookies}
upload_result = session.post(url, data=data, files=files, headers=headers)
I'm assuming that you have acquired valid cookies from your browser. If you want to get authenticated with requests check my answer to this post: login-wordpress-with-requests
I'm trying to do BDD testing on an upload method. I'm using Behat with Mink in a symfony2 project.
Now I'm able to do simple request with this client:
$this->client = $this->mink->getSession('goutte')->getDriver()->getClient();
and
$this->client->request("POST", $url, array('content-type' => 'application/json'), array(), array(), $fields);
without any issue.
How to do a request with a file? I tried this:
$file = new \Symfony\Component\HttpFoundation\File\UploadedFile($path, "video");
$fields = json_encode($table->getColumnsHash()[0]);
$this->client->request("POST", $url, array('content-type' => 'multipart/form-data'), array($file), array(), $fields);
And the error I receive is:
PHP Fatal error: Call to undefined method
GuzzleHttp\Stream\Stream::addFile()
What is the mistake?
Thanks!
Ok finally I found the answer. Hope that helps someone.
To upload a file, the correct way is:
$fields = $table->getColumnsHash()[0]; //array('name' => 'test', 'surname' => 'test');
$fields["file"] = fopen($path, 'rb');
$this->client->request("POST", $url, array('Content-Type => multipart/form-data'), array(), array(), $fields);
The trick is that you must not use the fourth parameter of the Goutte request, but you have to pass all fields as body raw data.
I don't know about Guzzle upload but simple upload works like below. You can remove unnecessary bits.
Note: I would suggest you to keep dummy image files in project folder because if there are a lot of developers work on same project they would have exactly same folder structure so image would be accessible. I've seen some guys selecting an image from their desktop which differs from person to person so tests fail.
files_path below must point to your project directory and it should exist as e.g. /var/www/html/myproject/test/build/dummy/
behat.yml
default:
context:
class: Site\FrontendBundle\Features\Context\FeatureContext
parameters:
output_path: %behat.paths.base%/build/behat/output/
screen_shot_path: %behat.paths.base%/build/behat/screenshot/
extensions:
Behat\Symfony2Extension\Extension:
mink_driver: true
kernel:
env: test
debug: true
Behat\MinkExtension\Extension:
base_url: 'http://localhost/local/symfony/web/app_test.php/'
files_path: %behat.paths.base%/build/dummy/
javascript_session: selenium2
browser_name: firefox
goutte: ~
selenium2: ~
paths:
features: %behat.paths.base%/src
bootstrap: %behat.paths.features%/Context
Assuming that you have jpg.jpg under /var/www/html/myproject/test/build/dummy/ folder as below.
Example feature for upload:
Feature: Create League
In order to upload a file
As a user
I should be able to select and upload a file
#javascript
Scenario: I can create league
Given I am on "/"
When I attach the file "jpg.jpg" to "league_flag"
And I press "Submit"
Then I should see "Succeeded."
I have a user collection with some deny update rules :
// The roles object
Schema.roles = new SimpleSchema({
maker: {
type: Boolean,
denyUpdate: true
},
admin: {
type: Boolean,
denyUpdate: true
}
});
Those datas are in the user profile. And obviously, I don't want the random user to be able to modify profile.roles.admin. But the admin user should be able to.
It works partially : the user cannot modify this boolean. But it should be possible to modify it from the following server side code.
Meteor.users.update({_id: targetID'}, {$set: {'profile.roles.admin': true}});
Is there a way to tell collection2 to trust the code from the server ?
EDIT : the answer
Thanks to the answer below, here's the code I use now for my schema :
admin: {
type: Boolean,
autoValue: function() {
// If the code is not from the server (isFromTrustedCode)
// unset the update
if(!this.isFromTrustedCode)
this.unset();
}
}
The isFromTrustedCode boolean tell if the code should be trusted. Simple. By the way, the autoValue option return a complete object about the update (or insert or set or upsert) action. Here are the parametters :
isSet: true
unset: [Function]
value: true
operator: '$set'
field: [Function]
siblingField: [Function]
isInsert: false
isUpdate: true
isUpsert: false
userId: null
isFromTrustedCode: true
So it is possible to have a really fine-grained management of the writing rights rules.
As provided in the official documentation, you can bypass validation using a simple option:
To skip validation, use the validate: false option when calling insert or update. On the client (untrusted code), this will skip only client-side validation. On the server (trusted code), it will skip all validation.
But if you want more fine-grained control, instead of using a denyUpdate, you can use a custom validation type which has a this context with a isFromTrustedCode property which is true when called on the server.
If you build up a block structure, convert it to a string with MOLD, and write it to a file like this:
>> write %datafile.dat mold [
[{Release} 12-Dec-2012]
[{Conference} [12-Jul-2013 .. 14-Jul-2013]]
]
You can LOAD it back in later. But what about headers? If a file contains code, it is supposed to start with a header like:
rebol [
title: "Local Area Defringer"
date: 1-Jun-1957
file: %defringe.r
purpose: {
Stabilize the wide area ignition transcriber
using a double ganged defringing algorithm.
}
]
If you are just writing out data and reading it back in, are you expected to have a rebol [] header, and extend it with any properties you want to add? Should you come up with your own myformat [] header concept with your own properties?
Also, given that LOAD does binding, does it make sense to use it for data or is there a different operation?
Rebol data doesn't have to have a header, but is best practice to include one (even if it's just data).
Some notes:
SAVE is your best bet for serializing to file! or port! and has a mechanism for including a header.
MOLD and SAVE both have an /ALL refinement that corresponds to LOAD (without /ALL, some data from MOLD and SAVE cannot be reliably recovered, including Object, Logic and None values).
LOAD discards the header, though you can load it using the /HEADER refinement.
Putting this together:
save/all/header %datafile.dat reduce [next "some" 'data][
title: "Some Data"
]
header: take data: load/header %datafile.dat
To use a header other than Rebol [], you'd need to devise a separate loader/saver.
For the case of reading, construct works very well alongside load to prevent evaluation (of code as opposed to data):
prefs: construct/with load %options.reb default-prefs
It is:
Similar to context
obj: [
name: "Fred"
age: 27
city: "Ukiah"
]
obj-context: context obj
obj-construct: construct obj
In this case, the same:
>> obj-context = obj-construct
== true
Different
when it comes to evaluating code:
obj-eval: [
name: uppercase "Fred"
age: 20 + 7
time: now/time
]
obj-eval-context: context obj-eval
obj-eval-construct: construct obj-eval
This time parsing differently:
>> obj-eval-context = obj-eval-construct
false
>> ?? obj-eval-construct
obj-eval-construct: make object! [
name: 'uppercase
age: 20
time: now/time
]
Aside:
This is the point I realize the following code wasn't behaving as I expected:
obj-eval: [
title: uppercase "Fred"
age: 20 + 7
city: "Ukiah"
time: now/time
]
gives in red (and by extension, rebol2):
>> obj-eval-construct: construct obj-eval
== make object! [
title: 'uppercase
age: 20
city: "Ukiah"
time: now/time
]
lit-word! and lit-path! is different.
TODO: question
It has also
Useful refinement /with
Which can be used for defaults, similar to make