Skip to content

Commit

Permalink
#105 CallerId is broken #42
Browse files Browse the repository at this point in the history
  • Loading branch information
avarapai committed Aug 30, 2024
1 parent 2458c92 commit 6e84293
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 16 deletions.
32 changes: 32 additions & 0 deletions src/Enum/UpsertBehaviorEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php
/**
* Copyright 2018 AlexaCRM
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
* OR OTHER DEALINGS IN THE SOFTWARE.
*
*/

namespace AlexaCRM\Enum;

/**
* Specifies the behavior for an Upsert operation.
*/
enum UpsertBehaviorEnum: int {

case CreateOrUpdate = 0;
case PreventUpdate = 1;
case PreventCreate = 2;
}
70 changes: 66 additions & 4 deletions src/WebAPI/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

namespace AlexaCRM\WebAPI;

use AlexaCRM\Enum\UpsertBehaviorEnum;
use AlexaCRM\WebAPI\OData\AuthenticationException;
use AlexaCRM\WebAPI\OData\Client as ODataClient;
use AlexaCRM\WebAPI\OData\EntityNotSupportedException;
Expand Down Expand Up @@ -49,13 +50,20 @@ class Client implements IOrganizationService {
*/
protected ODataClient $client;

protected ?string $MSCRMCallerID;

protected ?string $callerObjectId;

protected UpsertBehaviorEnum $upsertBehavior;

/**
* Client constructor.
*
* @param ODataClient $client
*/
public function __construct( ODataClient $client ) {
$this->client = $client;
$this->upsertBehavior = UpsertBehaviorEnum::CreateOrUpdate;
}

/**
Expand Down Expand Up @@ -107,7 +115,7 @@ public function Create( Entity $entity ): string {
$translatedData = $serializer->serializeEntity( $entity );

$collectionName = $this->client->getMetadata()->getEntitySetName( $entity->LogicalName );

$this->updateWebApiHeaderRequest();
$responseId = $this->client->create( $collectionName, $translatedData );

$entity->getAttributeState()->reset();
Expand Down Expand Up @@ -233,9 +241,8 @@ public function Retrieve( string $entityName, string $entityId, ColumnSet $colum
$response = $this->client->getRecord( $collectionName, $entityId, $options );

$serializer = new SerializationHelper( $this->client );
$entity = $serializer->deserializeEntity( $response, new EntityReference( $entityName, $entityId ) );

return $entity;
return $serializer->deserializeEntity( $response, new EntityReference( $entityName, $entityId ) );
} catch ( ODataException $e ) {
if ( $e->getCode() === 404 ) {
return null;
Expand Down Expand Up @@ -388,7 +395,7 @@ protected function retrieveViaQueryByAttribute( QueryByAttribute $query ): Entit
$queryValue = "'{$value}'";
break;
case is_bool( $value ):
$queryValue = $value? 'true' : 'false';
$queryValue = $value ? 'true' : 'false';
break;
case $value === null:
$queryValue = 'null';
Expand Down Expand Up @@ -491,6 +498,30 @@ protected function retrieveViaQueryByAttribute( QueryByAttribute $query ): Entit
}
}

/**
* Updating WebApi Request Headers
*
* @return void
*/
private function updateWebApiHeaderRequest(): void {
switch ( $this->upsertBehavior ) {
case UpsertBehaviorEnum::PreventCreate:
$this->client->setWebApiHeaderByName( "If-Match", "*" );
break;
case UpsertBehaviorEnum::PreventUpdate:
$this->client->setWebApiHeaderByName( "If-None-Match", "*" );
break;
default:
break;
}

if ( $this->callerObjectId !== null ) {
$this->client->setWebApiHeaderByName( 'CallerObjectId', $this->callerObjectId );
} elseif ( $this->MSCRMCallerID !== null ) {
$this->client->setWebApiHeaderByName( 'MSCRMCallerID', $this->MSCRMCallerID );
}
}

/**
* Updates an existing record.
*
Expand All @@ -508,6 +539,7 @@ public function Update( Entity $entity ): void {

$collectionName = $this->client->getMetadata()->getEntitySetName( $entity->LogicalName );

$this->updateWebApiHeaderRequest();
$this->client->update( $collectionName, $entity->Id, $translatedData );

$entity->getAttributeState()->reset();
Expand Down Expand Up @@ -536,4 +568,34 @@ public function getCachePool(): CacheItemPoolInterface {
return $this->client->getCachePool();
}

public function getMSCRMCallerID(): ?string {
return $this->MSCRMCallerID;
}

public function setMSCRMCallerID( ?string $MSCRMCallerID ): void {
try {
//search Microsoft Entra ID object ID
$entity = $this->Retrieve( 'systemuser', $MSCRMCallerID, new ColumnSet( [ 'azureactivedirectoryobjectid' ] ) );
$this->callerObjectId = $entity?->Attributes['azureactivedirectoryobjectid'];
} catch ( Exception ) {
}
$this->MSCRMCallerID = $MSCRMCallerID;
}

public function getCallerObjectId(): ?string {
return $this->callerObjectId;
}

public function setCallerObjectId( ?string $callerObjectId ): void {
$this->callerObjectId = $callerObjectId;
}

public function getUpsertBehavior(): UpsertBehaviorEnum {
return $this->upsertBehavior;
}

public function setUpsertBehavior( UpsertBehaviorEnum $upsertBehavior ): void {
$this->upsertBehavior = $upsertBehavior;
}

}
70 changes: 60 additions & 10 deletions src/WebAPI/OData/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\HandlerStack;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;

Expand Down Expand Up @@ -66,6 +67,13 @@ class Client {
*/
protected array $middlewares = [];

/**
* header to Web API request
*
* @var array
*/
protected array $webApiHeaders = [];

/**
* Client constructor.
*
Expand Down Expand Up @@ -129,8 +137,11 @@ public function getHttpClient(): HttpClient {
/**
* Retrieves OData Service Metadata.
*
* @return Metadata
* @throws AuthenticationException
* @throws GuzzleException
* @throws TransportException
* @throws InvalidArgumentException
*/
public function getMetadata(): Metadata {
if ( $this->metadata instanceof Metadata ) {
Expand Down Expand Up @@ -178,6 +189,34 @@ public function getMetadata(): Metadata {
return $this->metadata;
}

/**
* Get header for Web Api Request
*
* @param array|null $headers
* @param string|null $method
*
* @return array
*/
private function getRequestHeaders( ?array $headers = [], ?string $method = 'GET' ): array {
if ( in_array( $method, [ 'POST', 'PATCH' ] ) ) {
$headers = array_merge( [ 'Content-Type' => 'application/json' ], $headers );
}

//Odata/Client/Setting more important than the headers in the request
if ( $this->settings->callerObjectId || $this->settings->callerID ) {
$this->unsetWebApiHeaderByName( 'CallerObjectId' );
$this->unsetWebApiHeaderByName( 'MSCRMCallerID' );
}

if ( $this->settings->callerObjectId !== null ) {
$headers = array_merge( [ 'CallerObjectId' => $this->settings->callerObjectId ], $headers );
} elseif ( $this->settings->callerID !== null ) {
$headers = array_merge( [ 'MSCRMCallerID' => $this->settings->callerID ], $headers );
}

return $headers;
}

/**
* @param string $method
* @param string $url
Expand All @@ -190,13 +229,7 @@ public function getMetadata(): Metadata {
* @throws TransportException
*/
private function doRequest( string $method, string $url, $data = null, array $headers = [] ): ResponseInterface {
if ( in_array( $method, [ 'POST', 'PATCH' ] ) ) {
$headers = array_merge( [ 'Content-Type' => 'application/json' ], $headers );
}

if ( $this->settings->callerID !== null ) {
$headers = array_merge( [ 'MSCRMCallerID' => '$this->settings->callerID' ], $headers );
}
$headers = $this->getRequestHeaders( $headers, $method );

try {
$payload = [
Expand Down Expand Up @@ -577,7 +610,7 @@ public function associate(
string $fromEntityId,
string $navProperty,
string $toEntityCollection,
string $toEntityId
string $toEntityId,
): void {
$url = sprintf( '%s%s(%s)/%s/$ref', $this->settings->getEndpointURI(), $fromEntityCollection, $fromEntityId, $navProperty );
$data = [ Annotation::ODATA_ID => sprintf( '%s%s(%s)', $this->settings->getEndpointURI(), $toEntityCollection, $toEntityId ) ];
Expand All @@ -600,9 +633,10 @@ public function disassociate(
string $fromEntityId,
string $navProperty,
string $toEntityCollection,
string $toEntityId
string $toEntityId,
): void {
$url = sprintf( '%s%s(%s)/%s/$ref?$id=%s%s(%s)', $this->settings->getEndpointURI(), $fromEntityCollection, $fromEntityId, $navProperty, $this->settings->getEndpointURI(), $toEntityCollection, $toEntityId );
$url = sprintf( '%s%s(%s)/%s/$ref?$id=%s%s(%s)', $this->settings->getEndpointURI(), $fromEntityCollection, $fromEntityId, $navProperty, $this->settings->getEndpointURI(),
$toEntityCollection, $toEntityId );
$this->doRequest( 'DELETE', $url );
}

Expand Down Expand Up @@ -795,4 +829,20 @@ protected static function getEntityId( ResponseInterface $response ): ?string {
return $id;
}

public function getWebApiHeaderByName( $headerName ): array {
return $this?->webApiHeaders[ $headerName ];
}

public function unsetWebApiHeaderByName( $headerName ): void {
unset( $this->webApiHeaders[ $headerName ] );
}

public function setWebApiHeaderByName( $headerName, $headerValue ): void {
$this->webApiHeaders[ $headerName ] = $headerValue;
}

public function getWebApiHeaders(): array {
return $this->webApiHeaders;
}

}
11 changes: 9 additions & 2 deletions src/WebAPI/OData/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,19 @@ abstract class Settings implements LoggerAwareInterface {
public LoggerInterface $logger;

/**
* ID of the user to impersonate during calls.
*
* ID of the user (systemuserid) to impersonate during calls.
* @deprecated
* Null value means impersonation is not performed.
*/
public ?string $callerID = null;

/**
* Microsoft Entra ID object ID (Azure AD Object ID) - azureactivedirectoryobjectid
*
* Null value means impersonation is not performed.
*/
public ?string $callerObjectId = null;

/**
* Settings constructor.
*/
Expand Down

0 comments on commit 6e84293

Please sign in to comment.