stephangroen / tesla-php-client

A PHP client for easy integration of the Tesla API
MIT License
19 stars 8 forks source link

New Authentication #13

Open ralfonso4 opened 3 years ago

ralfonso4 commented 3 years ago

I am using your tesla-api class. Because the new Authentication of Tesla i had to make some changes: Its not so nice but it works:

    public function base64url_encode($data) { 
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); 
    } 

    public function getAccessToken(string $username, string $password)
    {

###Step 1: Obtain the login page

        $code_verifier = substr(hash('sha512', mt_rand()), 0, 86);
        $state = $this->base64url_encode(substr(hash('sha256', mt_rand()), 0, 12));
        $code_challenge = $this->base64url_encode($code_verifier);

        $data =[
            'client_id' => 'ownerapi',
            'code_challenge' => $code_challenge,
            'code_challenge_method' => 'S256',
            'redirect_uri' => 'https://auth.tesla.com/void/callback',
            'response_type' => 'code',
            'scope' => 'openid email offline_access',
            'state' => $state,
        ];
        $GetUrl = $this->accessUrl.'?'.http_build_query ($data);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $GetUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_HEADER, 1);
        $apiResult = curl_exec($ch);
        curl_close($ch);

        $dom = new DomDocument();
        @ $dom->loadHTML($apiResult);
        $child_elements = $dom->getElementsByTagName('input'); //DOMNodeList
        foreach( $child_elements as $h2 ) {
            $hiddeninputs[$h2->getAttribute('name')]=$h2->getAttribute('value');
        }
        $headers = [];
        $output = rtrim($apiResult);
        $data = explode("\n",$output);
        $headers['status'] = $data[0];
        array_shift($data);

        foreach($data as $part){
            //some headers will contain ":" character (Location for example), and the part after ":" will be lost, Thanks to @Emanuele
            $middle = explode(":",$part,2);

            //Supress warning message if $middle[1] does not exist, Thanks to @crayons
            if ( !isset($middle[1]) ) { $middle[1] = null; }
                $headers[trim($middle[0])] = trim($middle[1]);
        }
    $cookie = $headers['set-cookie'];

###Step 2: Obtain an authorization code

    $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $GetUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_HEADER, 1);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded','Cookie: '.$cookie));
    $postData = array(
            '_csrf' => $hiddeninputs['_csrf'],
            '_phase' => $hiddeninputs['_phase'],
            '_process' => $hiddeninputs['_process'],
            'transaction_id' => $hiddeninputs['transaction_id'],
            'cancel' =>$hiddeninputs['cancel'],
          'identity' => $username,
          'credential' => $password
        );
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
        curl_setopt($ch, CURLOPT_COOKIE, $cookie);
        $apiResult = curl_exec($ch);
        curl_close($ch);
    $code= explode('&',explode('https://auth.tesla.com/void/callback?code=',$apiResult)[1])[0];

###Step 3: Exchange authorization code for bearer token

    $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->tokenUrlNew);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'Accept: application/json',
        'User-Agent: Mozilla/5.0 (Linux; Android 9.0.0; VS985 4G Build/LRX21Y; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36',
        'X-Tesla-User-Agent: TeslaApp/3.4.4-350/fad4a582e/android/9.0.0',
        ]);
      curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'grant_type' => 'authorization_code',
        'client_id' => 'ownerapi',
        'code' => $code,
        'code_verifier' => $code_verifier,
        'redirect_uri' => 'https://auth.tesla.com/void/callback'
      ]));

      $apiResult = curl_exec($ch);
      curl_close($ch);

      $apiResultJson = json_decode($apiResult, true);
      $BearerToken = $apiResultJson['access_token'];
      $RefreshToken = $apiResultJson['refresh_token'];

###Step 4: Exchange bearer token for access token

$ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $this->tokenUrl);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_TIMEOUT, 30);
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer '.$BearerToken,
        'Content-Type: application/json'
      ]);
      curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        'client_id' => getenv('TESLA_CLIENT_ID'),
        'client_secret' => getenv('TESLA_CLIENT_SECRET'),
      ]));

      $apiResult = curl_exec($ch);
      curl_close($ch);

      $apiResultJson = json_decode($apiResult, true);
      $apiResultJson['refresh_token']=$RefreshToken;
      #print_r($apiResultJson);exit;
      $this->accessToken = $apiResultJson['access_token'];

        return $apiResultJson;
    }

    public function refreshAccessToken(string $refreshToken)
    {
    $tesla = new Tesla();
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'https://pastebin.com/raw/pS7Z6yyP');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $result = curl_exec($ch);
    curl_close($ch);
    $api = explode(PHP_EOL,$result);
    $id=explode('=',$api[0]);
    $secret=explode('=',$api[1]);
    $tesla->setClientId(trim($id[1]));
    $tesla->setClientSecret(trim($secret[1]));

      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $this->tokenUrlNew);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_TIMEOUT, 30);
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'Accept: application/json',
        'User-Agent: Mozilla/5.0 (Linux; Android 9.0.0; VS985 4G Build/LRX21Y; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36',
        'X-Tesla-User-Agent: TeslaApp/3.4.4-350/fad4a582e/android/9.0.0',
      ]);
      #print 'XX '.getenv('TESLA_CLIENT_ID');exit;
      curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'grant_type' => 'refresh_token',
        'client_id' => 'ownerapi',
        'client_secret' => getenv('TESLA_CLIENT_SECRET'),
        'refresh_token' => $refreshToken,
        'scope' => 'openid email offline_access'
      ]));

      $apiResult = curl_exec($ch);
      $apiResultJson = json_decode($apiResult, true);

      curl_close($ch);
      #print_r($apiResult);exit;

      $apiResultJson = json_decode($apiResult, true);
      $BearerToken = $apiResultJson['access_token'];
      $RefreshToken = $apiResultJson['refresh_token'];

###Step 4: Exchange bearer token for access token

    $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->tokenUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer '.$BearerToken,
        'Content-Type: application/json'
        ]);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        'client_id' => getenv('TESLA_CLIENT_ID'),
        'client_secret' => getenv('TESLA_CLIENT_SECRET'),
        ]));

      $apiResult = curl_exec($ch);
      curl_close($ch);

      $apiResultJson = json_decode($apiResult, true);
      $apiResultJson['refresh_token']=$RefreshToken;
#print_r($apiResultJson);exit;
      $this->accessToken = $apiResultJson['access_token'];

        return $apiResultJson;
    }
ralfonso4 commented 3 years ago

Please go here for the latest code: https://github.com/timdorr/tesla-api/discussions/296

corsair commented 3 years ago

@ralfonso4 Hey there, would you be willing to submit a PR to implement this? Thanks for your contributions.

I would encourage switching back to the original usage of environmental variables as utilizing the Pastebin URL could be utilized as a MITM attack vector (and also breaks functionality if the service goes down).

Edit: Will note this does not provide support for accounts with 2FA nor handles errors relating to it.