Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bf81c1a
Implement BRTC Endpoints API with models, controllers, and tests
smoghe-bw Mar 11, 2026
e4e4382
Refactor Connect and Endpoint classes to make toBxml method public; a…
smoghe-bw Mar 11, 2026
69f3305
Refactor APIController to integrate BRTC endpoint methods; remove End…
smoghe-bw Mar 11, 2026
3c425ff
Update APIController to use PHONENUMBERLOOKUPDEFAULT server for endpo…
smoghe-bw Mar 11, 2026
e77d05d
Add error handling for createEndpoint in ApiTest to improve test reli…
smoghe-bw Mar 11, 2026
8bcf942
Refactor ApiTest to use endpointClient for endpoint-related tests
smoghe-bw Mar 11, 2026
c5a405a
Trigger build
smoghe-bw Mar 12, 2026
8ceb7c7
Add BRTC OAuth2 token management to Configuration and BaseController;…
smoghe-bw Mar 16, 2026
b404ff9
Remove BRTC OAuth2 access token management from Configuration and Bas…
smoghe-bw Mar 16, 2026
2424fa7
Refactor ApiTest to simplify error handling by removing apiFailMsg me…
smoghe-bw Mar 16, 2026
f625ed8
Trigger build
smoghe-bw Mar 17, 2026
d477bd1
Fix authorization header configuration in BaseController for token ma…
smoghe-bw Mar 17, 2026
13f7408
Refactor authentication methods in BaseController and APIController t…
smoghe-bw Mar 17, 2026
c516d6d
Clear global basic auth before BRTC token exchange and handle token r…
smoghe-bw Mar 17, 2026
ef6edf9
Simplify token request body format in BaseController for BRTC authent…
smoghe-bw Mar 17, 2026
3d8f45a
Refactor BRTC token exchange to use cURL for improved error handling …
smoghe-bw Mar 17, 2026
741accb
Refactor configureBrtcAuth to streamline access token handling and im…
smoghe-bw Mar 17, 2026
8eb21da
Refactor configureBrtcAuth to remove access token expiration check an…
smoghe-bw Mar 17, 2026
d4b7b47
Clear global Basic auth before setting Bearer token in configureBrtcA…
smoghe-bw Mar 17, 2026
d7d9913
Rename configureBrtcAuth to configureOAuth2Auth for clarity and updat…
smoghe-bw Mar 18, 2026
dcd3fee
Refactor grant_type parameter in configureOAuth2Auth to use associati…
smoghe-bw Mar 18, 2026
486744b
Remove User-Agent header from OAuth2 token request for cleaner API calls
smoghe-bw Mar 18, 2026
f21f31a
Refactor grant_type parameter in configureOAuth2Auth to use Request\B…
smoghe-bw Mar 18, 2026
c7f870b
Update grant_type parameter in configureOAuth2Auth to use query strin…
smoghe-bw Mar 18, 2026
c7433fb
Clear global Basic auth in configureOAuth2Auth to prevent header over…
smoghe-bw Mar 18, 2026
ad2c358
Refactor endpoint properties and update API response handling for con…
smoghe-bw Mar 18, 2026
107f54f
Update authentication handling to use Request\Body::form for grant_ty…
smoghe-bw Mar 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ jobs:
BW_ACCOUNT_ID: ${{ secrets.BW_ACCOUNT_ID }}
BW_USERNAME: ${{ secrets.BW_USERNAME }}
BW_PASSWORD: ${{ secrets.BW_PASSWORD }}
BW_CLIENT_ID: ${{ secrets.BW_CLIENT_ID }}
BW_CLIENT_SECRET: ${{ secrets.BW_CLIENT_SECRET }}
BW_VOICE_APPLICATION_ID: ${{ secrets.BW_VOICE_APPLICATION_ID }}
BW_MESSAGING_APPLICATION_ID: ${{ secrets.BW_MESSAGING_APPLICATION_ID }}
BW_NUMBER: ${{ secrets.BW_NUMBER }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ composer.lock
.phpunit.result.cache
composer.phar
.idea
.env*
69 changes: 39 additions & 30 deletions src/Controllers/BaseController.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,37 +82,9 @@ protected function validateResponse(HttpResponse $response, HttpContext $_httpCo
*/
protected function configureAuth(&$headers, $authType)
{
if (!empty($this->config->getAccessToken()) &&
(empty($this->config->getAccessTokenExpiration()) ||
$this->config->getAccessTokenExpiration() > time() + 60)
) {
$headers['Authorization'] = 'Bearer ' . $this->config->getAccessToken();
return;
}

if (!empty($this->config->getClientId()) && !empty($this->config->getClientSecret())) {
$_tokenUrl = 'https://api.bandwidth.com/api/v1/oauth2/token';
$_tokenHeaders = array (
'User-Agent' => BaseController::USER_AGENT,
'Content-Type' => 'application/x-www-form-urlencoded',
'Authorization' => 'Basic ' . base64_encode(
$this->config->getClientId() . ':' . $this->config->getClientSecret()
)
);
$_tokenBody = Request\Body::Form([
'grant_type' => 'client_credentials'
]);
$response = Request::post($_tokenUrl, $_tokenHeaders, $_tokenBody);
$this->config->setAccessToken($response->body->access_token);
$this->config->setAccessTokenExpiration(time() + $response->body->expires_in);
$headers['Authorization'] = 'Bearer ' . $this->config->getAccessToken();

return;
}

$username = '';
$password = '';

switch ($authType) {
case 'messaging':
$username = $this->config->getMessagingBasicAuthUserName();
Expand All @@ -135,7 +107,44 @@ protected function configureAuth(&$headers, $authType)
$password = $this->config->getMultiFactorAuthBasicAuthPassword();
break;
}

Request::auth($username, $password);
}

/**
* Configure OAuth2 Bearer auth for BRTC endpoints using client credentials.
* Sets the Authorization header directly to avoid conflicts with Unirest's
* global Request::auth() state.
*
* @param array $headers The headers for the request (passed by reference)
*/
protected function configureOAuth2Auth(&$headers)
{
// Clear any global state set by prior configureAuth() calls so
// Unirest doesn't interfere with the token request.
Request::auth('', '');
Request::clearDefaultHeaders();
$response = Request::post(
'https://api.bandwidth.com/api/v1/oauth2/token',
[
'Content-Type' => 'application/x-www-form-urlencoded',
'Authorization' => 'Basic ' . base64_encode(
$this->config->getClientId() . ':' . $this->config->getClientSecret()
),
],
\Unirest\Request\Body::Form(['grant_type' => 'client_credentials'])
);

if ($response->code < 200 || $response->code > 299 || !isset($response->body->access_token)) {
throw new \RuntimeException(
"OAuth2 token request failed | status: {$response->code} | response: {$response->raw_body}"
);
}

$this->config->setAccessToken($response->body->access_token);
$this->config->setAccessTokenExpiration(time() + $response->body->expires_in);

$headers['Authorization'] = 'Bearer ' . $response->body->access_token;
}

}
54 changes: 54 additions & 0 deletions src/Voice/Bxml/Connect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
/**
* Connect.php
*
* Implementation of the BXML Connect verb for BRTC endpoints
*
* @copyright Bandwidth INC
*/

namespace BandwidthLib\Voice\Bxml;

use DOMDocument;
use DOMElement;

require_once "Verb.php";

class Connect extends Verb {
/**
* @var array
*/
private $endpoints;

/**
* @param array $endpoints Array of Endpoint objects
*/
public function __construct(array $endpoints = []) {
$this->endpoints = $endpoints;
}

/**
* Add an Endpoint to the Connect verb
*
* @param Endpoint $endpoint
* @return $this
*/
public function addEndpoint(Endpoint $endpoint): Connect {
$this->endpoints[] = $endpoint;
return $this;
}

/**
* Converts the Connect verb into a DOMElement
*
* @param DOMDocument $doc
* @return DOMElement
*/
public function toBxml(DOMDocument $doc): DOMElement {
$element = $doc->createElement("Connect");
foreach ($this->endpoints as $endpoint) {
$element->appendChild($endpoint->toBxml($doc));
}
return $element;
}
}
41 changes: 41 additions & 0 deletions src/Voice/Bxml/Endpoint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
/**
* Endpoint.php
*
* Implementation of the BXML Endpoint verb for BRTC endpoints
*
* @copyright Bandwidth INC
*/

namespace BandwidthLib\Voice\Bxml;

use DOMDocument;
use DOMElement;

require_once "Verb.php";

class Endpoint extends Verb {
/**
* @var string
*/
private $id;

/**
* @param string $id The endpointId to connect to
*/
public function __construct(string $id) {
$this->id = $id;
}

/**
* Converts the Endpoint verb into a DOMElement
*
* @param DOMDocument $doc
* @return DOMElement
*/
public function toBxml(DOMDocument $doc): DOMElement {
$element = $doc->createElement("Endpoint");
$element->setAttribute("id", $this->id);
return $element;
}
}
Loading
Loading