api( 'user', 'update', [ 'attributes' => [ 'user_id' => $this->session->get_user_id(), 'ecobee_account_id' => $this->get_ecobee_account_id($attributes) ] ] ); return parent::create($attributes); } /** * This should be called when connecting a new user. Get the access/refresh * tokens, then attach them to a brand new anonymous user. * * @param string $code The code from ecobee used to obtain the * access/refresh tokens. * * @return array The access/refresh tokens. */ public function obtain($code) { // Obtain the access and refresh tokens from the authorization code. $response = $this->api( 'ecobee', 'ecobee_api', [ 'method' => 'POST', 'endpoint' => 'token', 'arguments' => [ 'grant_type' => 'authorization_code', 'code' => $code, 'redirect_uri' => $this->setting->get('ecobee_redirect_uri') ] ] ); // Make sure we got the expected result. if ( isset($response['access_token']) === false || isset($response['refresh_token']) === false ) { throw new cora\exception('Could not get first ecobee token.', 10000); } return [ 'access_token' => $response['access_token'], 'refresh_token' => $response['refresh_token'], 'ecobee_account_id' => $this->get_ecobee_account_id($response), 'timestamp' => date('Y-m-d H:i:s'), 'deleted' => 0 ]; } /** * Get an ecobee_account_id from the ecobee JWT. Check a bunch of stuff to * make sure it's valid. * * @param ecobee_token $ecobee_token The ecobee_token. * * @return string The ecobee_account_id. */ public function get_ecobee_account_id($ecobee_token) { $parts = explode('.', $ecobee_token['access_token']); if(count($parts) !== 3) { return null; } $payload = $parts[1]; $payload = str_replace(['_', '-'], ['/', '+'], $payload); $json = base64_decode($payload); if($json === false) { return null; } $object = json_decode($json, true); if($object === null) { return null; } if(isset($object['sub']) === false) { return null; } $sub_parts = explode('|', $object['sub']); if(count($sub_parts) !== 2) { return null; } $ecobee_account_id = $sub_parts[1]; if(strlen($ecobee_account_id) !== 36) { return null; } return $ecobee_account_id; } /** * Get some new tokens. A database lock is obtained prior to getting a token * so that no other API call can attempt to get a token at the same time. * This way if two API calls fire off to ecobee at the same time, then * return at the same time, then call token->refresh() at the same time, * only one can run and actually refresh at a time. If the transactionless * one runs after that's fine as it will look up the token prior to * refreshing. * * @return array The new token. */ public function refresh() { $database = cora\database::get_transactionless_instance(); $lock_name = 'ecobee_token->refresh(' . $this->session->get_user_id() . ')'; $database->get_lock($lock_name, 3); $ecobee_tokens = $database->read( 'ecobee_token', [ 'user_id' => $this->session->get_user_id(), 'deleted' => false ] ); if(count($ecobee_tokens) === 0) { throw new cora\exception('Could not refresh ecobee token; no token found.', 10001); } $ecobee_token = $ecobee_tokens[0]; $response = $this->api( 'ecobee', 'ecobee_api', [ 'method' => 'POST', 'endpoint' => 'token', 'arguments' => [ 'ecobee_type' => 'jwt', 'grant_type' => 'refresh_token', 'refresh_token' => $ecobee_token['refresh_token'] ] ] ); if ( isset($response['access_token']) === false || isset($response['refresh_token']) === false ) { $this->delete($ecobee_token['ecobee_token_id']); $database->release_lock($lock_name); throw new cora\exception('Could not refresh ecobee token; ecobee returned no token.', 10002, true, null, false); } $ecobee_token = $database->update( 'ecobee_token', [ 'ecobee_token_id' => $ecobee_token['ecobee_token_id'], 'access_token' => $response['access_token'], 'refresh_token' => $response['refresh_token'], 'timestamp' => date('Y-m-d H:i:s') ] ); $this->api( 'user', 'update', [ 'attributes' => [ 'user_id' => $this->session->get_user_id(), 'ecobee_account_id' => $this->get_ecobee_account_id($ecobee_token) ] ] ); $database->release_lock($lock_name); return $ecobee_token; } /** * Delete an ecobee token and log the user out. Make sure to delete the * token prior to logging out so the right permissions are present. * * @param int $id * * @return int */ public function delete($id) { $return = parent::delete($id); $this->api('user', 'log_out'); return $return; } }