-1

So i'm devloping a small code that can control access for students / employees / visitors and strangers. So i'm facing this problem where the student when he Order his Unique QR code from a PHP form where the QR code is automaticly generated and encrypted and at the same time different from the ID of the student and at the end will be sent to the email of the student automaticly.

here is the code that i'm using rightnow :

  <?php
        include '../database/conn.php';
        require_once '../assets/libraries/phpqrcode/qrlib.php';
        require_once '../vendor/autoload.php';
        if (isset($_POST['SubmitButton'])) {
            function encrypt($data) {
                $key = '2469'; // Replace this with your own secret key
                $cipher = 'AES-256-CBC'; // Use a strong encryption cipher
                $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher)); // Generate a random IV
                $encrypted = openssl_encrypt($data, $cipher, $key, OPENSSL_RAW_DATA, $iv); // Encrypt the data
                $hmac = hash_hmac('sha256', $encrypted, $key, true); // Generate a HMAC for the encrypted data
                return base64_encode($iv . $hmac . $encrypted); // Encode the IV, HMAC, and encrypted data as a base64 string
              }
            $user_id = $_POST['id'];
            $email = $_POST['email'];
            $fullname = $_POST['name'];
            // Prepare the query
            $query3 = ("SELECT * FROM ecrypted_ids WHERE id = '$user_id'");
            // Bind the parameter
            $result3 = mysqli_query($connection, $query3);
            if (mysqli_num_rows($result3) > 0) {
            echo '<br><div class="alert alert-info" role="alert">ID Already exist or wrong !</div>';
              }else{ 
                    // Generate an encrypted ID related to the user's ID
                    $encrypted_id = encrypt($user_id); // You'll need to implement this function
                    // Save the encrypted ID in your database

                    // Prepare the query
                    $query4 = "INSERT INTO ecrypted_ids(id, encrypted_id) VALUES ('$user_id', '$encrypted_id')";
                    $result4 = mysqli_query($connection, $query3);
                    // Generate the QR code and save it to a file
                    QRcode::png($encrypted_id, 'qr_code.png');
                    sleep(3);
                    // Replace with the path to your service account's JSON key file
                    $keyFilePath = 'account.json';
                    // Replace with the email address that will be used to send the email
                    $senderEmail = 'bdeiramostephaamine@gmail.com';
                    // Replace with the recipient's email address
                    $recipientEmail = $_POST['email'];
                    // Replace with the subject of the email
                    $subject = 'Your QR code From UCBET';
                    // Replace with the body of the email
                    $body = 'Dear ' . $fullname . ',<br><br>' .
                            'Thank you for your order! Your QR code is attached to this email.<br><br>' .
                            'Best regards,<br>' .
                            'University Chadli Bendjedid Eltarf';
                    // Replace with the path to the QR code image file
                    $qrCodeImagePath = 'qr_code.png';
                    // Set up the Google API client
                    $client = new Google_Client();
                    $client->setAuthConfig($keyFilePath);
                    $client->addScope(Google_Service_Gmail::GMAIL_SEND);
                    $service = new Google_Service_Gmail($client);

                    // Set up the message
                    $message = new Google_Service_Gmail_Message();
                    $message->setRaw(base64_encode("To: $recipientEmail\r\n" .
                                                  "Subject: $subject\r\n" .
                                                  "MIME-Version: 1.0\r\n" .
                                                  "Content-Type: multipart/mixed; boundary=\"boundary\"\r\n\r\n" .
                                                  "--boundary\r\n" .
                                                  "Content-Type: text/html; charset=\"utf-8\"\r\n" .
                                                  "Content-Disposition: inline\r\n\r\n" .
                                                  "$body\r\n\r\n" .
                                                  "--boundary\r\n" .
                                                  "Content-Type: image/png\r\n" .
                                                  "Content-Disposition: attachment; filename=\"qrcode_". $fullname .".png\"\r\n\r\n" .
                                                  base64_encode(file_get_contents($qrCodeImagePath)) .
                                                  "\r\n\r\n" .
                                                  "--boundary--"));

                    // Send the email
                    try {
                        $message = $service->users_messages->send("me", $message);
                        echo '<br><div class="alert alert-success" role="alert">Your QR code has been sent to your email address!</div>';
                    } catch (Exception $e) {
                        echo '<br><div class="alert alert-danger" role="alert">An error occurred: ' . $e->getMessage() . '</div>';
                    }
        }
        }
        ?>

and at the end i always get this error

An error occurred: { "error": { "code": 400, "message": "Precondition check failed.", "errors": [ { "message": "Precondition check failed.", "domain": "global", "reason": "failedPrecondition" } ], "status": "FAILED_PRECONDITION" } } 

I've already created a private account on the google cloud and allowed the owner permissions. Exported the json key from the service account and re-named to "private.json". but it doesn't work. Also im using the email associated to the app that've created in the google cloud platform as a sender email. I've activated the service account to using the command gcloud auth activate-service-account --key-file=path to the file but nothing works ! I'll be grateful if you help me. Thanks.

Dharman
  • 30,962
  • 25
  • 85
  • 135
  • I suggest you to provide some more details regarding what is the error output you are getting. Also please provide what is the structure of `account.json`? – GoharSahi Mar 20 '23 at 05:38
  • 1
    What error 400? Where is it occurring? Also, please read about [how to avoid SQL injection attacks](https://stackoverflow.com/q/60174/1270789) as you've got a great big security hole waiting to be exploited. – Ken Y-N Mar 20 '23 at 05:41
  • @GoharSahi Can you check again please ?, i've added more information about the error code. – Mostepha Amine Bdeira Mar 20 '23 at 05:53
  • @KenY-N done. I've added the error output. Also i know about the SQL injections attacks, ill add statements later. i'm just testing the code. – Mostepha Amine Bdeira Mar 20 '23 at 05:55
  • @MostephaAmineBdeira, you cannot use `service account` for Gmail API. You need to use `Client ID` or `API Key` under `Google Cloud Console > Credentials` for that unless you are using domain-wide delegation (see link below). https://developers.google.com/identity/protocols/oauth2/service-account – GoharSahi Mar 20 '23 at 06:19
  • No problem @MostephaAmineBdeira. I've written some code for Google Client authentication. I'll share here in a while which can make it really easy for you (or someone else for that matter) to authenticate a google client. – GoharSahi Mar 20 '23 at 06:36
  • **Warning:** You are wide open to [SQL Injections](https://php.net/manual/en/security.database.sql-injection.php) and should use parameterized **prepared statements** instead of manually building your queries. They are provided by [PDO](https://php.net/manual/pdo.prepared-statements.php) or by [MySQLi](https://php.net/manual/mysqli.quickstart.prepared-statements.php). Never trust any kind of input! Even when your queries are executed only by trusted users, [you are still in risk of corrupting your data](http://bobby-tables.com/). [Escaping is not enough!](https://stackoverflow.com/q/32391315) – Dharman Mar 20 '23 at 09:13
  • @Dharman like i said before, im only testing sir. I'll focus on security side later. thanks for informing me anyway, i appreciate that. – Mostepha Amine Bdeira Mar 20 '23 at 18:41

1 Answers1

0

The code is written in Laminas framework. You can ask if you need any clarification. Also feel free to change it to suit your needs especially scopes. You need to have Google Cloud Console > Credentials > Client ID (for Web Application) for this code snippet to work.

GoogleClientService.php

<?php

namespace UserModule\Service;

use Laminas\Session\Container;
use UserModule\Module;
use Google_Client;

/**
 * Class GoogleClientService
 * @package CalendarModule\Service
 */
class GoogleClientService
{
    /** @var array SCOPES */
    public const SCOPES = ["openid email"];
    //NB: email and profile pic can be acquired using this endpoint with Guzzle Http : https://www.googleapis.com/oauth2/v3/userinfo?access_token=

    /** @var string ACCESS_TYPE */
    public const ACCESS_TYPE = "offline";

    /** @var bool CACHE_TIME_TO_LIVE */
    public const CACHE_TIME_TO_LIVE = 604800;

    /** @var Google_Client $googleClient */
    protected Google_Client $googleClient;

    /** @var string|null $authCode */
    protected ?string $authCode;

    /** @var string $clientId */
    protected string $clientId;

    /** @var string $clientSecret */
    protected string $clientSecret;

    /** @var string $redirectUri */
    protected string $redirectUri;

    /** @var string $scope */
    protected string $scope;

    /** @var Container $sessionContainer */
    protected Container $sessionContainer;

    /**
     * GoogleClientService constructor.
     * @param Google_Client $googleClient
     * @param Container $sessionContainer
     */
    public function __construct(
        Google_Client $googleClient,
        Container $sessionContainer
    ) {
        $this->googleClient = $googleClient;
        $this->sessionContainer = $sessionContainer;
    }

    /**
     * @return GoogleClientService
     */
    public function setupClient(): self
    {
        $this->getGoogleClient()->setApplicationName(Module::MODULE_NAME);
        $this->getGoogleClient()->setScopes($this->getScope());
        $this->getGoogleClient()->setClientId($this->getClientId());
        $this->getGoogleClient()->setClientSecret($this->getClientSecret());
        $this->getGoogleClient()->setRedirectUri($this->getRedirectUri());
        $this->getGoogleClient()->setAccessType(self::ACCESS_TYPE);

        return $this;
    }

    /**
     * @return Google_Client
     */
    public function getGoogleClient(): Google_Client
    {
        return $this->googleClient;
    }

    /**
     * @param Google_Client $googleClient
     * @return GoogleClientService
     */
    public function setGoogleClient(Google_Client $googleClient): self
    {
        $this->googleClient = $googleClient;

        return $this;
    }

    /**
     * @return string
     */
    public function getScope(): string
    {
        return $this->scope;
    }

    /**
     * @param string $scope
     * @return GoogleClientService
     */
    public function setScope(string $scope): self
    {
        $this->scope = $scope;

        return $this;
    }

    /**
     * @return string
     */
    public function getClientId(): string
    {
        return $this->clientId;
    }

    /**
     * @param string $clientId
     * @return GoogleClientService
     */
    public function setClientId(string $clientId): self
    {
        $this->clientId = $clientId;

        return $this;
    }

    /**
     * @return string
     */
    public function getClientSecret(): string
    {
        return $this->clientSecret;
    }

    /**
     * @param string $clientSecret
     * @return GoogleClientService
     */
    public function setClientSecret(string $clientSecret): self
    {
        $this->clientSecret = $clientSecret;

        return $this;
    }

    /**
     * @return string
     */
    public function getRedirectUri(): string
    {
        return $this->redirectUri;
    }

    /**
     * @param string $redirectUri
     * @return GoogleClientService
     */
    public function setRedirectUri(string $redirectUri): self
    {
        $this->redirectUri = $redirectUri;

        return $this;
    }

    /**
     * @return Google_Client|null
     */
    public function authenticateClient(): ?Google_Client
    {
        if ($this->getSessionContainer()->offsetExists("access_token")) {
            $accessToken = $this->getSessionContainer()->offsetGet("access_token");

            if (empty($accessToken) || (!empty($accessToken["error"]) && $accessToken["error"] == "invalid_grant")) {
                $this->getSessionContainer()->offsetUnset("access_token");
            } else {
                $this->getGoogleClient()->setAccessToken($accessToken);
            }
        }
        if ($this->getGoogleClient()->isAccessTokenExpired()) {
            $tokenCallback = function ($accessToken) {
            };
            $this->getGoogleClient()->setTokenCallback($tokenCallback);
            //NB: Fresh request for Access Token will be sent for each login attempt using Authorization Code.
            //In order to use Refresh Token to fetch Access Token, de-comment below code block.
            $refreshToken = $this->getSessionContainer()
                    ->offsetGet("access_token")["refresh_token"] ?? $this->getGoogleClient()
                    ->getRefreshToken();
            if (!empty($refreshToken)) {
                $this->getGoogleClient()->fetchAccessTokenWithRefreshToken($refreshToken);
                $accessToken = $this->getGoogleClient()->getAccessToken();
                $this->getSessionContainer()->offsetSet("access_token", $accessToken);
            } else {
                if (!$this->getAuthCode()) {
                    return null;
                }
                $accessToken = $this->getGoogleClient()->fetchAccessTokenWithAuthCode($this->getAuthCode());
                if (empty($accessToken) || (!empty($accessToken["error"]) && $accessToken["error"] == "invalid_grant")) {
                    if ($this->getSessionContainer()->offsetExists("access_token")) {
                        $this->getSessionContainer()->offsetUnset("access_token");
                    }
                } else {
                    $this->getSessionContainer()->offsetSet("access_token", $accessToken);
                    $this->getGoogleClient()->setAccessToken($accessToken);
                }
            }
        }

        return $this->getGoogleClient();
    }

    /**
     * @return Container
     */
    public function getSessionContainer(): Container
    {
        return $this->sessionContainer;
    }

    /**
     * @param Container $sessionContainer
     * @return GoogleClientService
     */
    public function setSessionContainer(Container $sessionContainer): self
    {
        $this->sessionContainer = $sessionContainer;
        return $this;
    }

    /**
     * @return string|null
     */
    public function getAuthCode(): ?string
    {
        return $this->authCode ?? null;
    }

    /**
     * @param string|null $authCode
     * @return GoogleClientService
     */
    public function setAuthCode(?string $authCode = null): self
    {
        $this->authCode = $authCode;

        return $this;
    }
}

GoogleClientServiceFactory.php

<?php

namespace UserModule\Service\Factory;

use Google_Client;
use Laminas\Session\Container;
use Laminas\Session\Exception\InvalidArgumentException;
use Psr\Container\ContainerInterface;
use UserModule\Service\GoogleClientService;
use Laminas\ServiceManager\Factory\FactoryInterface;

/**
 * Class GoogleClientServiceFactory
 * @package UserModule\Service\Factory
 */
class GoogleClientServiceFactory implements FactoryInterface
{
    /**
     * @param ContainerInterface $container
     * @param string $requestedName
     * @param array|null $options
     * @return object|GoogleClientService
     * @throws InvalidArgumentException
     */
    public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): GoogleClientService
    {
        return new GoogleClientService(
            new Google_Client(),
            new Container()
        );
    }
}

In your code do something like below (I'm skipping original functionality)

        $this->getGoogleClientService()->setClientId($oauthClient->client_id);
        $this->getGoogleClientService()->setClientSecret($oauthClient->client_secret);
        $this->getGoogleClientService()->setRedirectUri($oauthClient->redirect_uri);
        $this->getGoogleClientService()->setScope($oauthClient->scope);

        $this->getGoogleClientService()->setupClient();
        $googleClient = $this->getGoogleClientService()->authenticateClient();

        if (!$googleClient) {
          // Do your processing here
        }

Below is the sample client_secret json file.

{
    "web":
    {
        "client_id":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
        "project_id":"some-user-module",
        "auth_uri":"https://accounts.google.com/o/oauth2/auth",
        "token_uri":"https://oauth2.googleapis.com/token",
        "auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
        "client_secret":"XxXxXxXxXxXxXxXx",
        "redirect_uris":["http://localhost/user/googleLogin"],
        "javascript_origins":["http://localhost"]
    }
}
GoharSahi
  • 488
  • 1
  • 8
  • 22