I need some support with a personal project Im working on. I have a connected device which sends JSON data at a defined interval (every 1 min / 5 min/ 15 mins etc) to a specific IP address on port 8080.
The JSON that is sent is in following format:
{
"MeterSN": “1234”,
"Status": ###,
“Variable1”: “###”,
"Variable2”: “###”,
"Variable3”: ###
}
I have started building a PHP Rest API to process this data but am somehow not able to save the to mySQL.
Here is what I have so far:
meterdata.php
class MeterDataInput{
private $conn;
private $table_name = "meterdata";
public $MeterSN;
public $StatusA;
public $Variable1;
public $Variable2;
public $Variable3;
public function __construct($db){
$this->conn = $db;
}
}
function createMeterRecord(){
$query = "INSERT INTO
" . $this->table_name . "
SET
MeterSN=:MeterSN, Status=:Status, Variable1=:Variable1, Variable2=:Variable2, Variable3=:Variable3";
// prepare query
$stmt = $this->conn->prepare($query);
// sanitize
$this->MeterSN=htmlspecialchars(strip_tags($this->MeterSN));
$this->Status=htmlspecialchars(strip_tags($this->Status));
$this->Variable1=htmlspecialchars(strip_tags($this->Variable1));
$this->Variable2=htmlspecialchars(strip_tags($this->Variable2));
$this->Variable3=htmlspecialchars(strip_tags($this->Variable3));
// bind values
$stmt->bindParam(":MeterSN", $this->MeterSN);
$stmt->bindParam(":Status", $this->Status);
$stmt->bindParam(":Variable1", $this->Variable1);
$stmt->bindParam(":Variable2", $this->Variable2);
$stmt->bindParam(":Variable3", $this->Variable3);
// execute query
if($stmt->execute()){
return true;
}
return false;
}
index.php
// required headers
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
// get database connection
include_once 'config/db.php';
// instantiate MeterData object
include_once 'objects/meterdata.php';
$database = new Database();
$db = $database->getConnection();
$meterdata = new MeterDataInput($db);
// get posted data
$data = json_decode(file_get_contents("php://input"));
// make sure data is not empty
if(!empty($data->MeterSN)){
// set product property values
$meterdata->MeterSN = $data->MeterSN;
$meterdata->Status = $data->Status;
$meterdata->Variable1 = $data->Variable1;
$meterdata->Variable2 = $data->Variable2;
$meterdata->Variable3 = $data->Variable3;
// create the meter data entry
if($meterdata->createMeterRecord()){
// set response code - 201 created
http_response_code(201);
// update the status
echo json_encode(array("message" => "Data record was added"));
}
// if unable to create the record
else{
// set response code - 503 service unavailable
http_response_code(503);
// tell the user
echo json_encode(array("message" => "Unable to add record."));
}
}
// data is incomplete
else{
// set response code - 400 bad request
http_response_code(400);
// tell the user
echo json_encode(array("message" => "Unable to create data record. Data is incomplete."));
}
Obviously i also have config.php and db.php
I am not sure where i am going wrong, however I am not able to see the records popupate within mySQL.
So I am working with Yii2 and am fairly new to it. I am using Kartik File upload and have attempted to convert the code for multiple files. but it only saves the first file.
I have removed the validation as this was also failing but will add back in once I know all else is working.
Model:
/**
* Process upload of image
*
* #return mixed the uploaded image instance
*/
public function uploadImage() {
// get the uploaded file instance. for multiple file uploads
// the following data will return an array (you may need to use
// getInstances method)
$image = UploadedFile::getInstances($this, 'image');
foreach ($image as $images) {
// if no image was uploaded abort the upload
if (empty($images)) {
return false;
}
// store the source file name
$this->image_src_filename = $images->name;
$extvar = (explode(".", $images->name));
$ext = end($extvar);
// generate a unique file name
$this->image_web_filename = Yii::$app->security->generateRandomString().".{$ext}";
// the uploaded image instance
return $images;
} }
Controller:
/**
* Creates a new PhotoPhotos model.
* If creation is successful, the browser will be redirected to the 'view' page.
* #return mixed
*/
public function actionCreate()
{
$model = new PhotoPhotos();
if ($model->load(Yii::$app->request->post())) {
// process uploaded image file instance
$images = $model->uploadImage();
if ($model->save(false)) {
// upload only if valid uploaded file instance found
if ($images !== false) {
$path = $model->getImageFile();
$images->saveAs($path);
}
return $this->redirect(['view', 'id' => $model->ID]);
} else {
//error in saving
}
}
return $this->render('create', [
'model' => $model,
]);
}
View:
//uncomment for multiple file upload
echo $form->field($model, 'image[]')->widget(FileInput::classname(), [
'options'=>['accept'=>'image/*', 'multiple'=>true],
]);
I see one problem which is that you reversed $images and $image in
foreach ($image as $images)
which should be
foreach ($images as $image)
Cheers
I am uploading documents to Google Drive successfully but my meta-data does not appear to be getting back to me correctly.
protected File insertFile(Drive service, List<String> parentIds, com.google.drive.FileContent fileContent, File googleFile)throws IOException {
// Set the parent folder.
if (parentIds != null && parentIds.size() > 0) {
List<ParentReference> parentReferences = new ArrayList<ParentReference>();
for (String parentId : parentIds ){
parentReferences.add(new ParentReference().setId(parentId));
}
googleFile.setParents( parentReferences );
}
try {
googleFile = service.files().insert(googleFile, fileContent).execute();
// Uncomment the following line to print the File ID.
System.out.println("File ID: " + googleFile.getId());
return googleFile;
}
catch (IOException e) {
System.out.println("An error occured: " + e);
return null;
}
}
Above is my insert statement, below is what I am sending as details about the document.
{description=XXXXXXX Invoice, fileExtension=pdf,
indexableText={text=XXXXXXX Invoice}, labels={restricted=false},
mimeType=application/pdf, parents=[{id=0B_owsnWRsIy7S1VsWG1vNTYzM1k}],
properties=[{key=DocumentType, value=11}], title=XXXXXXX Invoice}
When I do a get for that same document using this code
protected InputStream downloadFile(Drive service, File file)throws IOException {
if (file.getDownloadUrl() != null && file.getDownloadUrl().length() > 0) {
HttpResponse resp =
service.getRequestFactory().buildGetRequest(new GenericUrl(file.getDownloadUrl()))
.execute();
return resp.getContent();
}
else {
// The file doesn't have any content stored on Drive.
return null;
}
}
I get most of the text back minus the indexable Text and File Extension, is that correct (Do not want to show since it contains a lot of information that is noise)?
Two separate issues here.
1) fileExtension is a read-only field so it is being ignored. When retrieving the information, it is derived from the original title/filename. Since your title doesn't include ".pdf" it is being set to empty.
2) indexableText is write-only in we don't allow you to retrieve it once set; it is only used by the drive backend to service search queries.
You can read more on the different metadata properties of the file resource in our documentation.
I have an Android app that stores my notes in hidden app data. I want to export my notes so the question is simple:
How can I access the hidden app data in Google Drive for a specific app?
Indeed, Google does not let you access this hidden app-data folder directly.
But, if you can get your hands on the app's client ID/client secret/digital signature that is used for authentication against Google's servers - then yes, you can basically emulate the app and access the hidden data in your Google Drive using the Drive API.
How it works in Android
Usually, when an android application wants to access a Google API (such as Drive, Games or Google Sign-In - not all are supported) it communicates with the Google Play services client library, which in turn obtains an access token from Google on behalf of the app. This access token is then sent with each request to the API, so that Google knows who is using it and what he is allowed to do with your account (OAuth 2.0). In order to get this access token for the first time, the Google Play service sends an HTTPS POST request to android.clients.google.com/auth with these fields (along with other details):
Token - a "master token" which identifies the Google account and basically allows full access to it
app - the application package name, such as com.whatsapp
client_sig - the application's digital signature (sent as SHA1)
device - the device's Android ID
service - the scopes (permissions) that the app wants to have
So before we can start using the Drive API in the name of a specific app, we need to know its signature and our account's master token. Fortunately, the signature can be easily extracted from the .apk file:
shell> unzip whatsapp.apk META-INF/*
Archive: whatsapp.apk
inflating: META-INF/MANIFEST.MF
inflating: META-INF/WHATSAPP.SF
inflating: META-INF/WHATSAPP.DSA
shell> cd META-INF
shell> keytool -printcert -file WHATSAPP.DSA # can be CERT.RSA or similar
.....
Certificate fingerprints:
SHA1: 38:A0:F7:D5:05:FE:18:FE:C6:4F:BF:34:3E:CA:AA:F3:10:DB:D7:99
Signature algorithm name: SHA1withDSA
Version: 3
The next thing we need is the master token. This special token is normally received and stored on the device when a new google account is added (for example, when first setting up the phone), by making a similar request to the same URL. The difference is that now the app that's asking for permissions is the Play services app itself (com.google.android.gms), and Google is also given additional Email and Passwd parameters to log in with. If the request is successful, we will get back our master token, which could then be added to the user's app request.
You can read this blogpost for more detailed information about the authentication process.
Putting it all together
Now, we can write a code for authentication using these two HTTP requests directly - a code that can browse any app's files with any Google account. Just choose your favorite programming language and client library. I found it easier with PHP:
require __DIR__ . '/vendor/autoload.php'; // Google Drive API
// HTTPS Authentication
$masterToken = getMasterTokenForAccount("your_username#gmail.com", "your_password");
$appSignature = '38a0f7d505fe18fec64fbf343ecaaaf310dbd799';
$appID = 'com.whatsapp';
$accessToken = getGoogleDriveAccessToken($masterToken, $appID, $appSignature);
if ($accessToken === false) return;
// Initializing the Google Drive Client
$client = new Google_Client();
$client->setAccessToken($accessToken);
$client->addScope(Google_Service_Drive::DRIVE_APPDATA);
$client->addScope(Google_Service_Drive::DRIVE_FILE);
$client->setClientId(""); // client id and client secret can be left blank
$client->setClientSecret(""); // because we're faking an android client
$service = new Google_Service_Drive($client);
// Print the names and IDs for up to 10 files.
$optParams = array(
'spaces' => 'appDataFolder',
'fields' => 'nextPageToken, files(id, name)',
'pageSize' => 10
);
$results = $service->files->listFiles($optParams);
if (count($results->getFiles()) == 0)
{
print "No files found.\n";
}
else
{
print "Files:\n";
foreach ($results->getFiles() as $file)
{
print $file->getName() . " (" . $file->getId() . ")\n";
}
}
/*
$fileId = '1kTFG5TmgIGTPJuVynWfhkXxLPgz32QnPJCe5jxL8dTn0';
$content = $service->files->get($fileId, array('alt' => 'media' ));
echo var_dump($content);
*/
function getGoogleDriveAccessToken($masterToken, $appIdentifier, $appSignature)
{
if ($masterToken === false) return false;
$url = 'https://android.clients.google.com/auth';
$deviceID = '0000000000000000';
$requestedService = 'oauth2:https://www.googleapis.com/auth/drive.appdata https://www.googleapis.com/auth/drive.file';
$data = array('Token' => $masterToken, 'app' => $appIdentifier, 'client_sig' => $appSignature, 'device' => $deviceID, 'google_play_services_version' => '8703000', 'service' => $requestedService, 'has_permission' => '1');
$options = array(
'http' => array(
'header' => "Content-type: application/x-www-form-urlencoded\r\nConnection: close",
'method' => 'POST',
'content' => http_build_query($data),
'ignore_errors' => TRUE,
'protocol_version'=>'1.1',
//'proxy' => 'tcp://127.0.0.1:8080', // optional proxy for debugging
//'request_fulluri' => true
)
);
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if (strpos($http_response_header[0], '200 OK') === false)
{
/* Handle error */
print 'An error occured while requesting an access token: ' . $result . "\r\n";
return false;
}
$startsAt = strpos($result, "Auth=") + strlen("Auth=");
$endsAt = strpos($result, "\n", $startsAt);
$accessToken = substr($result, $startsAt, $endsAt - $startsAt);
return "{\"access_token\":\"" . $accessToken . "\", \"refresh_token\":\"TOKEN\", \"token_type\":\"Bearer\", \"expires_in\":360000, \"id_token\":\"TOKEN\", \"created\":" . time() . "}";
}
function getMasterTokenForAccount($email, $password)
{
$url = 'https://android.clients.google.com/auth';
$deviceID = '0000000000000000';
$data = array('Email' => $email, 'Passwd' => $password, 'app' => 'com.google.android.gms', 'client_sig' => '38918a453d07199354f8b19af05ec6562ced5788', 'parentAndroidId' => $deviceID);
$options = array(
'http' => array(
'header' => "Content-type: application/x-www-form-urlencoded\r\nConnection: close",
'method' => 'POST',
'content' => http_build_query($data),
'ignore_errors' => TRUE,
'protocol_version'=>'1.1',
//'proxy' => 'tcp://127.0.0.1:8080', // optional proxy for debugging
//'request_fulluri' => true
)
);
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if (strpos($http_response_header[0], '200 OK') === false)
{
/* Handle error */
print 'An error occured while trying to log in: ' . $result . "\r\n";
return false;
}
$startsAt = strpos($result, "Token=") + strlen("Token=");
$endsAt = strpos($result, "\n", $startsAt);
$token = substr($result, $startsAt, $endsAt - $startsAt);
return $token;
}
And finally, the results -
Files:
gdrive_file_map (1d9QxgC3p4PTXRm_fkAY0OOuTGAckykmDfFls5bAyE1rp)
Databases/msgstore.db.crypt9 (1kTFG5TmgIGTPJuVynWfhkXxLPgz32QnPJCe5jxL8dTn0)
16467702039-invisible (1yHFaxfmuB5xRQHLyRfKlUCVZDkgT1zkcbNWoOuyv1WAR)
Done.
NOTE: This is an unofficial, hacky solution, and so it might have a few problems. For example, the access token is alive only for one hour, after which it won't be refreshed automatically.
A working example as of September 2020
Note: this is actually an addition for Tomer's answer
Things changed since Tomer's original answer was posted.
Currently, to get the master token and avoid the Error=BadAuthentication, you need two things:
Replace Passwd field with EncryptedPasswd and encrypt its value by RSA with google public key (the exact technique was reversed by some guy) - this can be done using phpseclib.
Make HTTPS connection to Google server with the same SSL/TLS options as in one of the supported Android systems. This includes TLS versions and exact list of supported ciphers in right order. If you change the order or add/remove ciphers you'll get Error=BadAuthentication. It took me a whole day to figure this out...
Luckily, PHP >=7.2 comes with openssl-1.1.1 that has all the necessary ciphers to emulate Android 10 client.
So here is rewriten getMasterTokenForAccount() function that sets the ciphers and uses EncryptedPasswd instead of plain Passwd. And below is encryptPasswordWithGoogleKey() implementation that does the encryption.
phpseclib is necessary and can be installed with composer: composer require phpseclib/phpseclib:~2.0
function getMasterTokenForAccount($email, $password)
{
$url = 'https://android.clients.google.com/auth';
$deviceID = '0000000000000000';
$data = array('Email' => $email, 'EncryptedPasswd' => encryptPasswordWithGoogleKey($email, $password), 'app' => 'com.google.android.gms', 'client_sig' => '38918a453d07199354f8b19af05ec6562ced5788', 'parentAndroidId' => $deviceID);
$options = array(
'ssl' => array(
'ciphers' => 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:ECDH+AESGCM:DH+AESGCM:ECDH+AES:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!eNULL:!MD5:!DSS'),
'http' => array(
'header' => "Content-type: application/x-www-form-urlencoded\r\nConnection: close",
'method' => 'POST',
'content' => http_build_query($data),
'ignore_errors' => TRUE,
'protocol_version'=>'1.1',
//'proxy' => 'tcp://127.0.0.1:8080', // optional proxy for debugging
//'request_fulluri' => true
)
);
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if (strpos($http_response_header[0], '200 OK') === false)
{
/* Handle error */
print 'An error occured while trying to log in: ' . $result . "\r\n";
return false;
}
$startsAt = strpos($result, "Token=") + strlen("Token=");
$endsAt = strpos($result, "\n", $startsAt);
$token = substr($result, $startsAt, $endsAt - $startsAt);
return $token;
}
function encryptPasswordWithGoogleKey($email, $password)
{
define('GOOGLE_KEY_B64', 'AAAAgMom/1a/v0lblO2Ubrt60J2gcuXSljGFQXgcyZWveWLEwo6prwgi3iJIZdodyhKZQrNWp5nKJ3srRXcUW+F1BD3baEVGcmEgqaLZUNBjm057pKRI16kB0YppeGx5qIQ5QjKzsR8ETQbKLNWgRY0QRNVz34kMJR3P/LgHax/6rmf5AAAAAwEAAQ==');
$google_key_bin = base64_decode(GOOGLE_KEY_B64);
$modulus_len = unpack('Nl', $google_key_bin)['l'];
$modulus_bin = substr($google_key_bin, 4, $modulus_len);
$exponent_len = unpack('Nl', substr($google_key_bin, 4 + $modulus_len, 4))['l'];
$exponent_bin = substr($google_key_bin, 4 + $modulus_len + 4, $exponent_len);
$modulus = new phpseclib\Math\BigInteger($modulus_bin, 256);
$exponent = new phpseclib\Math\BigInteger($exponent_bin, 256);
$rsa = new phpseclib\Crypt\RSA();
$rsa->loadKey(['n' => $modulus, 'e' => $exponent], phpseclib\Crypt\RSA::PUBLIC_FORMAT_RAW);
$rsa->setEncryptionMode(phpseclib\Crypt\RSA::ENCRYPTION_OAEP);
$rsa->setHash('sha1');
$rsa->setMGFHash('sha1');
$encrypted = $rsa->encrypt("{$email}\x00{$password}");
$hash = substr(sha1($google_key_bin, true), 0, 4);
return strtr(base64_encode("\x00{$hash}{$encrypted}"), '+/', '-_');
}
The user cannot directly access data in the hidden app folders, only the app can access them. This is designed for configuration or other hidden data that the user should not directly manipulate. (The user can choose to delete the data to free up the space used by it.)
The only way the user can get access to it is via some functionality exposed by the specific app.
public void retrieveContents(DriveFile file) {
Task<DriveContents> openFileTask =
getDriveResourceClient().openFile(file, DriveFile.MODE_READ_ONLY);
openFileTask.continueWithTask(new Continuation<DriveContents, Task<Void>>() {
#Override
public Task<Void> then(#NonNull Task<DriveContents> task) throws Exception {
DriveContents contents = task.getResult();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(contents.getInputStream()))) {
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line).append("\n");
}
Log.e("result ", builder.toString());
}
Task<Void> discardTask = MainActivity.this.getDriveResourceClient().discardContents(contents);
// [END drive_android_discard_contents]
return discardTask;
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});
}
public void retrieveContents(DriveFile file) {
Task<DriveContents> openFileTask =
getDriveResourceClient().openFile(file, DriveFile.MODE_READ_ONLY);
openFileTask.continueWithTask(new Continuation<DriveContents, Task<Void>>() {
#Override
public Task<Void> then(#NonNull Task<DriveContents> task) throws Exception {
DriveContents contents = task.getResult();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(contents.getInputStream()))) {
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line).append("\n");
}
Log.e("result ", builder.toString());
}
Task<Void> discardTask = MainActivity.this.getDriveResourceClient().discardContents(contents);
// [END drive_android_discard_contents]
return discardTask;
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});
}
to get all the file in app data try the code
private void listFiles() {
Query query =
new Query.Builder()
.addFilter(Filters.or(Filters.eq(SearchableField.MIME_TYPE, "text/html"),
Filters.eq(SearchableField.MIME_TYPE, "text/plain")))
.build();
getDriveResourceClient()
.query(query)
.addOnSuccessListener(this,
new OnSuccessListener<MetadataBuffer>() {
#Override
public void onSuccess(MetadataBuffer metadataBuffer) {
//mResultsAdapter.append(metadataBuffer);
for (int i = 0; i <metadataBuffer.getCount() ; i++) {
retrieveContents(metadataBuffer.get(i).getDriveId().asDriveFile());
}
}
}
)
.addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.e(TAG, "Error retrieving files", e);
MainActivity.this.finish();
}
});
}
also you can download the content of file bye the following code
public void retrieveContents(DriveFile file) {
Task<DriveContents> openFileTask =
getDriveResourceClient().openFile(file, DriveFile.MODE_READ_ONLY);
openFileTask.continueWithTask(new Continuation<DriveContents, Task<Void>>() {
#Override
public Task<Void> then(#NonNull Task<DriveContents> task) throws Exception {
DriveContents contents = task.getResult();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(contents.getInputStream()))) {
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line).append("\n");
}
Log.e("result ", builder.toString());
}
Task<Void> discardTask = MainActivity.this.getDriveResourceClient().discardContents(contents);
// [END drive_android_discard_contents]
return discardTask;
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});
}
I am trying to use service account of goolge drive api to upload the file to 'my' central Google Drive.
I get the sample code from the Google drive API, however, when I run the code, exactly, when insert file into drive using Google_DriveService, the Google chrome show that "This webpage is not available".
Here is my php code:
<?php
require_once "google-api-php-client/src/Google_Client.php";
require_once "google-api-php-client/src/contrib/Google_DriveService.php";
require_once "google-api-php-client/src/contrib/Google_Oauth2Service.php";
session_start();
/**
* Build and returns a Drive service object authorized with the service accounts.
*
* #return Google_DriveService service object.
*/
function buildService() {
$DRIVE_SCOPE = 'https://www.googleapis.com/auth/drive';
$SERVICE_ACCOUNT_EMAIL = '<email>#developer.gserviceaccount.com';
$SERVICE_ACCOUNT_PKCS12_FILE_PATH = '<filename>.p12';
$key = file_get_contents($SERVICE_ACCOUNT_PKCS12_FILE_PATH);
$auth = new Google_AssertionCredentials(
$SERVICE_ACCOUNT_EMAIL,
array($DRIVE_SCOPE),
$key);
$client = new Google_Client();
$client->setUseObjects(true);
$client->setAssertionCredentials($auth);
return new Google_DriveService($client);
}
function uploadFile($service, $mime, $src) {
//Insert a file
$file = new Google_DriveFile();
$file->setMimeType($mime);
$data = file_get_contents($src);
try {
//ERROR HERE: cannot insert (upload) file
$createdFile = $service->files->insert($file,
array(
'data' => $data,
'mimeType' => $mime,
'convert' => true,
)
);
return $createdFile;
} catch (Exception $e) {
print "An error occurred: " . $e->getMessage();
}
}
//test upload file to server
$service =buildService();
$mineType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
$path = "toupload.docx";
$file = uploadFile($service,$mineType, $path);
?>