tenantId}/oauth2/v2.0/token"; $body = [ 'client_id' => $credentials->clientId, 'client_secret' => $credentials->clientSecret, 'scope' => 'https://graph.microsoft.com/.default', 'grant_type' => 'client_credentials', ]; $requestHeaders = [ 'Content-Type' => 'application/x-www-form-urlencoded', ]; try { $response = Http::asForm()->post($url, $body); $transaction = [ 'name' => 'OAuth 2.0 Get Access Token', 'url' => $url, 'method' => 'POST', 'request_headers' => $requestHeaders, 'request_body' => collect($body)->except('client_secret')->put('client_secret', '********')->toArray(), 'status' => $response->status(), 'response_headers' => $response->headers(), 'response_body' => $response->json(), ]; if ($response->successful()) { return [ 'token' => $response->json('access_token'), 'transaction' => $transaction, ]; } } catch (Exception $e) { $transaction = [ 'name' => 'OAuth 2.0 Get Access Token', 'url' => $url, 'method' => 'POST', 'request_headers' => $requestHeaders, 'request_body' => collect($body)->except('client_secret')->put('client_secret', '********')->toArray(), 'status' => 'ERROR', 'response_body' => ['error' => $e->getMessage()], ]; } return [ 'token' => null, 'transaction' => $transaction, ]; } /** * Retrieve federation configuration for the domain. * * @return array{status: string, message?: string, config: ?array, transactions: array} */ public function getFederationConfig(MicrosoftGraphCredentialsData $credentials): array { $auth = $this->getAccessToken($credentials); $transactions = [$auth['transaction']]; if (! $auth['token']) { return [ 'status' => 'error', 'message' => 'Authentication with Microsoft Graph failed.', 'config' => null, 'transactions' => $transactions, ]; } $url = "https://graph.microsoft.com/v1.0/domains/{$credentials->domainName}/federationConfiguration"; $headers = [ 'Authorization' => 'Bearer ********', 'Content-Type' => 'application/json', ]; try { $response = Http::withToken($auth['token'])->get($url); $transactions[] = [ 'name' => 'Get Federation Configuration', 'url' => $url, 'method' => 'GET', 'request_headers' => $headers, 'request_body' => null, 'status' => $response->status(), 'response_headers' => $response->headers(), 'response_body' => $response->json(), ]; if ($response->successful()) { /** @var array $value */ $value = $response->json('value', []); return [ 'status' => 'success', 'config' => ! empty($value) ? $value[0] : null, 'transactions' => $transactions, ]; } /** @var string $errorMsg */ $errorMsg = $response->json('error.message', 'Failed to retrieve federation config.'); return [ 'status' => 'error', 'message' => $errorMsg, 'config' => null, 'transactions' => $transactions, ]; } catch (Exception $e) { $transactions[] = [ 'name' => 'Get Federation Configuration', 'url' => $url, 'method' => 'GET', 'request_headers' => $headers, 'request_body' => null, 'status' => 'ERROR', 'response_body' => ['error' => $e->getMessage()], ]; return [ 'status' => 'error', 'message' => $e->getMessage(), 'config' => null, 'transactions' => $transactions, ]; } } /** * Connect/Federate the domain. * * @return array{status: string, message?: string, config?: ?array, transactions: array} */ public function connectFederation( MicrosoftGraphCredentialsData $credentials, string $certPem, string $issuerUri, string $passiveSignInUri, string $activeSignInUri, string $signOutUri ): array { $auth = $this->getAccessToken($credentials); $transactions = [$auth['transaction']]; if (! $auth['token']) { return [ 'status' => 'error', 'message' => 'Authentication with Microsoft Graph failed.', 'transactions' => $transactions, ]; } // Check and delete existing configuration to ensure a clean setup $checkUrl = "https://graph.microsoft.com/v1.0/domains/{$credentials->domainName}/federationConfiguration"; try { $checkRes = Http::withToken($auth['token'])->get($checkUrl); $transactions[] = [ 'name' => 'Check Existing Federation Configuration', 'url' => $checkUrl, 'method' => 'GET', 'status' => $checkRes->status(), 'response_body' => $checkRes->json(), ]; if ($checkRes->successful()) { /** @var array $configs */ $configs = $checkRes->json('value', []); if (! empty($configs)) { /** @var string $existingId */ $existingId = $configs[0]['id']; $deleteUrl = "https://graph.microsoft.com/v1.0/domains/{$credentials->domainName}/federationConfiguration/{$existingId}"; $deleteRes = Http::withToken($auth['token'])->delete($deleteUrl); $transactions[] = [ 'name' => 'Delete Pre-existing Federation Configuration', 'url' => $deleteUrl, 'method' => 'DELETE', 'status' => $deleteRes->status(), 'response_body' => $deleteRes->json() ?: ['message' => 'Deleted successfully'], ]; if (! $deleteRes->successful()) { return [ 'status' => 'error', 'message' => 'Failed to delete pre-existing federation configuration.', 'transactions' => $transactions, ]; } } } } catch (Exception $e) { // Suppress exception and proceed } // Prepare the signing certificate PEM string $signingCertificateClean = str_replace( ['-----BEGIN CERTIFICATE-----', '-----END CERTIFICATE-----', "\r", "\n", ' '], '', $certPem ); $url = "https://graph.microsoft.com/v1.0/domains/{$credentials->domainName}/federationConfiguration"; $body = [ '@odata.type' => '#microsoft.graph.internalDomainFederation', 'displayName' => $credentials->displayName, 'issuerUri' => $issuerUri, 'signingCertificate' => $signingCertificateClean, 'passiveSignInUri' => $passiveSignInUri, 'preferredAuthenticationProtocol' => 'saml', 'activeSignInUri' => $activeSignInUri, 'signOutUri' => $signOutUri, 'promptLoginBehavior' => 'nativeSupport', 'isSignedAuthenticationRequestRequired' => false, 'federatedIdpMfaBehavior' => $credentials->mfaBehavior->value, ]; $headers = [ 'Authorization' => 'Bearer ********', 'Content-Type' => 'application/json', ]; try { $response = Http::withToken($auth['token'])->post($url, $body); $transactions[] = [ 'name' => 'Create Federation Configuration', 'url' => $url, 'method' => 'POST', 'request_headers' => $headers, 'request_body' => $body, 'status' => $response->status(), 'response_headers' => $response->headers(), 'response_body' => $response->json(), ]; if ($response->successful()) { return [ 'status' => 'success', 'config' => $response->json(), 'transactions' => $transactions, ]; } /** @var string $errorMsg */ $errorMsg = $response->json('error.message', 'Failed to create federation configuration.'); return [ 'status' => 'error', 'message' => $errorMsg, 'transactions' => $transactions, ]; } catch (Exception $e) { $transactions[] = [ 'name' => 'Create Federation Configuration', 'url' => $url, 'method' => 'POST', 'request_headers' => $headers, 'request_body' => $body, 'status' => 'ERROR', 'response_body' => ['error' => $e->getMessage()], ]; return [ 'status' => 'error', 'message' => $e->getMessage(), 'transactions' => $transactions, ]; } } /** * Disconnect/Remove federation configuration. * * @return array{status: string, message?: string, transactions: array} */ public function disconnectFederation(MicrosoftGraphCredentialsData $credentials, string $federationId): array { $auth = $this->getAccessToken($credentials); $transactions = [$auth['transaction']]; if (! $auth['token']) { return [ 'status' => 'error', 'message' => 'Authentication with Microsoft Graph failed.', 'transactions' => $transactions, ]; } $url = "https://graph.microsoft.com/v1.0/domains/{$credentials->domainName}/federationConfiguration/{$federationId}"; $headers = [ 'Authorization' => 'Bearer ********', ]; try { $response = Http::withToken($auth['token'])->delete($url); $transactions[] = [ 'name' => 'Delete Federation Configuration', 'url' => $url, 'method' => 'DELETE', 'request_headers' => $headers, 'request_body' => null, 'status' => $response->status(), 'response_headers' => $response->headers(), 'response_body' => $response->json() ?: ['message' => 'Federation removed successfully. Domain returned to Managed.'], ]; if ($response->successful()) { return [ 'status' => 'success', 'transactions' => $transactions, ]; } /** @var string $errorMsg */ $errorMsg = $response->json('error.message', 'Failed to remove federation configuration.'); return [ 'status' => 'error', 'message' => $errorMsg, 'transactions' => $transactions, ]; } catch (Exception $e) { $transactions[] = [ 'name' => 'Delete Federation Configuration', 'url' => $url, 'method' => 'DELETE', 'request_headers' => $headers, 'request_body' => null, 'status' => 'ERROR', 'response_body' => ['error' => $e->getMessage()], ]; return [ 'status' => 'error', 'message' => $e->getMessage(), 'transactions' => $transactions, ]; } } }