Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
199e46cb75 | ||
|
|
1201afcfd8 | ||
|
|
2e0e56a927 | ||
|
|
923befa860 | ||
|
|
6decd3c147 | ||
| 8d402afd99 |
163
actions.php
Normal file
@ -0,0 +1,163 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Log Your Action - Chirivia</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--primary-green: #2E8B57;
|
||||
--secondary-blue: #4682B4;
|
||||
--accent-color: #5F9EA0;
|
||||
--background-light: #F0F8FF;
|
||||
--surface-white: #FFFFFF;
|
||||
--text-dark: #2F4F4F;
|
||||
--font-headings: 'Inter', sans-serif;
|
||||
--font-body: 'Inter', sans-serif;
|
||||
--spacing-base: 8px;
|
||||
--border-radius: 0.5rem; /* 8px */
|
||||
--danger-red: #DC3545;
|
||||
--neutral-gray: #A9A9A9;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--background-light);
|
||||
color: var(--text-dark);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.header {
|
||||
background: var(--surface-white);
|
||||
padding: 1rem 2rem;
|
||||
border-bottom: 1px solid #E0E0E0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.header .logo {
|
||||
font-family: var(--font-headings);
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: var(--primary-green);
|
||||
text-decoration: none;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
.action-container {
|
||||
background: var(--surface-white);
|
||||
padding: 2rem;
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||
text-align: center;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--font-headings);
|
||||
color: var(--primary-green);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.message {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.form-container form {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
.btn {
|
||||
font-family: var(--font-body);
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
color: var(--surface-white);
|
||||
}
|
||||
.btn-yes {
|
||||
background-color: var(--primary-green);
|
||||
}
|
||||
.btn-no {
|
||||
background-color: var(--neutral-gray);
|
||||
}
|
||||
.result-message {
|
||||
margin-top: 2rem;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
.result-message .chick {
|
||||
font-size: 2rem;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 1.5rem;
|
||||
background: var(--surface-white);
|
||||
border-top: 1px solid #E0E0E0;
|
||||
font-size: 0.9rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="header">
|
||||
<a href="index.php" class="logo">Chirivia</a>
|
||||
</header>
|
||||
|
||||
<main class="main">
|
||||
<div class="action-container">
|
||||
<h1>Log Your Green Action</h1>
|
||||
|
||||
<?php
|
||||
$message = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['answer'])) {
|
||||
$answer = $_POST['answer'];
|
||||
if ($answer === 'yes') {
|
||||
$message = '<div class="chick">🐣</div> says thank you!';
|
||||
} elseif ($answer === 'no') {
|
||||
$message = '<div class="chick">🐣</div> says you should go do it asap!';
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="result-message">
|
||||
<?php echo $message; ?>
|
||||
<br><br>
|
||||
<a href="actions.php" class="btn btn-yes">Log another action</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p class="message">Did you do something good for the environment today?</p>
|
||||
<div class="form-container">
|
||||
<form method="POST" action="actions.php">
|
||||
<button type="submit" name="answer" value="yes" class="btn btn-yes">Yes, I did!</button>
|
||||
<button type="submit" name="answer" value="no" class="btn btn-no">Not yet</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<p>© <?php echo date("Y"); ?> Chirivia. All rights reserved. | <a href="privacy.php">Privacy Policy</a></p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
BIN
assets/pasted-20251004-192255-8c7c39e6.webp
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
assets/pasted-20251004-214303-680a8e12.webp
Normal file
|
After Width: | Height: | Size: 260 KiB |
BIN
assets/pasted-20251004-214642-63e1d23c.webp
Normal file
|
After Width: | Height: | Size: 192 KiB |
BIN
assets/pasted-20251004-222033-3b718197.webp
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
assets/pasted-20251004-231356-34be6417.webp
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
assets/pasted-20251004-232116-3451ebca.jpg
Normal file
|
After Width: | Height: | Size: 193 KiB |
BIN
assets/pasted-20251004-232406-9a846679.png
Normal file
|
After Width: | Height: | Size: 176 KiB |
BIN
assets/pasted-20251005-003013-33f48a9a.png
Normal file
|
After Width: | Height: | Size: 176 KiB |
BIN
assets/pasted-20251005-003524-d4bed2e1.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
assets/pasted-20251005-003819-6e811280.png
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
assets/pasted-20251005-004414-8785f3f1.png
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
assets/pasted-20251005-145045-e02a01f3.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
assets/pasted-20251005-170401-70cc8986.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
assets/vm-shot-2025-10-04T19-17-47-673Z.jpg
Normal file
|
After Width: | Height: | Size: 150 KiB |
92
chatbot.php
Normal file
@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Chatbot</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f0f2f5; }
|
||||
#chat-container { width: 400px; height: 600px; border: 1px solid #ccc; border-radius: 8px; display: flex; flex-direction: column; background-color: #fff; box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
|
||||
#chat-log { flex-grow: 1; padding: 10px; overflow-y: auto; border-bottom: 1px solid #ccc; }
|
||||
#chat-input-container { display: flex; padding: 10px; }
|
||||
#chat-input { flex-grow: 1; border: 1px solid #ccc; border-radius: 4px; padding: 8px; }
|
||||
#send-button { margin-left: 10px; padding: 8px 12px; border: none; background-color: #007bff; color: white; border-radius: 4px; cursor: pointer; }
|
||||
.message { margin-bottom: 10px; padding: 8px 12px; border-radius: 18px; max-width: 70%; word-wrap: break-word; }
|
||||
.user-message { background-color: #007bff; color: white; align-self: flex-end; margin-left: auto; }
|
||||
.bot-message { background-color: #e9e9eb; color: black; align-self: flex-start; }
|
||||
.chat-log-inner { display: flex; flex-direction: column; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="chat-container">
|
||||
<div id="chat-log">
|
||||
<div class="chat-log-inner"></div>
|
||||
</div>
|
||||
<div id="chat-input-container">
|
||||
<input type="text" id="chat-input" placeholder="Type a message...">
|
||||
<button id="send-button">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const chatLog = document.querySelector('#chat-log .chat-log-inner');
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
const sendButton = document.getElementById('send-button');
|
||||
|
||||
const appendMessage = (text, sender) => {
|
||||
const messageElement = document.createElement('div');
|
||||
messageElement.classList.add('message', sender + '-message');
|
||||
messageElement.textContent = text;
|
||||
chatLog.appendChild(messageElement);
|
||||
chatLog.parentElement.scrollTop = chatLog.parentElement.scrollHeight;
|
||||
};
|
||||
|
||||
const sendMessage = async () => {
|
||||
const messageText = chatInput.value.trim();
|
||||
if (messageText === '') return;
|
||||
|
||||
appendMessage(messageText, 'user');
|
||||
chatInput.value = '';
|
||||
|
||||
try {
|
||||
const response = await fetch('chatbot_api.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ message: messageText })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const botResponse = data.reply || 'Sorry, I could not understand.';
|
||||
appendMessage(botResponse, 'bot');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fetch error:', error);
|
||||
let errorMessage = 'Sorry, something went wrong. Please try again later.';
|
||||
if (error instanceof TypeError) { // Network error
|
||||
errorMessage = 'Could not connect to the server. Please check your network connection.';
|
||||
} else if (error instanceof SyntaxError) { // JSON parsing error
|
||||
errorMessage = 'Received an invalid response from the server.';
|
||||
} else if (error.message.startsWith('HTTP error!')) {
|
||||
errorMessage = 'There was a problem communicating with the server.';
|
||||
}
|
||||
appendMessage(errorMessage, 'bot');
|
||||
}
|
||||
};
|
||||
|
||||
sendButton.addEventListener('click', sendMessage);
|
||||
chatInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
|
||||
appendMessage("Hello! I'm a chatbot. How can I help you today?", "bot");
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
73
chatbot_api.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
// Set content type to JSON
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Include the Composer autoloader
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use Google\Cloud\Dialogflow\V2\Client\SessionsClient;
|
||||
use Google\Cloud\Dialogflow\V2\TextInput;
|
||||
use Google\Cloud\Dialogflow\V2\QueryInput;
|
||||
|
||||
function detect_intent_texts($projectId, $text, $sessionId, $languageCode = 'en-US')
|
||||
{
|
||||
// Set the path to your service account key file
|
||||
$credentialsPath = __DIR__ . '/gcp_creds/dialogflow_key.json';
|
||||
|
||||
// Check if the credentials file exists
|
||||
if (!file_exists($credentialsPath)) {
|
||||
return "Error: Service account key file not found.";
|
||||
}
|
||||
|
||||
try {
|
||||
// Create a new sessions client
|
||||
$sessionsClient = new SessionsClient([
|
||||
'credentials' => $credentialsPath
|
||||
]);
|
||||
|
||||
// Format the session name
|
||||
$session = $sessionsClient->sessionName($projectId, $sessionId);
|
||||
|
||||
// Create a new text input
|
||||
$textInput = new TextInput();
|
||||
$textInput->setText($text);
|
||||
$textInput->setLanguageCode($languageCode);
|
||||
|
||||
// Create a new query input
|
||||
$queryInput = new QueryInput();
|
||||
$queryInput->setText($textInput);
|
||||
|
||||
// Detect the intent
|
||||
$response = $sessionsClient->detectIntent($session, $queryInput);
|
||||
$queryResult = $response->getQueryResult();
|
||||
$fulfillmentText = $queryResult->getFulfillmentText();
|
||||
|
||||
// Close the sessions client
|
||||
$sessionsClient->close();
|
||||
|
||||
return $fulfillmentText;
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Return a generic error message
|
||||
error_log($e->getMessage());
|
||||
return "Error processing your request.";
|
||||
}
|
||||
}
|
||||
|
||||
// Get the user's message from the POST request
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$userMessage = $data['message'] ?? '';
|
||||
$sessionId = $data['sessionId'] ?? session_id(); // Use PHP session ID as Dialogflow session ID
|
||||
|
||||
// Your Google Cloud Project ID
|
||||
$projectId = 'chrivia-asxi';
|
||||
|
||||
// Get the bot's reply from Dialogflow
|
||||
if (!empty($userMessage)) {
|
||||
$botReply = detect_intent_texts($projectId, $userMessage, $sessionId);
|
||||
} else {
|
||||
$botReply = 'Please say something.';
|
||||
}
|
||||
|
||||
// Return the bot's reply as JSON
|
||||
echo json_encode(['reply' => $botReply]);
|
||||
1781
composer-setup.php
Normal file
5
composer.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"require": {
|
||||
"google/cloud-dialogflow": "^2.2"
|
||||
}
|
||||
}
|
||||
1395
composer.lock
generated
Normal file
1
gcp_creds/.htaccess
Normal file
@ -0,0 +1 @@
|
||||
Deny from all
|
||||
28
gcp_creds/dialogflow_key.json
Normal file
@ -0,0 +1,28 @@
|
||||
{ "type": "service_account", "project_id": "chrivia-asxi", "private_key_id": "6053badd24ceb066aba55c264b0dc5051bb3ce69", "private_key": "-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMCXYJzgk2AR64
|
||||
rxAVMq9Kf8yD4uYCjIxjtZcYoyrSSmC6V/i6GqQ/CRASzaY5L9NTRWEUN0Ukvlub
|
||||
lYvIHQoBnsr+Zm28PmuougyJdjLr4GKH++HhviNellkQ/QvwGtBG8WAMwu4HuCyK
|
||||
8yeEl6mJpdlJGthOvgt2w23BCsfLjOf6aA1dgZwohCzS5GJhsgwggREPjWnSyRIK
|
||||
YDZaXyfYBgM4imcBSh3lA0ekR87yb+zUY03KLDEo3uWuOplI2d9heKt1ZZoYJCSe
|
||||
UwZ0kFYwCsPDI8vDVV/dLjX7cB8eqZAHLlgYbGJyFBqTZ3qZv8ezAXUYRdOYcpgg
|
||||
nyz8dt3nAgMBAAECggEALlil0dNVv0kg19WYIyCItbTy2TBki8auKwX4BNYnZ24S
|
||||
q7FI48kibtkZqBPDgrDs4TjctNFbKN3+hAhDoJiMCdMui/vrSDuri797EoxhQ7gL
|
||||
2ZSq+fKNKTKgl53LJOaKUdsJNMzgcatxnrxdyR4EGiqsgRESekxr4TXCC/vtZzw6
|
||||
GVUBJMAxh8UY8sSCMO4QWBmxbfth722scCs0eCb1yp9K+1M5g24qFxan4fZVHmDE
|
||||
z/0r+caLSE9KPf+oGnJaccKGz7mcAEOla24/GALrW3JMgnXlcgB1DYNQyUxul+/G
|
||||
c3xT6TR5OG/T4MKDknN9eaHmRd8H5sMwdmj/BT5g2QKBgQDuBZwASn9LzPpwwbVq
|
||||
gNLvo03NRwicLhoSe+Kl8Yao+m7GOKbpfzZgFej2zgq40jgBAJ2zSds1VKEBwKeX
|
||||
CSaXJenKk3DN5trtzTsj3H8AvagNyZcV3YKlKLMc0HUPAWVKX5O6h322SJT3JV30
|
||||
NZTM46rTAD7dFeb9m+JmZ8+B7wKBgQDbcrgCNcijvtSApVw/hXLyhqzPqLDUGGhS
|
||||
XtXx0r6mVuMKOVkgr48/mO/xgSL4wjs4Ps4S0/yqtt5PSFloCOuytpWeTQrBCLL0
|
||||
Or9dPuS+qLmJxkvWV1uoZkfdKpTY2s2ovyilQtovPdoACJnK83pdwArWp3VkrM9
|
||||
Al6Hp0v7iQKBgAx27rx1KkVl7peJDV8Ob/1sp95gIetL3sGpCy11gH/I3ZQz00nX
|
||||
B5nwi8qg757OI3Cp/5gr/fbE/8l/tUcLi6HOsneRUQ73T++0F6zBF0WKqQpPzEGw
|
||||
3+6WOwr/P6IRiKRkbPAPuF2bX3Gx20G2rJwuL/vsv14Ej5woVarXNN6xAoGAaUP9
|
||||
SmocRZfLfa5Uss/D1NyPVslXkVXn7OM7A1YRR99T51qdC1XLhDlLl/BXIzagi5ls
|
||||
5pEzmXxA5Y0R/hqRXVfCK35PU0tl9Eud8g+yUFbFMXaieD1NZNkzTb8YSXGjx3dy
|
||||
+ts3p+z66dNurUjQ7FW+Cg3cuk81lWVmjPHOO+kCgYEA5TJu0h6YSPr5dnNjPgrw
|
||||
UxaGC0wtKX61w+tYOmpUKKCawi2K2tVTa3r/jJy27ClLHodwrNka4R/tNQD7EuRb
|
||||
jD58iSXrHeaPVtbvBZCTASM+ssxEVrhDouz9KH8pKi7TLgA8WNQQjXoV9XRdg4Vj
|
||||
7GFbXwUhhsoOgFWjAKjLol0=
|
||||
-----END PRIVATE KEY-----", "client_email": "chirivia@chrivia-asxi.iam.gserviceaccount.com", "client_id": "109471356352632635196", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/chirivia%40chrivia-asxi.iam.gserviceaccount.com" }
|
||||
554
index.php
@ -1,150 +1,422 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
session_start();
|
||||
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
|
||||
header("Cache-Control: post-check=0, pre-check=0", false);
|
||||
header("Pragma: no-cache");
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if (!isset($_SESSION['streak'])) {
|
||||
$_SESSION['streak'] = 0;
|
||||
}
|
||||
?>
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Chirivia - Save the Planet, Save Your Chircuit</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--primary-green: #2E8B57;
|
||||
--secondary-blue: #4682B4;
|
||||
--accent-color: #5F9EA0;
|
||||
--background-light: #F0F8FF;
|
||||
--surface-white: #FFFFFF;
|
||||
--text-dark: #2F4F4F;
|
||||
--font-headings: 'Inter', sans-serif;
|
||||
--font-body: 'Inter', sans-serif;
|
||||
--spacing-base: 8px;
|
||||
--border-radius: 0.5rem; /* 8px */
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
background-color: var(--background-light);
|
||||
color: var(--text-dark);
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: calc(var(--spacing-base) * 2);
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
font-family: var(--font-headings);
|
||||
color: var(--primary-green);
|
||||
}
|
||||
|
||||
.header {
|
||||
background: var(--surface-white);
|
||||
padding: calc(var(--spacing-base) * 2) 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header .logo {
|
||||
font-family: var(--font-headings);
|
||||
font-size: 2rem;
|
||||
color: var(--primary-green);
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hero {
|
||||
background: linear-gradient(135deg, var(--primary-green), var(--secondary-blue));
|
||||
color: var(--surface-white);
|
||||
padding: calc(var(--spacing-base) * 8) calc(var(--spacing-base) * 2);
|
||||
text-align: center;
|
||||
background-image: url('assets/pasted-20251004-214642-63e1d23c.webp');
|
||||
background-size: 100% auto;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 25%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 4rem;
|
||||
margin-bottom: var(--spacing-base);
|
||||
color: var(--surface-white);
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: calc(var(--spacing-base) * 3);
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
background-color: var(--accent-color);
|
||||
color: var(--surface-white);
|
||||
padding: calc(var(--spacing-base) * 1.5) calc(var(--spacing-base) * 3);
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.cta-button:hover {
|
||||
background-color: #53868B;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: calc(var(--spacing-base) * 6) 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.section:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.pet-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(var(--spacing-base) * 4);
|
||||
}
|
||||
|
||||
.pet-section .pet-image-frame {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
border: 5px solid var(--surface-white);
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.pet-section img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.pet-status h2 {
|
||||
margin-top: 0;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.streak-counter {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: var(--secondary-blue);
|
||||
}
|
||||
|
||||
.about-section h2 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.about-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(var(--spacing-base) * 4);
|
||||
}
|
||||
|
||||
.about-section img {
|
||||
max-width: 400px;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: var(--surface-white);
|
||||
text-align: center;
|
||||
padding: calc(var(--spacing-base) * 3) 0;
|
||||
margin-top: calc(var(--spacing-base) * 4);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: var(--primary-green);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
}
|
||||
|
||||
.animated-pet {
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.pet-image-frame img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.pet-image-frame img.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Chat Widget */
|
||||
.chat-widget-container {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 1000;
|
||||
}
|
||||
.chat-bubble {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-color: var(--secondary-blue);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.chat-bubble:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.chat-window {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 80px;
|
||||
right: 0;
|
||||
width: 350px;
|
||||
max-width: 90vw;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
||||
flex-direction: column;
|
||||
}
|
||||
.chat-header {
|
||||
background: var(--secondary-blue);
|
||||
color: white;
|
||||
padding: 15px;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.chat-header p {
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
.close-chat {
|
||||
cursor: pointer;
|
||||
font-size: 24px;
|
||||
}
|
||||
.chat-body {
|
||||
padding: 15px;
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
.message {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.message p {
|
||||
background: #e9e9eb;
|
||||
padding: 10px 15px;
|
||||
border-radius: 15px;
|
||||
display: inline-block;
|
||||
max-width: 80%;
|
||||
margin: 0;
|
||||
}
|
||||
.chat-footer {
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
.chat-footer input {
|
||||
flex: 1;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 20px;
|
||||
padding: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.chat-footer button {
|
||||
background: var(--secondary-blue);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cta-button img {
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
|
||||
|
||||
|
||||
|
||||
<main>
|
||||
<section class="hero">
|
||||
<div class="hero-content">
|
||||
<h1>Chirivia</h1>
|
||||
<p>Save the Planet, Save Your Chircuit.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div style="text-align: center; padding: 24px 0; background-color: var(--background-light); border-bottom: 1px solid #eee;">
|
||||
<a href="quiz.php?v=<?php echo time(); ?>" class="cta-button" style="margin-right: 16px;">Play Trivia</a>
|
||||
<a href="actions.php?v=<?php echo time(); ?>" class="cta-button" style="margin-right: 16px;">Log an Action</a>
|
||||
<a href="streaks.php?v=<?php echo time(); ?>" class="cta-button" style="margin-right: 16px;">Streaks</a>
|
||||
<a href="chatbot.php?v=<?php echo time(); ?>" class="cta-button">Chatbot</a>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<section class="section pet-section">
|
||||
<div class="pet-image-frame animated-pet" style="position: relative;">
|
||||
<img src="assets/pasted-20251004-192255-8c7c39e6.webp" alt="A cute, healthy pet chircuit." class="active">
|
||||
<img src="assets/pasted-20251004-222033-3b718197.webp" alt="A happy, playful pet chircuit.">
|
||||
</div>
|
||||
<div class="pet-status">
|
||||
<h2>Your Pet Chircuit</h2>
|
||||
<p>Keep me alive by learning about our planet!</p>
|
||||
<div class="streak-counter">
|
||||
🔥 Streak: <?php echo $_SESSION['streak']; ?> <?php echo ($_SESSION['streak'] == 1) ? 'day' : 'days'; ?> 🔥
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section about-section">
|
||||
<div>
|
||||
<h2>About Chirivia</h2>
|
||||
<p>Chirivia makes learning about environmental issues fun and engaging. Every correct answer helps you maintain your streak and contributes to a (virtual) healthier planet, keeping your pet happy and healthy.</p>
|
||||
<p>You can also log real-world actions to get extra points!</p>
|
||||
</div>
|
||||
<img src="https://picsum.photos/seed/about/800/600" alt="A person planting a small tree, symbolizing environmental action.">
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<p>© 2025 Chirivia. All Rights Reserved. | <a href="privacy.php">Privacy Policy</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const petImageFrame = document.querySelector('.pet-image-frame');
|
||||
const images = petImageFrame.querySelectorAll('img');
|
||||
let currentIndex = 0;
|
||||
|
||||
setInterval(() => {
|
||||
images[currentIndex].classList.remove('active');
|
||||
currentIndex = (currentIndex + 1) % images.length;
|
||||
images[currentIndex].classList.add('active');
|
||||
}, 3000); // Change image every 3 seconds
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Chat Widget -->
|
||||
<div class="chat-widget-container">
|
||||
<div id="chat-bubble" class="chat-bubble">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="white"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>
|
||||
</div>
|
||||
<div id="chat-window" class="chat-window">
|
||||
<div class="chat-header">
|
||||
<p>Chat with us!</p>
|
||||
<span id="close-chat" class="close-chat">×</span>
|
||||
</div>
|
||||
<div class="chat-body">
|
||||
<div class="message received">
|
||||
<p>Hi there! How can I help you today?</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-footer">
|
||||
<input type="text" placeholder="Type a message...">
|
||||
<button>Send</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const chatBubble = document.getElementById('chat-bubble');
|
||||
const chatWindow = document.getElementById('chat-window');
|
||||
const closeChat = document.getElementById('close-chat');
|
||||
|
||||
chatBubble.addEventListener('click', () => {
|
||||
chatWindow.style.display = 'flex';
|
||||
chatBubble.style.display = 'none';
|
||||
});
|
||||
|
||||
closeChat.addEventListener('click', () => {
|
||||
chatWindow.style.display = 'none';
|
||||
chatBubble.style.display = 'flex';
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
51
privacy.php
Normal file
@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Privacy Policy - Chirivia</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--primary-green: #2E8B57;
|
||||
--secondary-blue: #4682B4;
|
||||
--accent-color: #5F9EA0;
|
||||
--background-light: #F0F8FF;
|
||||
--surface-white: #FFFFFF;
|
||||
--text-dark: #2F4F4F;
|
||||
--font-headings: 'Inter', sans-serif;
|
||||
--font-body: 'Inter', sans-serif;
|
||||
--spacing-base: 8px;
|
||||
--border-radius: 0.5rem;
|
||||
}
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
background-color: var(--background-light);
|
||||
color: var(--text-dark);
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: calc(var(--spacing-base) * 4);
|
||||
}
|
||||
h1 {
|
||||
color: var(--primary-green);
|
||||
}
|
||||
a {
|
||||
color: var(--primary-green);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Privacy Policy</h1>
|
||||
<p>This is a placeholder for the Privacy Policy.</p>
|
||||
<p>Information about how user data is collected, used, and protected will be detailed here.</p>
|
||||
<a href="index.php">Back to Home</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
366
quiz.php
Normal file
@ -0,0 +1,366 @@
|
||||
<?php
|
||||
session_start();
|
||||
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
|
||||
header("Cache-Control: post-check=0, pre-check=0", false);
|
||||
header("Pragma: no-cache");
|
||||
|
||||
if (!isset($_SESSION['streak'])) {
|
||||
$_SESSION['streak'] = 0;
|
||||
}
|
||||
|
||||
$questions = [
|
||||
[
|
||||
'question' => 'What is the impact of climate change on the planet?',
|
||||
'options' => [
|
||||
'Rising sea levels',
|
||||
'More natural disasters',
|
||||
'Increased temperatures globally',
|
||||
'All of the above'
|
||||
],
|
||||
'answer' => 'All of the above'
|
||||
],
|
||||
[
|
||||
'question' => 'Heat waves enter the atmosphere and are trapped because of the thick layer of gases that surround the planet. This is called…',
|
||||
'options' => [
|
||||
'Thick Atmosphere Effect',
|
||||
'Greenhouse Effect',
|
||||
'Increasing Heat Effect',
|
||||
'None of the above'
|
||||
],
|
||||
'answer' => 'Greenhouse Effect'
|
||||
],
|
||||
[
|
||||
'question' => 'When did climate change begin?',
|
||||
'options' => [
|
||||
'When the Industrial Revolution began',
|
||||
'When the world population was larger than two billion people',
|
||||
'It always has existed',
|
||||
'Yesterday!'
|
||||
],
|
||||
'answer' => ['When the Industrial Revolution began', 'It always has existed']
|
||||
]
|
||||
];
|
||||
|
||||
$question_index = isset($_GET['q']) ? (int)$_GET['q'] : 0;
|
||||
|
||||
if ($question_index === 0) {
|
||||
unset($_SESSION['quiz_completed_reward']);
|
||||
}
|
||||
|
||||
$feedback = '';
|
||||
|
||||
if (isset($_POST['answer'])) {
|
||||
$selected_answer = $_POST['answer'];
|
||||
$correct_answer = $questions[$question_index]['answer'];
|
||||
|
||||
$is_correct = false;
|
||||
if (is_array($correct_answer)) {
|
||||
if (in_array($selected_answer, $correct_answer)) {
|
||||
$is_correct = true;
|
||||
}
|
||||
} else {
|
||||
if ($selected_answer == $correct_answer) {
|
||||
$is_correct = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_correct) {
|
||||
$feedback = '<div class="feedback correct" style="padding: 0;"><p style="padding: 16px 16px 0; margin: 0;">Correct! Great job.</p><img src="assets/pasted-20251004-232406-9a846679.png" alt="Happy Chircuit" class="feedback-image"></div>';
|
||||
$question_index++;
|
||||
} else {
|
||||
$_SESSION['streak'] = 0;
|
||||
$feedback = '<div class="feedback incorrect">Not quite! Try again.<br><img src="assets/pasted-20251005-145045-e02a01f3.png" alt="Sad Chircuit" class="feedback-image-incorrect"></div>';
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Trivia Quiz - Chirivia</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--primary-green: #2E8B57;
|
||||
--secondary-blue: #4682B4;
|
||||
--accent-color: #5F9EA0;
|
||||
--background-light: #F0F8FF;
|
||||
--surface-white: #FFFFFF;
|
||||
--text-dark: #2F4F4F;
|
||||
--font-headings: 'Inter', sans-serif;
|
||||
--font-body: 'Inter', sans-serif;
|
||||
--spacing-base: 8px;
|
||||
--border-radius: 0.5rem;
|
||||
}
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
background-color: var(--background-light);
|
||||
color: var(--text-dark);
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.quiz-container {
|
||||
background: var(--surface-white);
|
||||
padding: calc(var(--spacing-base) * 4);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--font-headings);
|
||||
color: var(--primary-green);
|
||||
}
|
||||
.question {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: calc(var(--spacing-base) * 2);
|
||||
}
|
||||
.options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-base);
|
||||
margin-bottom: calc(var(--spacing-base) * 3);
|
||||
}
|
||||
.option-button {
|
||||
background-color: #eee;
|
||||
border: 2px solid #ddd;
|
||||
padding: calc(var(--spacing-base) * 1.5);
|
||||
border-radius: var(--border-radius);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
width: 100%;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.option-button:hover {
|
||||
background-color: #ddd;
|
||||
border-color: #ccc;
|
||||
}
|
||||
.feedback {
|
||||
padding: calc(var(--spacing-base) * 2);
|
||||
border-radius: var(--border-radius);
|
||||
margin-top: calc(var(--spacing-base) * 2);
|
||||
margin-bottom: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.feedback.correct {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
.feedback.incorrect {
|
||||
background-color: #E0E0E0;
|
||||
color: #2F4F4F;
|
||||
}
|
||||
.next-button {
|
||||
background-color: var(--accent-color);
|
||||
color: var(--surface-white);
|
||||
padding: calc(var(--spacing-base) * 1.5) calc(var(--spacing-base) * 3);
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-green);
|
||||
display: inline-block;
|
||||
margin-top: calc(var(--spacing-base) * 2);
|
||||
}
|
||||
.feedback-image {
|
||||
max-width: 200px;
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
}
|
||||
.feedback-image-incorrect {
|
||||
max-width: 100px;
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
/* Chat Widget */
|
||||
.chat-widget-container {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 1000;
|
||||
}
|
||||
.chat-bubble {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-color: var(--secondary-blue);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.chat-bubble:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.chat-window {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 80px;
|
||||
right: 0;
|
||||
width: 350px;
|
||||
max-width: 90vw;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
||||
flex-direction: column;
|
||||
}
|
||||
.chat-header {
|
||||
background: var(--secondary-blue);
|
||||
color: white;
|
||||
padding: 15px;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.chat-header p {
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
.close-chat {
|
||||
cursor: pointer;
|
||||
font-size: 24px;
|
||||
}
|
||||
.chat-body {
|
||||
padding: 15px;
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
.message {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.message p {
|
||||
background: #e9e9eb;
|
||||
padding: 10px 15px;
|
||||
border-radius: 15px;
|
||||
display: inline-block;
|
||||
max-width: 80%;
|
||||
margin: 0;
|
||||
}
|
||||
.chat-footer {
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
.chat-footer input {
|
||||
flex: 1;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 20px;
|
||||
padding: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.chat-footer button {
|
||||
background: var(--secondary-blue);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="quiz-container">
|
||||
<h1>Trivia Quiz</h1>
|
||||
<?php if ($question_index < count($questions)) {
|
||||
$current_question = $questions[$question_index];
|
||||
?>
|
||||
<p class="question"><?php echo $current_question['question']; ?></p>
|
||||
<?php echo $feedback; ?>
|
||||
<form method="POST" action="quiz.php?q=<?php echo $question_index; ?>">
|
||||
<div class="options">
|
||||
<?php foreach ($current_question['options'] as $option) { ?>
|
||||
<button type="submit" name="answer" value="<?php echo htmlspecialchars($option); ?>" class="option-button"><?php echo htmlspecialchars($option); ?></button>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</form>
|
||||
<?php
|
||||
} else {
|
||||
if (!isset($_SESSION['quiz_completed_reward'])) {
|
||||
$_SESSION['streak']++;
|
||||
$_SESSION['quiz_completed_reward'] = true;
|
||||
}
|
||||
?>
|
||||
<h2>Quiz Complete!</h2>
|
||||
<p>You've answered all the questions! Your streak is now <?php echo $_SESSION['streak']; ?>!</p>
|
||||
<div id="animation-container" style="position: relative; width: 200px; height: 200px; margin: 20px auto;">
|
||||
<img src="assets/pasted-20251005-003819-6e811280.png" alt="Chircuit with a strawberry" class="anim-image" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: contain; opacity: 1; transition: opacity 0.5s ease-in-out;">
|
||||
<img src="assets/pasted-20251005-003524-d4bed2e1.png" alt="Chircuit eating a strawberry" class="anim-image" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: contain; opacity: 0; transition: opacity 0.5s ease-in-out;">
|
||||
<img src="assets/pasted-20251005-004414-8785f3f1.png" alt="Happy Chircuit" class="anim-image" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: contain; opacity: 0; transition: opacity 0.5s ease-in-out;">
|
||||
</div>
|
||||
<a href="index.php" class="next-button" style="margin-top: 20px;">Back to Home</a>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const images = document.querySelectorAll('#animation-container .anim-image');
|
||||
if (images.length > 0) {
|
||||
let currentIndex = 0;
|
||||
setInterval(() => {
|
||||
images[currentIndex].style.opacity = 0;
|
||||
currentIndex = (currentIndex + 1) % images.length;
|
||||
images[currentIndex].style.opacity = 1;
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Chat Widget -->
|
||||
<div class="chat-widget-container">
|
||||
<div id="chat-bubble" class="chat-bubble">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="white"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>
|
||||
</div>
|
||||
<div id="chat-window" class="chat-window">
|
||||
<div class="chat-header">
|
||||
<p>Chat with us!</p>
|
||||
<span id="close-chat" class="close-chat">×</span>
|
||||
</div>
|
||||
<div class="chat-body">
|
||||
<div class="message received">
|
||||
<p>Hi there! How can I help you today?</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-footer">
|
||||
<input type="text" placeholder="Type a message...">
|
||||
<button>Send</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const chatBubble = document.getElementById('chat-bubble');
|
||||
const chatWindow = document.getElementById('chat-window');
|
||||
const closeChat = document.getElementById('close-chat');
|
||||
|
||||
chatBubble.addEventListener('click', () => {
|
||||
chatWindow.style.display = 'flex';
|
||||
chatBubble.style.display = 'none';
|
||||
});
|
||||
|
||||
closeChat.addEventListener('click', () => {
|
||||
chatWindow.style.display = 'none';
|
||||
chatBubble.style.display = 'flex';
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
178
streaks.php
Normal file
@ -0,0 +1,178 @@
|
||||
<?php
|
||||
session_start();
|
||||
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
|
||||
header("Cache-Control: post-check=0, pre-check=0", false);
|
||||
header("Pragma: no-cache");
|
||||
|
||||
if (!isset($_SESSION['streak'])) {
|
||||
$_SESSION['streak'] = 0;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Your Streaks - Chirivia</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--primary-green: #2E8B57;
|
||||
--secondary-blue: #4682B4;
|
||||
--accent-color: #5F9EA0;
|
||||
--background-light: #F0F8FF;
|
||||
--surface-white: #FFFFFF;
|
||||
--text-dark: #2F4F4F;
|
||||
--font-headings: 'Inter', sans-serif;
|
||||
--font-body: 'Inter', sans-serif;
|
||||
--spacing-base: 8px;
|
||||
--border-radius: 0.5rem; /* 8px */
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
background-color: var(--background-light);
|
||||
color: var(--text-dark);
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: calc(var(--spacing-base) * 2);
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
font-family: var(--font-headings);
|
||||
color: var(--primary-green);
|
||||
}
|
||||
|
||||
.header {
|
||||
background: var(--surface-white);
|
||||
padding: calc(var(--spacing-base) * 2) 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header .logo {
|
||||
font-family: var(--font-headings);
|
||||
font-size: 2rem;
|
||||
color: var(--primary-green);
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
main {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(var(--spacing-base) * 8);
|
||||
}
|
||||
|
||||
.image-container {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.streak-image {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
border-radius: 50%;
|
||||
object-fit: contain;
|
||||
background-color: var(--surface-white);
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
||||
border: 5px solid var(--surface-white);
|
||||
animation: bob 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.text-container {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@keyframes bob {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-15px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: var(--surface-white);
|
||||
text-align: center;
|
||||
padding: calc(var(--spacing-base) * 3) 0;
|
||||
margin-top: auto;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: var(--primary-green);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
background-color: var(--accent-color);
|
||||
color: var(--surface-white);
|
||||
padding: calc(var(--spacing-base) * 1.5) calc(var(--spacing-base) * 3);
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
margin-top: calc(var(--spacing-base) * 2);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.cta-button:hover {
|
||||
background-color: #53868B;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<div class="container">
|
||||
<a href="index.php" class="logo">Chirivia</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<div class="content-wrapper">
|
||||
<div class="image-container">
|
||||
<img src="assets/pasted-20251005-170401-70cc8986.png" alt="Streaks Calendar" class="streak-image">
|
||||
</div>
|
||||
<div class="text-container">
|
||||
<h1>Track Your Progress</h1>
|
||||
<p>Your commitment to learning and taking action creates a powerful impact. Keep the momentum going!</p>
|
||||
<p style="font-size: 1.5rem; font-weight: bold; color: var(--secondary-blue);">
|
||||
🔥 Current Streak: <?php echo $_SESSION['streak']; ?> <?php echo ($_SESSION['streak'] == 1) ? 'day' : 'days'; ?> 🔥
|
||||
</p>
|
||||
<a href="index.php" class="cta-button">Back to Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<p>© 2025 Chirivia. All Rights Reserved. | <a href="privacy.php">Privacy Policy</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
22
vendor/autoload.php
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException($err);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit2be695aecbd4dcc8ecce8c4845e9f2d7::getLoader();
|
||||
513
vendor/brick/math/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,513 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.14.0](https://github.com/brick/math/releases/tag/0.14.0) - 2025-08-29
|
||||
|
||||
✨ **New features**
|
||||
|
||||
- New methods: `BigInteger::clamp()` and `BigDecimal::clamp()` (#96 by @JesterIruka)
|
||||
|
||||
✨ **Improvements**
|
||||
|
||||
- All pure methods in `BigNumber` classes are now marked as `@pure` for better static analysis
|
||||
|
||||
💥 **Breaking changes**
|
||||
|
||||
- Minimum PHP version is now 8.2
|
||||
- `BigNumber` classes are now `readonly`
|
||||
- `BigNumber` is now marked as sealed: it must not be extended outside of this package
|
||||
- Exception classes are now `final`
|
||||
|
||||
## [0.13.1](https://github.com/brick/math/releases/tag/0.13.1) - 2025-03-29
|
||||
|
||||
✨ **Improvements**
|
||||
|
||||
- `__toString()` methods of `BigInteger` and `BigDecimal` are now type-hinted as returning `numeric-string` instead of `string` (#90 by @vudaltsov)
|
||||
|
||||
## [0.13.0](https://github.com/brick/math/releases/tag/0.13.0) - 2025-03-03
|
||||
|
||||
💥 **Breaking changes**
|
||||
|
||||
- `BigDecimal::ofUnscaledValue()` no longer throws an exception if the scale is negative
|
||||
- `MathException` now extends `RuntimeException` instead of `Exception`; this reverts the change introduced in version `0.11.0` (#82)
|
||||
|
||||
✨ **New features**
|
||||
|
||||
- `BigDecimal::ofUnscaledValue()` allows a negative scale (and converts the values to create a zero scale number)
|
||||
|
||||
## [0.12.3](https://github.com/brick/math/releases/tag/0.12.3) - 2025-02-28
|
||||
|
||||
✨ **New features**
|
||||
|
||||
- `BigDecimal::getPrecision()` Returns the number of significant digits in a decimal number
|
||||
|
||||
## [0.12.2](https://github.com/brick/math/releases/tag/0.12.2) - 2025-02-26
|
||||
|
||||
⚡️ **Performance improvements**
|
||||
|
||||
- Division in `NativeCalculator` is now faster for small divisors, thanks to [@Izumi-kun](https://github.com/Izumi-kun) in [#87](https://github.com/brick/math/pull/87).
|
||||
|
||||
👌 **Improvements**
|
||||
|
||||
- Add missing `RoundingNecessaryException` to the `@throws` annotation of `BigNumber::of()`
|
||||
|
||||
## [0.12.1](https://github.com/brick/math/releases/tag/0.12.1) - 2023-11-29
|
||||
|
||||
⚡️ **Performance improvements**
|
||||
|
||||
- `BigNumber::of()` is now faster, thanks to [@SebastienDug](https://github.com/SebastienDug) in [#77](https://github.com/brick/math/pull/77).
|
||||
|
||||
## [0.12.0](https://github.com/brick/math/releases/tag/0.12.0) - 2023-11-26
|
||||
|
||||
💥 **Breaking changes**
|
||||
|
||||
- Minimum PHP version is now 8.1
|
||||
- `RoundingMode` is now an `enum`; if you're type-hinting rounding modes, you need to type-hint against `RoundingMode` instead of `int` now
|
||||
- `BigNumber` classes do not implement the `Serializable` interface anymore (they use the [new custom object serialization mechanism](https://wiki.php.net/rfc/custom_object_serialization))
|
||||
- The following breaking changes only affect you if you're creating your own `BigNumber` subclasses:
|
||||
- the return type of `BigNumber::of()` is now `static`
|
||||
- `BigNumber` has a new abstract method `from()`
|
||||
- all `public` and `protected` functions of `BigNumber` are now `final`
|
||||
|
||||
## [0.11.0](https://github.com/brick/math/releases/tag/0.11.0) - 2023-01-16
|
||||
|
||||
💥 **Breaking changes**
|
||||
|
||||
- Minimum PHP version is now 8.0
|
||||
- Methods accepting a union of types are now strongly typed<sup>*</sup>
|
||||
- `MathException` now extends `Exception` instead of `RuntimeException`
|
||||
|
||||
<sup>* You may now run into type errors if you were passing `Stringable` objects to `of()` or any of the methods
|
||||
internally calling `of()`, with `strict_types` enabled. You can fix this by casting `Stringable` objects to `string`
|
||||
first.</sup>
|
||||
|
||||
## [0.10.2](https://github.com/brick/math/releases/tag/0.10.2) - 2022-08-11
|
||||
|
||||
👌 **Improvements**
|
||||
|
||||
- `BigRational::toFloat()` now simplifies the fraction before performing division (#73) thanks to @olsavmic
|
||||
|
||||
## [0.10.1](https://github.com/brick/math/releases/tag/0.10.1) - 2022-08-02
|
||||
|
||||
✨ **New features**
|
||||
|
||||
- `BigInteger::gcdMultiple()` returns the GCD of multiple `BigInteger` numbers
|
||||
|
||||
## [0.10.0](https://github.com/brick/math/releases/tag/0.10.0) - 2022-06-18
|
||||
|
||||
💥 **Breaking changes**
|
||||
|
||||
- Minimum PHP version is now 7.4
|
||||
|
||||
## [0.9.3](https://github.com/brick/math/releases/tag/0.9.3) - 2021-08-15
|
||||
|
||||
🚀 **Compatibility with PHP 8.1**
|
||||
|
||||
- Support for custom object serialization; this removes a warning on PHP 8.1 due to the `Serializable` interface being deprecated (#60) thanks @TRowbotham
|
||||
|
||||
## [0.9.2](https://github.com/brick/math/releases/tag/0.9.2) - 2021-01-20
|
||||
|
||||
🐛 **Bug fix**
|
||||
|
||||
- Incorrect results could be returned when using the BCMath calculator, with a default scale set with `bcscale()`, on PHP >= 7.2 (#55).
|
||||
|
||||
## [0.9.1](https://github.com/brick/math/releases/tag/0.9.1) - 2020-08-19
|
||||
|
||||
✨ **New features**
|
||||
|
||||
- `BigInteger::not()` returns the bitwise `NOT` value
|
||||
|
||||
🐛 **Bug fixes**
|
||||
|
||||
- `BigInteger::toBytes()` could return an incorrect binary representation for some numbers
|
||||
- The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available
|
||||
|
||||
## [0.9.0](https://github.com/brick/math/releases/tag/0.9.0) - 2020-08-18
|
||||
|
||||
👌 **Improvements**
|
||||
|
||||
- `BigNumber::of()` now accepts `.123` and `123.` formats, both of which return a `BigDecimal`
|
||||
|
||||
💥 **Breaking changes**
|
||||
|
||||
- Deprecated method `BigInteger::powerMod()` has been removed - use `modPow()` instead
|
||||
- Deprecated method `BigInteger::parse()` has been removed - use `fromBase()` instead
|
||||
|
||||
## [0.8.17](https://github.com/brick/math/releases/tag/0.8.17) - 2020-08-19
|
||||
|
||||
🐛 **Bug fix**
|
||||
|
||||
- `BigInteger::toBytes()` could return an incorrect binary representation for some numbers
|
||||
- The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available
|
||||
|
||||
## [0.8.16](https://github.com/brick/math/releases/tag/0.8.16) - 2020-08-18
|
||||
|
||||
🚑 **Critical fix**
|
||||
|
||||
- This version reintroduces the deprecated `BigInteger::parse()` method, that has been removed by mistake in version `0.8.9` and should have lasted for the whole `0.8` release cycle.
|
||||
|
||||
✨ **New features**
|
||||
|
||||
- `BigInteger::modInverse()` calculates a modular multiplicative inverse
|
||||
- `BigInteger::fromBytes()` creates a `BigInteger` from a byte string
|
||||
- `BigInteger::toBytes()` converts a `BigInteger` to a byte string
|
||||
- `BigInteger::randomBits()` creates a pseudo-random `BigInteger` of a given bit length
|
||||
- `BigInteger::randomRange()` creates a pseudo-random `BigInteger` between two bounds
|
||||
|
||||
💩 **Deprecations**
|
||||
|
||||
- `BigInteger::powerMod()` is now deprecated in favour of `modPow()`
|
||||
|
||||
## [0.8.15](https://github.com/brick/math/releases/tag/0.8.15) - 2020-04-15
|
||||
|
||||
🐛 **Fixes**
|
||||
|
||||
- added missing `ext-json` requirement, due to `BigNumber` implementing `JsonSerializable`
|
||||
|
||||
⚡️ **Optimizations**
|
||||
|
||||
- additional optimization in `BigInteger::remainder()`
|
||||
|
||||
## [0.8.14](https://github.com/brick/math/releases/tag/0.8.14) - 2020-02-18
|
||||
|
||||
✨ **New features**
|
||||
|
||||
- `BigInteger::getLowestSetBit()` returns the index of the rightmost one bit
|
||||
|
||||
## [0.8.13](https://github.com/brick/math/releases/tag/0.8.13) - 2020-02-16
|
||||
|
||||
✨ **New features**
|
||||
|
||||
- `BigInteger::isEven()` tests whether the number is even
|
||||
- `BigInteger::isOdd()` tests whether the number is odd
|
||||
- `BigInteger::testBit()` tests if a bit is set
|
||||
- `BigInteger::getBitLength()` returns the number of bits in the minimal representation of the number
|
||||
|
||||
## [0.8.12](https://github.com/brick/math/releases/tag/0.8.12) - 2020-02-03
|
||||
|
||||
🛠️ **Maintenance release**
|
||||
|
||||
Classes are now annotated for better static analysis with [psalm](https://psalm.dev/).
|
||||
|
||||
This is a maintenance release: no bug fixes, no new features, no breaking changes.
|
||||
|
||||
## [0.8.11](https://github.com/brick/math/releases/tag/0.8.11) - 2020-01-23
|
||||
|
||||
✨ **New feature**
|
||||
|
||||
`BigInteger::powerMod()` performs a power-with-modulo operation. Useful for crypto.
|
||||
|
||||
## [0.8.10](https://github.com/brick/math/releases/tag/0.8.10) - 2020-01-21
|
||||
|
||||
✨ **New feature**
|
||||
|
||||
`BigInteger::mod()` returns the **modulo** of two numbers. The *modulo* differs from the *remainder* when the signs of the operands are different.
|
||||
|
||||
## [0.8.9](https://github.com/brick/math/releases/tag/0.8.9) - 2020-01-08
|
||||
|
||||
⚡️ **Performance improvements**
|
||||
|
||||
A few additional optimizations in `BigInteger` and `BigDecimal` when one of the operands can be returned as is. Thanks to @tomtomsen in #24.
|
||||
|
||||
## [0.8.8](https://github.com/brick/math/releases/tag/0.8.8) - 2019-04-25
|
||||
|
||||
🐛 **Bug fixes**
|
||||
|
||||
- `BigInteger::toBase()` could return an empty string for zero values (BCMath & Native calculators only, GMP calculator unaffected)
|
||||
|
||||
✨ **New features**
|
||||
|
||||
- `BigInteger::toArbitraryBase()` converts a number to an arbitrary base, using a custom alphabet
|
||||
- `BigInteger::fromArbitraryBase()` converts a string in an arbitrary base, using a custom alphabet, back to a number
|
||||
|
||||
These methods can be used as the foundation to convert strings between different bases/alphabets, using BigInteger as an intermediate representation.
|
||||
|
||||
💩 **Deprecations**
|
||||
|
||||
- `BigInteger::parse()` is now deprecated in favour of `fromBase()`
|
||||
|
||||
`BigInteger::fromBase()` works the same way as `parse()`, with 2 minor differences:
|
||||
|
||||
- the `$base` parameter is required, it does not default to `10`
|
||||
- it throws a `NumberFormatException` instead of an `InvalidArgumentException` when the number is malformed
|
||||
|
||||
## [0.8.7](https://github.com/brick/math/releases/tag/0.8.7) - 2019-04-20
|
||||
|
||||
**Improvements**
|
||||
|
||||
- Safer conversion from `float` when using custom locales
|
||||
- **Much faster** `NativeCalculator` implementation 🚀
|
||||
|
||||
You can expect **at least a 3x performance improvement** for common arithmetic operations when using the library on systems without GMP or BCMath; it gets exponentially faster on multiplications with a high number of digits. This is due to calculations now being performed on whole blocks of digits (the block size depending on the platform, 32-bit or 64-bit) instead of digit-by-digit as before.
|
||||
|
||||
## [0.8.6](https://github.com/brick/math/releases/tag/0.8.6) - 2019-04-11
|
||||
|
||||
**New method**
|
||||
|
||||
`BigNumber::sum()` returns the sum of one or more numbers.
|
||||
|
||||
## [0.8.5](https://github.com/brick/math/releases/tag/0.8.5) - 2019-02-12
|
||||
|
||||
**Bug fix**: `of()` factory methods could fail when passing a `float` in environments using a `LC_NUMERIC` locale with a decimal separator other than `'.'` (#20).
|
||||
|
||||
Thanks @manowark 👍
|
||||
|
||||
## [0.8.4](https://github.com/brick/math/releases/tag/0.8.4) - 2018-12-07
|
||||
|
||||
**New method**
|
||||
|
||||
`BigDecimal::sqrt()` calculates the square root of a decimal number, to a given scale.
|
||||
|
||||
## [0.8.3](https://github.com/brick/math/releases/tag/0.8.3) - 2018-12-06
|
||||
|
||||
**New method**
|
||||
|
||||
`BigInteger::sqrt()` calculates the square root of a number (thanks @peter279k).
|
||||
|
||||
**New exception**
|
||||
|
||||
`NegativeNumberException` is thrown when calling `sqrt()` on a negative number.
|
||||
|
||||
## [0.8.2](https://github.com/brick/math/releases/tag/0.8.2) - 2018-11-08
|
||||
|
||||
**Performance update**
|
||||
|
||||
- Further improvement of `toInt()` performance
|
||||
- `NativeCalculator` can now perform some multiplications more efficiently
|
||||
|
||||
## [0.8.1](https://github.com/brick/math/releases/tag/0.8.1) - 2018-11-07
|
||||
|
||||
Performance optimization of `toInt()` methods.
|
||||
|
||||
## [0.8.0](https://github.com/brick/math/releases/tag/0.8.0) - 2018-10-13
|
||||
|
||||
**Breaking changes**
|
||||
|
||||
The following deprecated methods have been removed. Use the new method name instead:
|
||||
|
||||
| Method removed | Replacement method |
|
||||
| --- | --- |
|
||||
| `BigDecimal::getIntegral()` | `BigDecimal::getIntegralPart()` |
|
||||
| `BigDecimal::getFraction()` | `BigDecimal::getFractionalPart()` |
|
||||
|
||||
---
|
||||
|
||||
**New features**
|
||||
|
||||
`BigInteger` has been augmented with 5 new methods for bitwise operations:
|
||||
|
||||
| New method | Description |
|
||||
| --- | --- |
|
||||
| `and()` | performs a bitwise `AND` operation on two numbers |
|
||||
| `or()` | performs a bitwise `OR` operation on two numbers |
|
||||
| `xor()` | performs a bitwise `XOR` operation on two numbers |
|
||||
| `shiftedLeft()` | returns the number shifted left by a number of bits |
|
||||
| `shiftedRight()` | returns the number shifted right by a number of bits |
|
||||
|
||||
Thanks to @DASPRiD 👍
|
||||
|
||||
## [0.7.3](https://github.com/brick/math/releases/tag/0.7.3) - 2018-08-20
|
||||
|
||||
**New method:** `BigDecimal::hasNonZeroFractionalPart()`
|
||||
|
||||
**Renamed/deprecated methods:**
|
||||
|
||||
- `BigDecimal::getIntegral()` has been renamed to `getIntegralPart()` and is now deprecated
|
||||
- `BigDecimal::getFraction()` has been renamed to `getFractionalPart()` and is now deprecated
|
||||
|
||||
## [0.7.2](https://github.com/brick/math/releases/tag/0.7.2) - 2018-07-21
|
||||
|
||||
**Performance update**
|
||||
|
||||
`BigInteger::parse()` and `toBase()` now use GMP's built-in base conversion features when available.
|
||||
|
||||
## [0.7.1](https://github.com/brick/math/releases/tag/0.7.1) - 2018-03-01
|
||||
|
||||
This is a maintenance release, no code has been changed.
|
||||
|
||||
- When installed with `--no-dev`, the autoloader does not autoload tests anymore
|
||||
- Tests and other files unnecessary for production are excluded from the dist package
|
||||
|
||||
This will help make installations more compact.
|
||||
|
||||
## [0.7.0](https://github.com/brick/math/releases/tag/0.7.0) - 2017-10-02
|
||||
|
||||
Methods renamed:
|
||||
|
||||
- `BigNumber:sign()` has been renamed to `getSign()`
|
||||
- `BigDecimal::unscaledValue()` has been renamed to `getUnscaledValue()`
|
||||
- `BigDecimal::scale()` has been renamed to `getScale()`
|
||||
- `BigDecimal::integral()` has been renamed to `getIntegral()`
|
||||
- `BigDecimal::fraction()` has been renamed to `getFraction()`
|
||||
- `BigRational::numerator()` has been renamed to `getNumerator()`
|
||||
- `BigRational::denominator()` has been renamed to `getDenominator()`
|
||||
|
||||
Classes renamed:
|
||||
|
||||
- `ArithmeticException` has been renamed to `MathException`
|
||||
|
||||
## [0.6.2](https://github.com/brick/math/releases/tag/0.6.2) - 2017-10-02
|
||||
|
||||
The base class for all exceptions is now `MathException`.
|
||||
`ArithmeticException` has been deprecated, and will be removed in 0.7.0.
|
||||
|
||||
## [0.6.1](https://github.com/brick/math/releases/tag/0.6.1) - 2017-10-02
|
||||
|
||||
A number of methods have been renamed:
|
||||
|
||||
- `BigNumber:sign()` is deprecated; use `getSign()` instead
|
||||
- `BigDecimal::unscaledValue()` is deprecated; use `getUnscaledValue()` instead
|
||||
- `BigDecimal::scale()` is deprecated; use `getScale()` instead
|
||||
- `BigDecimal::integral()` is deprecated; use `getIntegral()` instead
|
||||
- `BigDecimal::fraction()` is deprecated; use `getFraction()` instead
|
||||
- `BigRational::numerator()` is deprecated; use `getNumerator()` instead
|
||||
- `BigRational::denominator()` is deprecated; use `getDenominator()` instead
|
||||
|
||||
The old methods will be removed in version 0.7.0.
|
||||
|
||||
## [0.6.0](https://github.com/brick/math/releases/tag/0.6.0) - 2017-08-25
|
||||
|
||||
- Minimum PHP version is now [7.1](https://gophp71.org/); for PHP 5.6 and PHP 7.0 support, use version `0.5`
|
||||
- Deprecated method `BigDecimal::withScale()` has been removed; use `toScale()` instead
|
||||
- Method `BigNumber::toInteger()` has been renamed to `toInt()`
|
||||
|
||||
## [0.5.4](https://github.com/brick/math/releases/tag/0.5.4) - 2016-10-17
|
||||
|
||||
`BigNumber` classes now implement [JsonSerializable](http://php.net/manual/en/class.jsonserializable.php).
|
||||
The JSON output is always a string.
|
||||
|
||||
## [0.5.3](https://github.com/brick/math/releases/tag/0.5.3) - 2016-03-31
|
||||
|
||||
This is a bugfix release. Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
|
||||
|
||||
## [0.5.2](https://github.com/brick/math/releases/tag/0.5.2) - 2015-08-06
|
||||
|
||||
The `$scale` parameter of `BigDecimal::dividedBy()` is now optional again.
|
||||
|
||||
## [0.5.1](https://github.com/brick/math/releases/tag/0.5.1) - 2015-07-05
|
||||
|
||||
**New method: `BigNumber::toScale()`**
|
||||
|
||||
This allows to convert any `BigNumber` to a `BigDecimal` with a given scale, using rounding if necessary.
|
||||
|
||||
## [0.5.0](https://github.com/brick/math/releases/tag/0.5.0) - 2015-07-04
|
||||
|
||||
**New features**
|
||||
- Common `BigNumber` interface for all classes, with the following methods:
|
||||
- `sign()` and derived methods (`isZero()`, `isPositive()`, ...)
|
||||
- `compareTo()` and derived methods (`isEqualTo()`, `isGreaterThan()`, ...) that work across different `BigNumber` types
|
||||
- `toBigInteger()`, `toBigDecimal()`, `toBigRational`() conversion methods
|
||||
- `toInteger()` and `toFloat()` conversion methods to native types
|
||||
- Unified `of()` behaviour: every class now accepts any type of number, provided that it can be safely converted to the current type
|
||||
- New method: `BigDecimal::exactlyDividedBy()`; this method automatically computes the scale of the result, provided that the division yields a finite number of digits
|
||||
- New methods: `BigRational::quotient()` and `remainder()`
|
||||
- Fine-grained exceptions: `DivisionByZeroException`, `RoundingNecessaryException`, `NumberFormatException`
|
||||
- Factory methods `zero()`, `one()` and `ten()` available in all classes
|
||||
- Rounding mode reintroduced in `BigInteger::dividedBy()`
|
||||
|
||||
This release also comes with many performance improvements.
|
||||
|
||||
---
|
||||
|
||||
**Breaking changes**
|
||||
- `BigInteger`:
|
||||
- `getSign()` is renamed to `sign()`
|
||||
- `toString()` is renamed to `toBase()`
|
||||
- `BigInteger::dividedBy()` now throws an exception by default if the remainder is not zero; use `quotient()` to get the previous behaviour
|
||||
- `BigDecimal`:
|
||||
- `getSign()` is renamed to `sign()`
|
||||
- `getUnscaledValue()` is renamed to `unscaledValue()`
|
||||
- `getScale()` is renamed to `scale()`
|
||||
- `getIntegral()` is renamed to `integral()`
|
||||
- `getFraction()` is renamed to `fraction()`
|
||||
- `divideAndRemainder()` is renamed to `quotientAndRemainder()`
|
||||
- `dividedBy()` now takes a **mandatory** `$scale` parameter **before** the rounding mode
|
||||
- `toBigInteger()` does not accept a `$roundingMode` parameter anymore
|
||||
- `toBigRational()` does not simplify the fraction anymore; explicitly add `->simplified()` to get the previous behaviour
|
||||
- `BigRational`:
|
||||
- `getSign()` is renamed to `sign()`
|
||||
- `getNumerator()` is renamed to `numerator()`
|
||||
- `getDenominator()` is renamed to `denominator()`
|
||||
- `of()` is renamed to `nd()`, while `parse()` is renamed to `of()`
|
||||
- Miscellaneous:
|
||||
- `ArithmeticException` is moved to an `Exception\` sub-namespace
|
||||
- `of()` factory methods now throw `NumberFormatException` instead of `InvalidArgumentException`
|
||||
|
||||
## [0.4.3](https://github.com/brick/math/releases/tag/0.4.3) - 2016-03-31
|
||||
|
||||
Backport of two bug fixes from the 0.5 branch:
|
||||
- `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected
|
||||
- Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
|
||||
|
||||
## [0.4.2](https://github.com/brick/math/releases/tag/0.4.2) - 2015-06-16
|
||||
|
||||
New method: `BigDecimal::stripTrailingZeros()`
|
||||
|
||||
## [0.4.1](https://github.com/brick/math/releases/tag/0.4.1) - 2015-06-12
|
||||
|
||||
Introducing a `BigRational` class, to perform calculations on fractions of any size.
|
||||
|
||||
## [0.4.0](https://github.com/brick/math/releases/tag/0.4.0) - 2015-06-12
|
||||
|
||||
Rounding modes have been removed from `BigInteger`, and are now a concept specific to `BigDecimal`.
|
||||
|
||||
`BigInteger::dividedBy()` now always returns the quotient of the division.
|
||||
|
||||
## [0.3.5](https://github.com/brick/math/releases/tag/0.3.5) - 2016-03-31
|
||||
|
||||
Backport of two bug fixes from the 0.5 branch:
|
||||
|
||||
- `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected
|
||||
- Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
|
||||
|
||||
## [0.3.4](https://github.com/brick/math/releases/tag/0.3.4) - 2015-06-11
|
||||
|
||||
New methods:
|
||||
- `BigInteger::remainder()` returns the remainder of a division only
|
||||
- `BigInteger::gcd()` returns the greatest common divisor of two numbers
|
||||
|
||||
## [0.3.3](https://github.com/brick/math/releases/tag/0.3.3) - 2015-06-07
|
||||
|
||||
Fix `toString()` not handling negative numbers.
|
||||
|
||||
## [0.3.2](https://github.com/brick/math/releases/tag/0.3.2) - 2015-06-07
|
||||
|
||||
`BigInteger` and `BigDecimal` now have a `getSign()` method that returns:
|
||||
- `-1` if the number is negative
|
||||
- `0` if the number is zero
|
||||
- `1` if the number is positive
|
||||
|
||||
## [0.3.1](https://github.com/brick/math/releases/tag/0.3.1) - 2015-06-05
|
||||
|
||||
Minor performance improvements
|
||||
|
||||
## [0.3.0](https://github.com/brick/math/releases/tag/0.3.0) - 2015-06-04
|
||||
|
||||
The `$roundingMode` and `$scale` parameters have been swapped in `BigDecimal::dividedBy()`.
|
||||
|
||||
## [0.2.2](https://github.com/brick/math/releases/tag/0.2.2) - 2015-06-04
|
||||
|
||||
Stronger immutability guarantee for `BigInteger` and `BigDecimal`.
|
||||
|
||||
So far, it would have been possible to break immutability of these classes by calling the `unserialize()` internal function. This release fixes that.
|
||||
|
||||
## [0.2.1](https://github.com/brick/math/releases/tag/0.2.1) - 2015-06-02
|
||||
|
||||
Added `BigDecimal::divideAndRemainder()`
|
||||
|
||||
## [0.2.0](https://github.com/brick/math/releases/tag/0.2.0) - 2015-05-22
|
||||
|
||||
- `min()` and `max()` do not accept an `array` anymore, but a variable number of parameters
|
||||
- **minimum PHP version is now 5.6**
|
||||
- continuous integration with PHP 7
|
||||
|
||||
## [0.1.1](https://github.com/brick/math/releases/tag/0.1.1) - 2014-09-01
|
||||
|
||||
- Added `BigInteger::power()`
|
||||
- Added HHVM support
|
||||
|
||||
## [0.1.0](https://github.com/brick/math/releases/tag/0.1.0) - 2014-08-31
|
||||
|
||||
First beta release.
|
||||
|
||||
20
vendor/brick/math/LICENSE
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-present Benjamin Morel
|
||||
|
||||
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.
|
||||
39
vendor/brick/math/composer.json
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "brick/math",
|
||||
"description": "Arbitrary-precision arithmetic library",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"Brick",
|
||||
"Math",
|
||||
"Mathematics",
|
||||
"Arbitrary-precision",
|
||||
"Arithmetic",
|
||||
"BigInteger",
|
||||
"BigDecimal",
|
||||
"BigRational",
|
||||
"BigNumber",
|
||||
"Bignum",
|
||||
"Decimal",
|
||||
"Rational",
|
||||
"Integer"
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^11.5",
|
||||
"php-coveralls/php-coveralls": "^2.2",
|
||||
"phpstan/phpstan": "2.1.22"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Brick\\Math\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Brick\\Math\\Tests\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
14
vendor/brick/math/phpstan.neon
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
parameters:
|
||||
level: 10
|
||||
checkUninitializedProperties: true
|
||||
paths:
|
||||
- src
|
||||
ignoreErrors:
|
||||
- '~Impure call to function array_shift\(\) in pure method~'
|
||||
- '~Possibly impure call to function assert\(\) in pure method~'
|
||||
- '~Possibly impure call to function hex2bin\(\) in pure method~'
|
||||
- '~Possibly impure call to function preg_match\(\) in pure method~'
|
||||
- '~Impure static variable in pure method Brick\\Math\\Big(Integer|Decimal|Rational)::(zero|one|ten)\(\)~'
|
||||
-
|
||||
message: '~Parameter #\d \$\S+ of function bc\S+ expects numeric-string, string given~'
|
||||
path: src/Internal/Calculator/BcMathCalculator.php
|
||||
862
vendor/brick/math/src/BigDecimal.php
vendored
Normal file
@ -0,0 +1,862 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math;
|
||||
|
||||
use Brick\Math\Exception\DivisionByZeroException;
|
||||
use Brick\Math\Exception\MathException;
|
||||
use Brick\Math\Exception\NegativeNumberException;
|
||||
use Brick\Math\Internal\Calculator;
|
||||
use Brick\Math\Internal\CalculatorRegistry;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* Immutable, arbitrary-precision signed decimal numbers.
|
||||
*/
|
||||
final readonly class BigDecimal extends BigNumber
|
||||
{
|
||||
/**
|
||||
* The unscaled value of this decimal number.
|
||||
*
|
||||
* This is a string of digits with an optional leading minus sign.
|
||||
* No leading zero must be present.
|
||||
* No leading minus sign must be present if the value is 0.
|
||||
*/
|
||||
private string $value;
|
||||
|
||||
/**
|
||||
* The scale (number of digits after the decimal point) of this decimal number.
|
||||
*
|
||||
* This must be zero or more.
|
||||
*/
|
||||
private int $scale;
|
||||
|
||||
/**
|
||||
* Protected constructor. Use a factory method to obtain an instance.
|
||||
*
|
||||
* @param string $value The unscaled value, validated.
|
||||
* @param int $scale The scale, validated.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
protected function __construct(string $value, int $scale = 0)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->scale = $scale;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
protected static function from(BigNumber $number): static
|
||||
{
|
||||
return $number->toBigDecimal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BigDecimal from an unscaled value and a scale.
|
||||
*
|
||||
* Example: `(12345, 3)` will result in the BigDecimal `12.345`.
|
||||
*
|
||||
* @param BigNumber|int|float|string $value The unscaled value. Must be convertible to a BigInteger.
|
||||
* @param int $scale The scale of the number. If negative, the scale will be set to zero
|
||||
* and the unscaled value will be adjusted accordingly.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public static function ofUnscaledValue(BigNumber|int|float|string $value, int $scale = 0) : BigDecimal
|
||||
{
|
||||
$value = (string) BigInteger::of($value);
|
||||
|
||||
if ($scale < 0) {
|
||||
if ($value !== '0') {
|
||||
$value .= \str_repeat('0', -$scale);
|
||||
}
|
||||
$scale = 0;
|
||||
}
|
||||
|
||||
return new BigDecimal($value, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BigDecimal representing zero, with a scale of zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public static function zero() : BigDecimal
|
||||
{
|
||||
/** @var BigDecimal|null $zero */
|
||||
static $zero;
|
||||
|
||||
if ($zero === null) {
|
||||
$zero = new BigDecimal('0');
|
||||
}
|
||||
|
||||
return $zero;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BigDecimal representing one, with a scale of zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public static function one() : BigDecimal
|
||||
{
|
||||
/** @var BigDecimal|null $one */
|
||||
static $one;
|
||||
|
||||
if ($one === null) {
|
||||
$one = new BigDecimal('1');
|
||||
}
|
||||
|
||||
return $one;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BigDecimal representing ten, with a scale of zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public static function ten() : BigDecimal
|
||||
{
|
||||
/** @var BigDecimal|null $ten */
|
||||
static $ten;
|
||||
|
||||
if ($ten === null) {
|
||||
$ten = new BigDecimal('10');
|
||||
}
|
||||
|
||||
return $ten;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sum of this number and the given one.
|
||||
*
|
||||
* The result has a scale of `max($this->scale, $that->scale)`.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal.
|
||||
*
|
||||
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function plus(BigNumber|int|float|string $that) : BigDecimal
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->value === '0' && $that->scale <= $this->scale) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($this->value === '0' && $this->scale <= $that->scale) {
|
||||
return $that;
|
||||
}
|
||||
|
||||
[$a, $b] = $this->scaleValues($this, $that);
|
||||
|
||||
$value = CalculatorRegistry::get()->add($a, $b);
|
||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
|
||||
|
||||
return new BigDecimal($value, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the difference of this number and the given one.
|
||||
*
|
||||
* The result has a scale of `max($this->scale, $that->scale)`.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal.
|
||||
*
|
||||
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function minus(BigNumber|int|float|string $that) : BigDecimal
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->value === '0' && $that->scale <= $this->scale) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
[$a, $b] = $this->scaleValues($this, $that);
|
||||
|
||||
$value = CalculatorRegistry::get()->sub($a, $b);
|
||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
|
||||
|
||||
return new BigDecimal($value, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the product of this number and the given one.
|
||||
*
|
||||
* The result has a scale of `$this->scale + $that->scale`.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal.
|
||||
*
|
||||
* @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function multipliedBy(BigNumber|int|float|string $that) : BigDecimal
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->value === '1' && $that->scale === 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($this->value === '1' && $this->scale === 0) {
|
||||
return $that;
|
||||
}
|
||||
|
||||
$value = CalculatorRegistry::get()->mul($this->value, $that->value);
|
||||
$scale = $this->scale + $that->scale;
|
||||
|
||||
return new BigDecimal($value, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of the division of this number by the given one, at the given scale.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The divisor.
|
||||
* @param int|null $scale The desired scale, or null to use the scale of this number.
|
||||
* @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY.
|
||||
*
|
||||
* @throws \InvalidArgumentException If the scale or rounding mode is invalid.
|
||||
* @throws MathException If the number is invalid, is zero, or rounding was necessary.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function dividedBy(BigNumber|int|float|string $that, ?int $scale = null, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->isZero()) {
|
||||
throw DivisionByZeroException::divisionByZero();
|
||||
}
|
||||
|
||||
if ($scale === null) {
|
||||
$scale = $this->scale;
|
||||
} elseif ($scale < 0) {
|
||||
throw new \InvalidArgumentException('Scale cannot be negative.');
|
||||
}
|
||||
|
||||
if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$p = $this->valueWithMinScale($that->scale + $scale);
|
||||
$q = $that->valueWithMinScale($this->scale - $scale);
|
||||
|
||||
$result = CalculatorRegistry::get()->divRound($p, $q, $roundingMode);
|
||||
|
||||
return new BigDecimal($result, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the exact result of the division of this number by the given one.
|
||||
*
|
||||
* The scale of the result is automatically calculated to fit all the fraction digits.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||
*
|
||||
* @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero,
|
||||
* or the result yields an infinite number of digits.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function exactlyDividedBy(BigNumber|int|float|string $that) : BigDecimal
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->value === '0') {
|
||||
throw DivisionByZeroException::divisionByZero();
|
||||
}
|
||||
|
||||
[, $b] = $this->scaleValues($this, $that);
|
||||
|
||||
$d = \rtrim($b, '0');
|
||||
$scale = \strlen($b) - \strlen($d);
|
||||
|
||||
$calculator = CalculatorRegistry::get();
|
||||
|
||||
foreach ([5, 2] as $prime) {
|
||||
for (;;) {
|
||||
$lastDigit = (int) $d[-1];
|
||||
|
||||
if ($lastDigit % $prime !== 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
$d = $calculator->divQ($d, (string) $prime);
|
||||
$scale++;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->dividedBy($that, $scale)->stripTrailingZeros();
|
||||
}
|
||||
|
||||
/**
|
||||
* Limits (clamps) this number between the given minimum and maximum values.
|
||||
*
|
||||
* If the number is lower than $min, returns a copy of $min.
|
||||
* If the number is greater than $max, returns a copy of $max.
|
||||
* Otherwise, returns this number unchanged.
|
||||
*
|
||||
* @param BigNumber|int|float|string $min The minimum. Must be convertible to a BigDecimal.
|
||||
* @param BigNumber|int|float|string $max The maximum. Must be convertible to a BigDecimal.
|
||||
*
|
||||
* @throws MathException If min/max are not convertible to a BigDecimal.
|
||||
*/
|
||||
public function clamp(BigNumber|int|float|string $min, BigNumber|int|float|string $max) : BigDecimal
|
||||
{
|
||||
if ($this->isLessThan($min)) {
|
||||
return BigDecimal::of($min);
|
||||
} elseif ($this->isGreaterThan($max)) {
|
||||
return BigDecimal::of($max);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this number exponentiated to the given value.
|
||||
*
|
||||
* The result has a scale of `$this->scale * $exponent`.
|
||||
*
|
||||
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function power(int $exponent) : BigDecimal
|
||||
{
|
||||
if ($exponent === 0) {
|
||||
return BigDecimal::one();
|
||||
}
|
||||
|
||||
if ($exponent === 1) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($exponent < 0 || $exponent > Calculator::MAX_POWER) {
|
||||
throw new \InvalidArgumentException(\sprintf(
|
||||
'The exponent %d is not in the range 0 to %d.',
|
||||
$exponent,
|
||||
Calculator::MAX_POWER
|
||||
));
|
||||
}
|
||||
|
||||
return new BigDecimal(CalculatorRegistry::get()->pow($this->value, $exponent), $this->scale * $exponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quotient of the division of this number by the given one.
|
||||
*
|
||||
* The quotient has a scale of `0`.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||
*
|
||||
* @throws MathException If the divisor is not a valid decimal number, or is zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function quotient(BigNumber|int|float|string $that) : BigDecimal
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->isZero()) {
|
||||
throw DivisionByZeroException::divisionByZero();
|
||||
}
|
||||
|
||||
$p = $this->valueWithMinScale($that->scale);
|
||||
$q = $that->valueWithMinScale($this->scale);
|
||||
|
||||
$quotient = CalculatorRegistry::get()->divQ($p, $q);
|
||||
|
||||
return new BigDecimal($quotient, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remainder of the division of this number by the given one.
|
||||
*
|
||||
* The remainder has a scale of `max($this->scale, $that->scale)`.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||
*
|
||||
* @throws MathException If the divisor is not a valid decimal number, or is zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function remainder(BigNumber|int|float|string $that) : BigDecimal
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->isZero()) {
|
||||
throw DivisionByZeroException::divisionByZero();
|
||||
}
|
||||
|
||||
$p = $this->valueWithMinScale($that->scale);
|
||||
$q = $that->valueWithMinScale($this->scale);
|
||||
|
||||
$remainder = CalculatorRegistry::get()->divR($p, $q);
|
||||
|
||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
|
||||
|
||||
return new BigDecimal($remainder, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quotient and remainder of the division of this number by the given one.
|
||||
*
|
||||
* The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||
*
|
||||
* @return array{BigDecimal, BigDecimal} An array containing the quotient and the remainder.
|
||||
*
|
||||
* @throws MathException If the divisor is not a valid decimal number, or is zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function quotientAndRemainder(BigNumber|int|float|string $that) : array
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->isZero()) {
|
||||
throw DivisionByZeroException::divisionByZero();
|
||||
}
|
||||
|
||||
$p = $this->valueWithMinScale($that->scale);
|
||||
$q = $that->valueWithMinScale($this->scale);
|
||||
|
||||
[$quotient, $remainder] = CalculatorRegistry::get()->divQR($p, $q);
|
||||
|
||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
|
||||
|
||||
$quotient = new BigDecimal($quotient, 0);
|
||||
$remainder = new BigDecimal($remainder, $scale);
|
||||
|
||||
return [$quotient, $remainder];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the square root of this number, rounded down to the given number of decimals.
|
||||
*
|
||||
* @throws \InvalidArgumentException If the scale is negative.
|
||||
* @throws NegativeNumberException If this number is negative.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function sqrt(int $scale) : BigDecimal
|
||||
{
|
||||
if ($scale < 0) {
|
||||
throw new \InvalidArgumentException('Scale cannot be negative.');
|
||||
}
|
||||
|
||||
if ($this->value === '0') {
|
||||
return new BigDecimal('0', $scale);
|
||||
}
|
||||
|
||||
if ($this->value[0] === '-') {
|
||||
throw new NegativeNumberException('Cannot calculate the square root of a negative number.');
|
||||
}
|
||||
|
||||
$value = $this->value;
|
||||
$addDigits = 2 * $scale - $this->scale;
|
||||
|
||||
if ($addDigits > 0) {
|
||||
// add zeros
|
||||
$value .= \str_repeat('0', $addDigits);
|
||||
} elseif ($addDigits < 0) {
|
||||
// trim digits
|
||||
if (-$addDigits >= \strlen($this->value)) {
|
||||
// requesting a scale too low, will always yield a zero result
|
||||
return new BigDecimal('0', $scale);
|
||||
}
|
||||
|
||||
$value = \substr($value, 0, $addDigits);
|
||||
}
|
||||
|
||||
$value = CalculatorRegistry::get()->sqrt($value);
|
||||
|
||||
return new BigDecimal($value, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this BigDecimal with the decimal point moved $n places to the left.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function withPointMovedLeft(int $n) : BigDecimal
|
||||
{
|
||||
if ($n === 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($n < 0) {
|
||||
return $this->withPointMovedRight(-$n);
|
||||
}
|
||||
|
||||
return new BigDecimal($this->value, $this->scale + $n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this BigDecimal with the decimal point moved $n places to the right.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function withPointMovedRight(int $n) : BigDecimal
|
||||
{
|
||||
if ($n === 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($n < 0) {
|
||||
return $this->withPointMovedLeft(-$n);
|
||||
}
|
||||
|
||||
$value = $this->value;
|
||||
$scale = $this->scale - $n;
|
||||
|
||||
if ($scale < 0) {
|
||||
if ($value !== '0') {
|
||||
$value .= \str_repeat('0', -$scale);
|
||||
}
|
||||
$scale = 0;
|
||||
}
|
||||
|
||||
return new BigDecimal($value, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function stripTrailingZeros() : BigDecimal
|
||||
{
|
||||
if ($this->scale === 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$trimmedValue = \rtrim($this->value, '0');
|
||||
|
||||
if ($trimmedValue === '') {
|
||||
return BigDecimal::zero();
|
||||
}
|
||||
|
||||
$trimmableZeros = \strlen($this->value) - \strlen($trimmedValue);
|
||||
|
||||
if ($trimmableZeros === 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($trimmableZeros > $this->scale) {
|
||||
$trimmableZeros = $this->scale;
|
||||
}
|
||||
|
||||
$value = \substr($this->value, 0, -$trimmableZeros);
|
||||
$scale = $this->scale - $trimmableZeros;
|
||||
|
||||
return new BigDecimal($value, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute value of this number.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function abs() : BigDecimal
|
||||
{
|
||||
return $this->isNegative() ? $this->negated() : $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the negated value of this number.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function negated() : BigDecimal
|
||||
{
|
||||
return new BigDecimal(CalculatorRegistry::get()->neg($this->value), $this->scale);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function compareTo(BigNumber|int|float|string $that) : int
|
||||
{
|
||||
$that = BigNumber::of($that);
|
||||
|
||||
if ($that instanceof BigInteger) {
|
||||
$that = $that->toBigDecimal();
|
||||
}
|
||||
|
||||
if ($that instanceof BigDecimal) {
|
||||
[$a, $b] = $this->scaleValues($this, $that);
|
||||
|
||||
return CalculatorRegistry::get()->cmp($a, $b);
|
||||
}
|
||||
|
||||
return - $that->compareTo($this);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getSign() : int
|
||||
{
|
||||
return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @pure
|
||||
*/
|
||||
public function getUnscaledValue() : BigInteger
|
||||
{
|
||||
return self::newBigInteger($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @pure
|
||||
*/
|
||||
public function getScale() : int
|
||||
{
|
||||
return $this->scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of significant digits in the number.
|
||||
*
|
||||
* This is the number of digits to both sides of the decimal point, stripped of leading zeros.
|
||||
* The sign has no impact on the result.
|
||||
*
|
||||
* Examples:
|
||||
* 0 => 0
|
||||
* 0.0 => 0
|
||||
* 123 => 3
|
||||
* 123.456 => 6
|
||||
* 0.00123 => 3
|
||||
* 0.0012300 => 5
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function getPrecision(): int
|
||||
{
|
||||
$value = $this->value;
|
||||
|
||||
if ($value === '0') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$length = \strlen($value);
|
||||
|
||||
return ($value[0] === '-') ? $length - 1 : $length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representing the integral part of this decimal number.
|
||||
*
|
||||
* Example: `-123.456` => `-123`.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function getIntegralPart() : string
|
||||
{
|
||||
if ($this->scale === 0) {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
$value = $this->getUnscaledValueWithLeadingZeros();
|
||||
|
||||
return \substr($value, 0, -$this->scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representing the fractional part of this decimal number.
|
||||
*
|
||||
* If the scale is zero, an empty string is returned.
|
||||
*
|
||||
* Examples: `-123.456` => '456', `123` => ''.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function getFractionalPart() : string
|
||||
{
|
||||
if ($this->scale === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$value = $this->getUnscaledValueWithLeadingZeros();
|
||||
|
||||
return \substr($value, -$this->scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this decimal number has a non-zero fractional part.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function hasNonZeroFractionalPart() : bool
|
||||
{
|
||||
return $this->getFractionalPart() !== \str_repeat('0', $this->scale);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function toBigInteger() : BigInteger
|
||||
{
|
||||
$zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0);
|
||||
|
||||
return self::newBigInteger($zeroScaleDecimal->value);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function toBigDecimal() : BigDecimal
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function toBigRational() : BigRational
|
||||
{
|
||||
$numerator = self::newBigInteger($this->value);
|
||||
$denominator = self::newBigInteger('1' . \str_repeat('0', $this->scale));
|
||||
|
||||
return self::newBigRational($numerator, $denominator, false);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
|
||||
{
|
||||
if ($scale === $this->scale) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->dividedBy(BigDecimal::one(), $scale, $roundingMode);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function toInt() : int
|
||||
{
|
||||
return $this->toBigInteger()->toInt();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function toFloat() : float
|
||||
{
|
||||
return (float) (string) $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return numeric-string
|
||||
*/
|
||||
#[Override]
|
||||
public function __toString() : string
|
||||
{
|
||||
if ($this->scale === 0) {
|
||||
/** @var numeric-string */
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
$value = $this->getUnscaledValueWithLeadingZeros();
|
||||
|
||||
/** @phpstan-ignore return.type */
|
||||
return \substr($value, 0, -$this->scale) . '.' . \substr($value, -$this->scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is required for serializing the object and SHOULD NOT be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array{value: string, scale: int}
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return ['value' => $this->value, 'scale' => $this->scale];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is only here to allow unserializing the object and cannot be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param array{value: string, scale: int} $data
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
/** @phpstan-ignore isset.initializedProperty */
|
||||
if (isset($this->value)) {
|
||||
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
|
||||
}
|
||||
|
||||
/** @phpstan-ignore deadCode.unreachable */
|
||||
$this->value = $data['value'];
|
||||
$this->scale = $data['scale'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the internal values of the given decimal numbers on the same scale.
|
||||
*
|
||||
* @return array{string, string} The scaled integer values of $x and $y.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private function scaleValues(BigDecimal $x, BigDecimal $y) : array
|
||||
{
|
||||
$a = $x->value;
|
||||
$b = $y->value;
|
||||
|
||||
if ($b !== '0' && $x->scale > $y->scale) {
|
||||
$b .= \str_repeat('0', $x->scale - $y->scale);
|
||||
} elseif ($a !== '0' && $x->scale < $y->scale) {
|
||||
$a .= \str_repeat('0', $y->scale - $x->scale);
|
||||
}
|
||||
|
||||
return [$a, $b];
|
||||
}
|
||||
|
||||
/**
|
||||
* @pure
|
||||
*/
|
||||
private function valueWithMinScale(int $scale) : string
|
||||
{
|
||||
$value = $this->value;
|
||||
|
||||
if ($this->value !== '0' && $scale > $this->scale) {
|
||||
$value .= \str_repeat('0', $scale - $this->scale);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds leading zeros if necessary to the unscaled value to represent the full decimal number.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private function getUnscaledValueWithLeadingZeros() : string
|
||||
{
|
||||
$value = $this->value;
|
||||
$targetLength = $this->scale + 1;
|
||||
$negative = ($value[0] === '-');
|
||||
$length = \strlen($value);
|
||||
|
||||
if ($negative) {
|
||||
$length--;
|
||||
}
|
||||
|
||||
if ($length >= $targetLength) {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
if ($negative) {
|
||||
$value = \substr($value, 1);
|
||||
}
|
||||
|
||||
$value = \str_pad($value, $targetLength, '0', STR_PAD_LEFT);
|
||||
|
||||
if ($negative) {
|
||||
$value = '-' . $value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
1139
vendor/brick/math/src/BigInteger.php
vendored
Normal file
556
vendor/brick/math/src/BigNumber.php
vendored
Normal file
@ -0,0 +1,556 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math;
|
||||
|
||||
use Brick\Math\Exception\DivisionByZeroException;
|
||||
use Brick\Math\Exception\MathException;
|
||||
use Brick\Math\Exception\NumberFormatException;
|
||||
use Brick\Math\Exception\RoundingNecessaryException;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* Base class for arbitrary-precision numbers.
|
||||
*
|
||||
* This class is sealed: it is part of the public API but should not be subclassed in userland.
|
||||
* Protected methods may change in any version.
|
||||
*
|
||||
* @phpstan-sealed BigInteger|BigDecimal|BigRational
|
||||
*/
|
||||
abstract readonly class BigNumber implements \JsonSerializable, \Stringable
|
||||
{
|
||||
/**
|
||||
* The regular expression used to parse integer or decimal numbers.
|
||||
*/
|
||||
private const PARSE_REGEXP_NUMERICAL =
|
||||
'/^' .
|
||||
'(?<sign>[\-\+])?' .
|
||||
'(?<integral>[0-9]+)?' .
|
||||
'(?<point>\.)?' .
|
||||
'(?<fractional>[0-9]+)?' .
|
||||
'(?:[eE](?<exponent>[\-\+]?[0-9]+))?' .
|
||||
'$/';
|
||||
|
||||
/**
|
||||
* The regular expression used to parse rational numbers.
|
||||
*/
|
||||
private const PARSE_REGEXP_RATIONAL =
|
||||
'/^' .
|
||||
'(?<sign>[\-\+])?' .
|
||||
'(?<numerator>[0-9]+)' .
|
||||
'\/?' .
|
||||
'(?<denominator>[0-9]+)' .
|
||||
'$/';
|
||||
|
||||
/**
|
||||
* Creates a BigNumber of the given value.
|
||||
*
|
||||
* When of() is called on BigNumber, the concrete return type is dependent on the given value, with the following
|
||||
* rules:
|
||||
*
|
||||
* - BigNumber instances are returned as is
|
||||
* - integer numbers are returned as BigInteger
|
||||
* - floating point numbers are converted to a string then parsed as such
|
||||
* - strings containing a `/` character are returned as BigRational
|
||||
* - strings containing a `.` character or using an exponential notation are returned as BigDecimal
|
||||
* - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger
|
||||
*
|
||||
* When of() is called on BigInteger, BigDecimal, or BigRational, the resulting number is converted to an instance
|
||||
* of the subclass when possible; otherwise a RoundingNecessaryException exception is thrown.
|
||||
*
|
||||
* @throws NumberFormatException If the format of the number is not valid.
|
||||
* @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
|
||||
* @throws RoundingNecessaryException If the value cannot be converted to an instance of the subclass without rounding.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public static function of(BigNumber|int|float|string $value) : static
|
||||
{
|
||||
$value = self::_of($value);
|
||||
|
||||
if (static::class === BigNumber::class) {
|
||||
assert($value instanceof static);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
return static::from($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NumberFormatException If the format of the number is not valid.
|
||||
* @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private static function _of(BigNumber|int|float|string $value) : BigNumber
|
||||
{
|
||||
if ($value instanceof BigNumber) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (\is_int($value)) {
|
||||
return new BigInteger((string) $value);
|
||||
}
|
||||
|
||||
if (is_float($value)) {
|
||||
$value = (string) $value;
|
||||
}
|
||||
|
||||
if (str_contains($value, '/')) {
|
||||
// Rational number
|
||||
if (\preg_match(self::PARSE_REGEXP_RATIONAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) {
|
||||
throw NumberFormatException::invalidFormat($value);
|
||||
}
|
||||
|
||||
$sign = $matches['sign'];
|
||||
$numerator = $matches['numerator'];
|
||||
$denominator = $matches['denominator'];
|
||||
|
||||
$numerator = self::cleanUp($sign, $numerator);
|
||||
$denominator = self::cleanUp(null, $denominator);
|
||||
|
||||
if ($denominator === '0') {
|
||||
throw DivisionByZeroException::denominatorMustNotBeZero();
|
||||
}
|
||||
|
||||
return new BigRational(
|
||||
new BigInteger($numerator),
|
||||
new BigInteger($denominator),
|
||||
false
|
||||
);
|
||||
} else {
|
||||
// Integer or decimal number
|
||||
if (\preg_match(self::PARSE_REGEXP_NUMERICAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) {
|
||||
throw NumberFormatException::invalidFormat($value);
|
||||
}
|
||||
|
||||
$sign = $matches['sign'];
|
||||
$point = $matches['point'];
|
||||
$integral = $matches['integral'];
|
||||
$fractional = $matches['fractional'];
|
||||
$exponent = $matches['exponent'];
|
||||
|
||||
if ($integral === null && $fractional === null) {
|
||||
throw NumberFormatException::invalidFormat($value);
|
||||
}
|
||||
|
||||
if ($integral === null) {
|
||||
$integral = '0';
|
||||
}
|
||||
|
||||
if ($point !== null || $exponent !== null) {
|
||||
$fractional ??= '';
|
||||
$exponent = ($exponent !== null) ? (int)$exponent : 0;
|
||||
|
||||
if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) {
|
||||
throw new NumberFormatException('Exponent too large.');
|
||||
}
|
||||
|
||||
$unscaledValue = self::cleanUp($sign, $integral . $fractional);
|
||||
|
||||
$scale = \strlen($fractional) - $exponent;
|
||||
|
||||
if ($scale < 0) {
|
||||
if ($unscaledValue !== '0') {
|
||||
$unscaledValue .= \str_repeat('0', -$scale);
|
||||
}
|
||||
$scale = 0;
|
||||
}
|
||||
|
||||
return new BigDecimal($unscaledValue, $scale);
|
||||
}
|
||||
|
||||
$integral = self::cleanUp($sign, $integral);
|
||||
|
||||
return new BigInteger($integral);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden by subclasses to convert a BigNumber to an instance of the subclass.
|
||||
*
|
||||
* @throws RoundingNecessaryException If the value cannot be converted.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract protected static function from(BigNumber $number): static;
|
||||
|
||||
/**
|
||||
* Proxy method to access BigInteger's protected constructor from sibling classes.
|
||||
*
|
||||
* @pure
|
||||
* @internal
|
||||
*/
|
||||
final protected function newBigInteger(string $value) : BigInteger
|
||||
{
|
||||
return new BigInteger($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy method to access BigDecimal's protected constructor from sibling classes.
|
||||
*
|
||||
* @pure
|
||||
* @internal
|
||||
*/
|
||||
final protected function newBigDecimal(string $value, int $scale = 0) : BigDecimal
|
||||
{
|
||||
return new BigDecimal($value, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy method to access BigRational's protected constructor from sibling classes.
|
||||
*
|
||||
* @pure
|
||||
* @internal
|
||||
*/
|
||||
final protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) : BigRational
|
||||
{
|
||||
return new BigRational($numerator, $denominator, $checkDenominator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum of the given values.
|
||||
*
|
||||
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
|
||||
* to an instance of the class this method is called on.
|
||||
*
|
||||
* @throws \InvalidArgumentException If no values are given.
|
||||
* @throws MathException If an argument is not valid.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public static function min(BigNumber|int|float|string ...$values) : static
|
||||
{
|
||||
$min = null;
|
||||
|
||||
foreach ($values as $value) {
|
||||
$value = static::of($value);
|
||||
|
||||
if ($min === null || $value->isLessThan($min)) {
|
||||
$min = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($min === null) {
|
||||
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
|
||||
}
|
||||
|
||||
return $min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum of the given values.
|
||||
*
|
||||
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
|
||||
* to an instance of the class this method is called on.
|
||||
*
|
||||
* @throws \InvalidArgumentException If no values are given.
|
||||
* @throws MathException If an argument is not valid.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public static function max(BigNumber|int|float|string ...$values) : static
|
||||
{
|
||||
$max = null;
|
||||
|
||||
foreach ($values as $value) {
|
||||
$value = static::of($value);
|
||||
|
||||
if ($max === null || $value->isGreaterThan($max)) {
|
||||
$max = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($max === null) {
|
||||
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
|
||||
}
|
||||
|
||||
return $max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sum of the given values.
|
||||
*
|
||||
* When called on BigNumber, sum() accepts any supported type and returns a result whose type is the widest among
|
||||
* the given values (BigInteger < BigDecimal < BigRational).
|
||||
*
|
||||
* When called on BigInteger, BigDecimal, or BigRational, sum() requires that all values can be converted to that
|
||||
* specific subclass, and returns a result of the same type.
|
||||
*
|
||||
* @param BigNumber|int|float|string ...$values The values to add. All values must be convertible to the class on
|
||||
* which this method is called.
|
||||
*
|
||||
* @throws \InvalidArgumentException If no values are given.
|
||||
* @throws MathException If an argument is not valid.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public static function sum(BigNumber|int|float|string ...$values) : static
|
||||
{
|
||||
$first = array_shift($values);
|
||||
|
||||
if ($first === null) {
|
||||
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
|
||||
}
|
||||
|
||||
$sum = static::of($first);
|
||||
|
||||
foreach ($values as $value) {
|
||||
$sum = self::add($sum, static::of($value));
|
||||
}
|
||||
|
||||
assert($sum instanceof static);
|
||||
|
||||
return $sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private static function add(BigNumber $a, BigNumber $b) : BigNumber
|
||||
{
|
||||
if ($a instanceof BigRational) {
|
||||
return $a->plus($b);
|
||||
}
|
||||
|
||||
if ($b instanceof BigRational) {
|
||||
return $b->plus($a);
|
||||
}
|
||||
|
||||
if ($a instanceof BigDecimal) {
|
||||
return $a->plus($b);
|
||||
}
|
||||
|
||||
if ($b instanceof BigDecimal) {
|
||||
return $b->plus($a);
|
||||
}
|
||||
|
||||
return $a->plus($b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes optional leading zeros and applies sign.
|
||||
*
|
||||
* @param string|null $sign The sign, '+' or '-', optional. Null is allowed for convenience and treated as '+'.
|
||||
* @param string $number The number, validated as a string of digits.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private static function cleanUp(string|null $sign, string $number) : string
|
||||
{
|
||||
$number = \ltrim($number, '0');
|
||||
|
||||
if ($number === '') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
return $sign === '-' ? '-' . $number : $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is equal to the given one.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function isEqualTo(BigNumber|int|float|string $that) : bool
|
||||
{
|
||||
return $this->compareTo($that) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is strictly lower than the given one.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function isLessThan(BigNumber|int|float|string $that) : bool
|
||||
{
|
||||
return $this->compareTo($that) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is lower than or equal to the given one.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function isLessThanOrEqualTo(BigNumber|int|float|string $that) : bool
|
||||
{
|
||||
return $this->compareTo($that) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is strictly greater than the given one.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function isGreaterThan(BigNumber|int|float|string $that) : bool
|
||||
{
|
||||
return $this->compareTo($that) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is greater than or equal to the given one.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that) : bool
|
||||
{
|
||||
return $this->compareTo($that) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number equals zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function isZero() : bool
|
||||
{
|
||||
return $this->getSign() === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is strictly negative.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function isNegative() : bool
|
||||
{
|
||||
return $this->getSign() < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is negative or zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function isNegativeOrZero() : bool
|
||||
{
|
||||
return $this->getSign() <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is strictly positive.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function isPositive() : bool
|
||||
{
|
||||
return $this->getSign() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is positive or zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function isPositiveOrZero() : bool
|
||||
{
|
||||
return $this->getSign() >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sign of this number.
|
||||
*
|
||||
* Returns -1 if the number is negative, 0 if zero, 1 if positive.
|
||||
*
|
||||
* @return -1|0|1
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function getSign() : int;
|
||||
|
||||
/**
|
||||
* Compares this number to the given one.
|
||||
*
|
||||
* Returns -1 if `$this` is lower than, 0 if equal to, 1 if greater than `$that`.
|
||||
*
|
||||
* @return -1|0|1
|
||||
*
|
||||
* @throws MathException If the number is not valid.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function compareTo(BigNumber|int|float|string $that) : int;
|
||||
|
||||
/**
|
||||
* Converts this number to a BigInteger.
|
||||
*
|
||||
* @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function toBigInteger() : BigInteger;
|
||||
|
||||
/**
|
||||
* Converts this number to a BigDecimal.
|
||||
*
|
||||
* @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function toBigDecimal() : BigDecimal;
|
||||
|
||||
/**
|
||||
* Converts this number to a BigRational.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function toBigRational() : BigRational;
|
||||
|
||||
/**
|
||||
* Converts this number to a BigDecimal with the given scale, using rounding if necessary.
|
||||
*
|
||||
* @param int $scale The scale of the resulting `BigDecimal`.
|
||||
* @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY.
|
||||
*
|
||||
* @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding.
|
||||
* This only applies when RoundingMode::UNNECESSARY is used.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal;
|
||||
|
||||
/**
|
||||
* Returns the exact value of this number as a native integer.
|
||||
*
|
||||
* If this number cannot be converted to a native integer without losing precision, an exception is thrown.
|
||||
* Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit.
|
||||
*
|
||||
* @throws MathException If this number cannot be exactly converted to a native integer.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function toInt() : int;
|
||||
|
||||
/**
|
||||
* Returns an approximation of this number as a floating-point value.
|
||||
*
|
||||
* Note that this method can discard information as the precision of a floating-point value
|
||||
* is inherently limited.
|
||||
*
|
||||
* If the number is greater than the largest representable floating point number, positive infinity is returned.
|
||||
* If the number is less than the smallest representable floating point number, negative infinity is returned.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function toFloat() : float;
|
||||
|
||||
/**
|
||||
* Returns a string representation of this number.
|
||||
*
|
||||
* The output of this method can be parsed by the `of()` factory method;
|
||||
* this will yield an object equal to this one, without any information loss.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function __toString() : string;
|
||||
|
||||
#[Override]
|
||||
final public function jsonSerialize() : string
|
||||
{
|
||||
return $this->__toString();
|
||||
}
|
||||
}
|
||||
441
vendor/brick/math/src/BigRational.php
vendored
Normal file
@ -0,0 +1,441 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math;
|
||||
|
||||
use Brick\Math\Exception\DivisionByZeroException;
|
||||
use Brick\Math\Exception\MathException;
|
||||
use Brick\Math\Exception\NumberFormatException;
|
||||
use Brick\Math\Exception\RoundingNecessaryException;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* An arbitrarily large rational number.
|
||||
*
|
||||
* This class is immutable.
|
||||
*/
|
||||
final readonly class BigRational extends BigNumber
|
||||
{
|
||||
/**
|
||||
* The numerator.
|
||||
*/
|
||||
private BigInteger $numerator;
|
||||
|
||||
/**
|
||||
* The denominator. Always strictly positive.
|
||||
*/
|
||||
private BigInteger $denominator;
|
||||
|
||||
/**
|
||||
* Protected constructor. Use a factory method to obtain an instance.
|
||||
*
|
||||
* @param BigInteger $numerator The numerator.
|
||||
* @param BigInteger $denominator The denominator.
|
||||
* @param bool $checkDenominator Whether to check the denominator for negative and zero.
|
||||
*
|
||||
* @throws DivisionByZeroException If the denominator is zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
protected function __construct(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator)
|
||||
{
|
||||
if ($checkDenominator) {
|
||||
if ($denominator->isZero()) {
|
||||
throw DivisionByZeroException::denominatorMustNotBeZero();
|
||||
}
|
||||
|
||||
if ($denominator->isNegative()) {
|
||||
$numerator = $numerator->negated();
|
||||
$denominator = $denominator->negated();
|
||||
}
|
||||
}
|
||||
|
||||
$this->numerator = $numerator;
|
||||
$this->denominator = $denominator;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
protected static function from(BigNumber $number): static
|
||||
{
|
||||
return $number->toBigRational();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BigRational out of a numerator and a denominator.
|
||||
*
|
||||
* If the denominator is negative, the signs of both the numerator and the denominator
|
||||
* will be inverted to ensure that the denominator is always positive.
|
||||
*
|
||||
* @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger.
|
||||
* @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger.
|
||||
*
|
||||
* @throws NumberFormatException If an argument does not represent a valid number.
|
||||
* @throws RoundingNecessaryException If an argument represents a non-integer number.
|
||||
* @throws DivisionByZeroException If the denominator is zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public static function nd(
|
||||
BigNumber|int|float|string $numerator,
|
||||
BigNumber|int|float|string $denominator,
|
||||
) : BigRational {
|
||||
$numerator = BigInteger::of($numerator);
|
||||
$denominator = BigInteger::of($denominator);
|
||||
|
||||
return new BigRational($numerator, $denominator, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BigRational representing zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public static function zero() : BigRational
|
||||
{
|
||||
/** @var BigRational|null $zero */
|
||||
static $zero;
|
||||
|
||||
if ($zero === null) {
|
||||
$zero = new BigRational(BigInteger::zero(), BigInteger::one(), false);
|
||||
}
|
||||
|
||||
return $zero;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BigRational representing one.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public static function one() : BigRational
|
||||
{
|
||||
/** @var BigRational|null $one */
|
||||
static $one;
|
||||
|
||||
if ($one === null) {
|
||||
$one = new BigRational(BigInteger::one(), BigInteger::one(), false);
|
||||
}
|
||||
|
||||
return $one;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BigRational representing ten.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public static function ten() : BigRational
|
||||
{
|
||||
/** @var BigRational|null $ten */
|
||||
static $ten;
|
||||
|
||||
if ($ten === null) {
|
||||
$ten = new BigRational(BigInteger::ten(), BigInteger::one(), false);
|
||||
}
|
||||
|
||||
return $ten;
|
||||
}
|
||||
|
||||
/**
|
||||
* @pure
|
||||
*/
|
||||
public function getNumerator() : BigInteger
|
||||
{
|
||||
return $this->numerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @pure
|
||||
*/
|
||||
public function getDenominator() : BigInteger
|
||||
{
|
||||
return $this->denominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quotient of the division of the numerator by the denominator.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function quotient() : BigInteger
|
||||
{
|
||||
return $this->numerator->quotient($this->denominator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remainder of the division of the numerator by the denominator.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function remainder() : BigInteger
|
||||
{
|
||||
return $this->numerator->remainder($this->denominator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quotient and remainder of the division of the numerator by the denominator.
|
||||
*
|
||||
* @return array{BigInteger, BigInteger}
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function quotientAndRemainder() : array
|
||||
{
|
||||
return $this->numerator->quotientAndRemainder($this->denominator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sum of this number and the given one.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The number to add.
|
||||
*
|
||||
* @throws MathException If the number is not valid.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function plus(BigNumber|int|float|string $that) : BigRational
|
||||
{
|
||||
$that = BigRational::of($that);
|
||||
|
||||
$numerator = $this->numerator->multipliedBy($that->denominator);
|
||||
$numerator = $numerator->plus($that->numerator->multipliedBy($this->denominator));
|
||||
$denominator = $this->denominator->multipliedBy($that->denominator);
|
||||
|
||||
return new BigRational($numerator, $denominator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the difference of this number and the given one.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The number to subtract.
|
||||
*
|
||||
* @throws MathException If the number is not valid.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function minus(BigNumber|int|float|string $that) : BigRational
|
||||
{
|
||||
$that = BigRational::of($that);
|
||||
|
||||
$numerator = $this->numerator->multipliedBy($that->denominator);
|
||||
$numerator = $numerator->minus($that->numerator->multipliedBy($this->denominator));
|
||||
$denominator = $this->denominator->multipliedBy($that->denominator);
|
||||
|
||||
return new BigRational($numerator, $denominator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the product of this number and the given one.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The multiplier.
|
||||
*
|
||||
* @throws MathException If the multiplier is not a valid number.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function multipliedBy(BigNumber|int|float|string $that) : BigRational
|
||||
{
|
||||
$that = BigRational::of($that);
|
||||
|
||||
$numerator = $this->numerator->multipliedBy($that->numerator);
|
||||
$denominator = $this->denominator->multipliedBy($that->denominator);
|
||||
|
||||
return new BigRational($numerator, $denominator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of the division of this number by the given one.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The divisor.
|
||||
*
|
||||
* @throws MathException If the divisor is not a valid number, or is zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function dividedBy(BigNumber|int|float|string $that) : BigRational
|
||||
{
|
||||
$that = BigRational::of($that);
|
||||
|
||||
$numerator = $this->numerator->multipliedBy($that->denominator);
|
||||
$denominator = $this->denominator->multipliedBy($that->numerator);
|
||||
|
||||
return new BigRational($numerator, $denominator, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this number exponentiated to the given value.
|
||||
*
|
||||
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function power(int $exponent) : BigRational
|
||||
{
|
||||
if ($exponent === 0) {
|
||||
$one = BigInteger::one();
|
||||
|
||||
return new BigRational($one, $one, false);
|
||||
}
|
||||
|
||||
if ($exponent === 1) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return new BigRational(
|
||||
$this->numerator->power($exponent),
|
||||
$this->denominator->power($exponent),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reciprocal of this BigRational.
|
||||
*
|
||||
* The reciprocal has the numerator and denominator swapped.
|
||||
*
|
||||
* @throws DivisionByZeroException If the numerator is zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function reciprocal() : BigRational
|
||||
{
|
||||
return new BigRational($this->denominator, $this->numerator, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute value of this BigRational.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function abs() : BigRational
|
||||
{
|
||||
return new BigRational($this->numerator->abs(), $this->denominator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the negated value of this BigRational.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function negated() : BigRational
|
||||
{
|
||||
return new BigRational($this->numerator->negated(), $this->denominator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the simplified value of this BigRational.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function simplified() : BigRational
|
||||
{
|
||||
$gcd = $this->numerator->gcd($this->denominator);
|
||||
|
||||
$numerator = $this->numerator->quotient($gcd);
|
||||
$denominator = $this->denominator->quotient($gcd);
|
||||
|
||||
return new BigRational($numerator, $denominator, false);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function compareTo(BigNumber|int|float|string $that) : int
|
||||
{
|
||||
return $this->minus($that)->getSign();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getSign() : int
|
||||
{
|
||||
return $this->numerator->getSign();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function toBigInteger() : BigInteger
|
||||
{
|
||||
$simplified = $this->simplified();
|
||||
|
||||
if (! $simplified->denominator->isEqualTo(1)) {
|
||||
throw new RoundingNecessaryException('This rational number cannot be represented as an integer value without rounding.');
|
||||
}
|
||||
|
||||
return $simplified->numerator;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function toBigDecimal() : BigDecimal
|
||||
{
|
||||
return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function toBigRational() : BigRational
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
|
||||
{
|
||||
return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function toInt() : int
|
||||
{
|
||||
return $this->toBigInteger()->toInt();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function toFloat() : float
|
||||
{
|
||||
$simplified = $this->simplified();
|
||||
return $simplified->numerator->toFloat() / $simplified->denominator->toFloat();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function __toString() : string
|
||||
{
|
||||
$numerator = (string) $this->numerator;
|
||||
$denominator = (string) $this->denominator;
|
||||
|
||||
if ($denominator === '1') {
|
||||
return $numerator;
|
||||
}
|
||||
|
||||
return $numerator . '/' . $denominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is required for serializing the object and SHOULD NOT be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array{numerator: BigInteger, denominator: BigInteger}
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return ['numerator' => $this->numerator, 'denominator' => $this->denominator];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is only here to allow unserializing the object and cannot be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param array{numerator: BigInteger, denominator: BigInteger} $data
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
/** @phpstan-ignore isset.initializedProperty */
|
||||
if (isset($this->numerator)) {
|
||||
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
|
||||
}
|
||||
|
||||
/** @phpstan-ignore deadCode.unreachable */
|
||||
$this->numerator = $data['numerator'];
|
||||
$this->denominator = $data['denominator'];
|
||||
}
|
||||
}
|
||||
35
vendor/brick/math/src/Exception/DivisionByZeroException.php
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a division by zero occurs.
|
||||
*/
|
||||
final class DivisionByZeroException extends MathException
|
||||
{
|
||||
/**
|
||||
* @pure
|
||||
*/
|
||||
public static function divisionByZero() : DivisionByZeroException
|
||||
{
|
||||
return new self('Division by zero.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @pure
|
||||
*/
|
||||
public static function modulusMustNotBeZero() : DivisionByZeroException
|
||||
{
|
||||
return new self('The modulus must not be zero.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @pure
|
||||
*/
|
||||
public static function denominatorMustNotBeZero() : DivisionByZeroException
|
||||
{
|
||||
return new self('The denominator of a rational number cannot be zero.');
|
||||
}
|
||||
}
|
||||
23
vendor/brick/math/src/Exception/IntegerOverflowException.php
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Exception;
|
||||
|
||||
use Brick\Math\BigInteger;
|
||||
|
||||
/**
|
||||
* Exception thrown when an integer overflow occurs.
|
||||
*/
|
||||
final class IntegerOverflowException extends MathException
|
||||
{
|
||||
/**
|
||||
* @pure
|
||||
*/
|
||||
public static function toIntOverflow(BigInteger $value) : IntegerOverflowException
|
||||
{
|
||||
$message = '%s is out of range %d to %d and cannot be represented as an integer.';
|
||||
|
||||
return new self(\sprintf($message, (string) $value, PHP_INT_MIN, PHP_INT_MAX));
|
||||
}
|
||||
}
|
||||
12
vendor/brick/math/src/Exception/MathException.php
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Exception;
|
||||
|
||||
/**
|
||||
* Base class for all math exceptions.
|
||||
*/
|
||||
class MathException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
12
vendor/brick/math/src/Exception/NegativeNumberException.php
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when attempting to perform an unsupported operation, such as a square root, on a negative number.
|
||||
*/
|
||||
final class NegativeNumberException extends MathException
|
||||
{
|
||||
}
|
||||
44
vendor/brick/math/src/Exception/NumberFormatException.php
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when attempting to create a number from a string with an invalid format.
|
||||
*/
|
||||
final class NumberFormatException extends MathException
|
||||
{
|
||||
/**
|
||||
* @pure
|
||||
*/
|
||||
public static function invalidFormat(string $value) : self
|
||||
{
|
||||
return new self(\sprintf(
|
||||
'The given value "%s" does not represent a valid number.',
|
||||
$value,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $char The failing character.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public static function charNotInAlphabet(string $char) : self
|
||||
{
|
||||
$ord = \ord($char);
|
||||
|
||||
if ($ord < 32 || $ord > 126) {
|
||||
$char = \strtoupper(\dechex($ord));
|
||||
|
||||
if ($ord < 10) {
|
||||
$char = '0' . $char;
|
||||
}
|
||||
} else {
|
||||
$char = '"' . $char . '"';
|
||||
}
|
||||
|
||||
return new self(\sprintf('Char %s is not a valid character in the given alphabet.', $char));
|
||||
}
|
||||
}
|
||||
19
vendor/brick/math/src/Exception/RoundingNecessaryException.php
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a number cannot be represented at the requested scale without rounding.
|
||||
*/
|
||||
final class RoundingNecessaryException extends MathException
|
||||
{
|
||||
/**
|
||||
* @pure
|
||||
*/
|
||||
public static function roundingNecessary() : RoundingNecessaryException
|
||||
{
|
||||
return new self('Rounding is necessary to represent the result of the operation at this scale.');
|
||||
}
|
||||
}
|
||||
665
vendor/brick/math/src/Internal/Calculator.php
vendored
Normal file
@ -0,0 +1,665 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Internal;
|
||||
|
||||
use Brick\Math\Exception\RoundingNecessaryException;
|
||||
use Brick\Math\RoundingMode;
|
||||
|
||||
/**
|
||||
* Performs basic operations on arbitrary size integers.
|
||||
*
|
||||
* Unless otherwise specified, all parameters must be validated as non-empty strings of digits,
|
||||
* without leading zero, and with an optional leading minus sign if the number is not zero.
|
||||
*
|
||||
* Any other parameter format will lead to undefined behaviour.
|
||||
* All methods must return strings respecting this format, unless specified otherwise.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract readonly class Calculator
|
||||
{
|
||||
/**
|
||||
* The maximum exponent value allowed for the pow() method.
|
||||
*/
|
||||
public const MAX_POWER = 1_000_000;
|
||||
|
||||
/**
|
||||
* The alphabet for converting from and to base 2 to 36, lowercase.
|
||||
*/
|
||||
public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
/**
|
||||
* Extracts the sign & digits of the operands.
|
||||
*
|
||||
* @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final protected function init(string $a, string $b) : array
|
||||
{
|
||||
return [
|
||||
$aNeg = ($a[0] === '-'),
|
||||
$bNeg = ($b[0] === '-'),
|
||||
|
||||
$aNeg ? \substr($a, 1) : $a,
|
||||
$bNeg ? \substr($b, 1) : $b,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute value of a number.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function abs(string $n) : string
|
||||
{
|
||||
return ($n[0] === '-') ? \substr($n, 1) : $n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Negates a number.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function neg(string $n) : string
|
||||
{
|
||||
if ($n === '0') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
if ($n[0] === '-') {
|
||||
return \substr($n, 1);
|
||||
}
|
||||
|
||||
return '-' . $n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two numbers.
|
||||
*
|
||||
* Returns -1 if the first number is less than, 0 if equal to, 1 if greater than the second number.
|
||||
*
|
||||
* @return -1|0|1
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function cmp(string $a, string $b) : int
|
||||
{
|
||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||
|
||||
if ($aNeg && ! $bNeg) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ($bNeg && ! $aNeg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$aLen = \strlen($aDig);
|
||||
$bLen = \strlen($bDig);
|
||||
|
||||
if ($aLen < $bLen) {
|
||||
$result = -1;
|
||||
} elseif ($aLen > $bLen) {
|
||||
$result = 1;
|
||||
} else {
|
||||
$result = $aDig <=> $bDig;
|
||||
}
|
||||
|
||||
return $aNeg ? -$result : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds two numbers.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function add(string $a, string $b) : string;
|
||||
|
||||
/**
|
||||
* Subtracts two numbers.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function sub(string $a, string $b) : string;
|
||||
|
||||
/**
|
||||
* Multiplies two numbers.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function mul(string $a, string $b) : string;
|
||||
|
||||
/**
|
||||
* Returns the quotient of the division of two numbers.
|
||||
*
|
||||
* @param string $a The dividend.
|
||||
* @param string $b The divisor, must not be zero.
|
||||
*
|
||||
* @return string The quotient.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function divQ(string $a, string $b) : string;
|
||||
|
||||
/**
|
||||
* Returns the remainder of the division of two numbers.
|
||||
*
|
||||
* @param string $a The dividend.
|
||||
* @param string $b The divisor, must not be zero.
|
||||
*
|
||||
* @return string The remainder.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function divR(string $a, string $b) : string;
|
||||
|
||||
/**
|
||||
* Returns the quotient and remainder of the division of two numbers.
|
||||
*
|
||||
* @param string $a The dividend.
|
||||
* @param string $b The divisor, must not be zero.
|
||||
*
|
||||
* @return array{string, string} An array containing the quotient and remainder.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function divQR(string $a, string $b) : array;
|
||||
|
||||
/**
|
||||
* Exponentiates a number.
|
||||
*
|
||||
* @param string $a The base number.
|
||||
* @param int $e The exponent, validated as an integer between 0 and MAX_POWER.
|
||||
*
|
||||
* @return string The power.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function pow(string $a, int $e) : string;
|
||||
|
||||
/**
|
||||
* @param string $b The modulus; must not be zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function mod(string $a, string $b) : string
|
||||
{
|
||||
return $this->divR($this->add($this->divR($a, $b), $b), $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the modular multiplicative inverse of $x modulo $m.
|
||||
*
|
||||
* If $x has no multiplicative inverse mod m, this method must return null.
|
||||
*
|
||||
* This method can be overridden by the concrete implementation if the underlying library has built-in support.
|
||||
*
|
||||
* @param string $m The modulus; must not be negative or zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function modInverse(string $x, string $m) : ?string
|
||||
{
|
||||
if ($m === '1') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
$modVal = $x;
|
||||
|
||||
if ($x[0] === '-' || ($this->cmp($this->abs($x), $m) >= 0)) {
|
||||
$modVal = $this->mod($x, $m);
|
||||
}
|
||||
|
||||
[$g, $x] = $this->gcdExtended($modVal, $m);
|
||||
|
||||
if ($g !== '1') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->mod($this->add($this->mod($x, $m), $m), $m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Raises a number into power with modulo.
|
||||
*
|
||||
* @param string $base The base number; must be positive or zero.
|
||||
* @param string $exp The exponent; must be positive or zero.
|
||||
* @param string $mod The modulus; must be strictly positive.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function modPow(string $base, string $exp, string $mod) : string;
|
||||
|
||||
/**
|
||||
* Returns the greatest common divisor of the two numbers.
|
||||
*
|
||||
* This method can be overridden by the concrete implementation if the underlying library
|
||||
* has built-in support for GCD calculations.
|
||||
*
|
||||
* @return string The GCD, always positive, or zero if both arguments are zero.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function gcd(string $a, string $b) : string
|
||||
{
|
||||
if ($a === '0') {
|
||||
return $this->abs($b);
|
||||
}
|
||||
|
||||
if ($b === '0') {
|
||||
return $this->abs($a);
|
||||
}
|
||||
|
||||
return $this->gcd($b, $this->divR($a, $b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{string, string, string} GCD, X, Y
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private function gcdExtended(string $a, string $b) : array
|
||||
{
|
||||
if ($a === '0') {
|
||||
return [$b, '0', '1'];
|
||||
}
|
||||
|
||||
[$gcd, $x1, $y1] = $this->gcdExtended($this->mod($b, $a), $a);
|
||||
|
||||
$x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1));
|
||||
$y = $x1;
|
||||
|
||||
return [$gcd, $x, $y];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the square root of the given number, rounded down.
|
||||
*
|
||||
* The result is the largest x such that x² ≤ n.
|
||||
* The input MUST NOT be negative.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
abstract public function sqrt(string $n) : string;
|
||||
|
||||
/**
|
||||
* Converts a number from an arbitrary base.
|
||||
*
|
||||
* This method can be overridden by the concrete implementation if the underlying library
|
||||
* has built-in support for base conversion.
|
||||
*
|
||||
* @param string $number The number, positive or zero, non-empty, case-insensitively validated for the given base.
|
||||
* @param int $base The base of the number, validated from 2 to 36.
|
||||
*
|
||||
* @return string The converted number, following the Calculator conventions.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function fromBase(string $number, int $base) : string
|
||||
{
|
||||
return $this->fromArbitraryBase(\strtolower($number), self::ALPHABET, $base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a number to an arbitrary base.
|
||||
*
|
||||
* This method can be overridden by the concrete implementation if the underlying library
|
||||
* has built-in support for base conversion.
|
||||
*
|
||||
* @param string $number The number to convert, following the Calculator conventions.
|
||||
* @param int $base The base to convert to, validated from 2 to 36.
|
||||
*
|
||||
* @return string The converted number, lowercase.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function toBase(string $number, int $base) : string
|
||||
{
|
||||
$negative = ($number[0] === '-');
|
||||
|
||||
if ($negative) {
|
||||
$number = \substr($number, 1);
|
||||
}
|
||||
|
||||
$number = $this->toArbitraryBase($number, self::ALPHABET, $base);
|
||||
|
||||
if ($negative) {
|
||||
return '-' . $number;
|
||||
}
|
||||
|
||||
return $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a non-negative number in an arbitrary base using a custom alphabet, to base 10.
|
||||
*
|
||||
* @param string $number The number to convert, validated as a non-empty string,
|
||||
* containing only chars in the given alphabet/base.
|
||||
* @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum.
|
||||
* @param int $base The base of the number, validated from 2 to alphabet length.
|
||||
*
|
||||
* @return string The number in base 10, following the Calculator conventions.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function fromArbitraryBase(string $number, string $alphabet, int $base) : string
|
||||
{
|
||||
// remove leading "zeros"
|
||||
$number = \ltrim($number, $alphabet[0]);
|
||||
|
||||
if ($number === '') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
// optimize for "one"
|
||||
if ($number === $alphabet[1]) {
|
||||
return '1';
|
||||
}
|
||||
|
||||
$result = '0';
|
||||
$power = '1';
|
||||
|
||||
$base = (string) $base;
|
||||
|
||||
for ($i = \strlen($number) - 1; $i >= 0; $i--) {
|
||||
$index = \strpos($alphabet, $number[$i]);
|
||||
|
||||
if ($index !== 0) {
|
||||
$result = $this->add($result, ($index === 1)
|
||||
? $power
|
||||
: $this->mul($power, (string) $index)
|
||||
);
|
||||
}
|
||||
|
||||
if ($i !== 0) {
|
||||
$power = $this->mul($power, $base);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a non-negative number to an arbitrary base using a custom alphabet.
|
||||
*
|
||||
* @param string $number The number to convert, positive or zero, following the Calculator conventions.
|
||||
* @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum.
|
||||
* @param int $base The base to convert to, validated from 2 to alphabet length.
|
||||
*
|
||||
* @return string The converted number in the given alphabet.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function toArbitraryBase(string $number, string $alphabet, int $base) : string
|
||||
{
|
||||
if ($number === '0') {
|
||||
return $alphabet[0];
|
||||
}
|
||||
|
||||
$base = (string) $base;
|
||||
$result = '';
|
||||
|
||||
while ($number !== '0') {
|
||||
[$number, $remainder] = $this->divQR($number, $base);
|
||||
$remainder = (int) $remainder;
|
||||
|
||||
$result .= $alphabet[$remainder];
|
||||
}
|
||||
|
||||
return \strrev($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a rounded division.
|
||||
*
|
||||
* Rounding is performed when the remainder of the division is not zero.
|
||||
*
|
||||
* @param string $a The dividend.
|
||||
* @param string $b The divisor, must not be zero.
|
||||
* @param RoundingMode $roundingMode The rounding mode.
|
||||
*
|
||||
* @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public function divRound(string $a, string $b, RoundingMode $roundingMode) : string
|
||||
{
|
||||
[$quotient, $remainder] = $this->divQR($a, $b);
|
||||
|
||||
$hasDiscardedFraction = ($remainder !== '0');
|
||||
$isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-');
|
||||
|
||||
$discardedFractionSign = function() use ($remainder, $b) : int {
|
||||
$r = $this->abs($this->mul($remainder, '2'));
|
||||
$b = $this->abs($b);
|
||||
|
||||
return $this->cmp($r, $b);
|
||||
};
|
||||
|
||||
$increment = false;
|
||||
|
||||
switch ($roundingMode) {
|
||||
case RoundingMode::UNNECESSARY:
|
||||
if ($hasDiscardedFraction) {
|
||||
throw RoundingNecessaryException::roundingNecessary();
|
||||
}
|
||||
break;
|
||||
|
||||
case RoundingMode::UP:
|
||||
$increment = $hasDiscardedFraction;
|
||||
break;
|
||||
|
||||
case RoundingMode::DOWN:
|
||||
break;
|
||||
|
||||
case RoundingMode::CEILING:
|
||||
$increment = $hasDiscardedFraction && $isPositiveOrZero;
|
||||
break;
|
||||
|
||||
case RoundingMode::FLOOR:
|
||||
$increment = $hasDiscardedFraction && ! $isPositiveOrZero;
|
||||
break;
|
||||
|
||||
case RoundingMode::HALF_UP:
|
||||
$increment = $discardedFractionSign() >= 0;
|
||||
break;
|
||||
|
||||
case RoundingMode::HALF_DOWN:
|
||||
$increment = $discardedFractionSign() > 0;
|
||||
break;
|
||||
|
||||
case RoundingMode::HALF_CEILING:
|
||||
$increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0;
|
||||
break;
|
||||
|
||||
case RoundingMode::HALF_FLOOR:
|
||||
$increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
|
||||
break;
|
||||
|
||||
case RoundingMode::HALF_EVEN:
|
||||
$lastDigit = (int) $quotient[-1];
|
||||
$lastDigitIsEven = ($lastDigit % 2 === 0);
|
||||
$increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($increment) {
|
||||
return $this->add($quotient, $isPositiveOrZero ? '1' : '-1');
|
||||
}
|
||||
|
||||
return $quotient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates bitwise AND of two numbers.
|
||||
*
|
||||
* This method can be overridden by the concrete implementation if the underlying library
|
||||
* has built-in support for bitwise operations.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function and(string $a, string $b) : string
|
||||
{
|
||||
return $this->bitwise('and', $a, $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates bitwise OR of two numbers.
|
||||
*
|
||||
* This method can be overridden by the concrete implementation if the underlying library
|
||||
* has built-in support for bitwise operations.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function or(string $a, string $b) : string
|
||||
{
|
||||
return $this->bitwise('or', $a, $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates bitwise XOR of two numbers.
|
||||
*
|
||||
* This method can be overridden by the concrete implementation if the underlying library
|
||||
* has built-in support for bitwise operations.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
public function xor(string $a, string $b) : string
|
||||
{
|
||||
return $this->bitwise('xor', $a, $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a bitwise operation on a decimal number.
|
||||
*
|
||||
* @param 'and'|'or'|'xor' $operator The operator to use.
|
||||
* @param string $a The left operand.
|
||||
* @param string $b The right operand.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private function bitwise(string $operator, string $a, string $b) : string
|
||||
{
|
||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||
|
||||
$aBin = $this->toBinary($aDig);
|
||||
$bBin = $this->toBinary($bDig);
|
||||
|
||||
$aLen = \strlen($aBin);
|
||||
$bLen = \strlen($bBin);
|
||||
|
||||
if ($aLen > $bLen) {
|
||||
$bBin = \str_repeat("\x00", $aLen - $bLen) . $bBin;
|
||||
} elseif ($bLen > $aLen) {
|
||||
$aBin = \str_repeat("\x00", $bLen - $aLen) . $aBin;
|
||||
}
|
||||
|
||||
if ($aNeg) {
|
||||
$aBin = $this->twosComplement($aBin);
|
||||
}
|
||||
if ($bNeg) {
|
||||
$bBin = $this->twosComplement($bBin);
|
||||
}
|
||||
|
||||
$value = match ($operator) {
|
||||
'and' => $aBin & $bBin,
|
||||
'or' => $aBin | $bBin,
|
||||
'xor' => $aBin ^ $bBin,
|
||||
};
|
||||
|
||||
$negative = match ($operator) {
|
||||
'and' => $aNeg and $bNeg,
|
||||
'or' => $aNeg or $bNeg,
|
||||
'xor' => $aNeg xor $bNeg,
|
||||
};
|
||||
|
||||
if ($negative) {
|
||||
$value = $this->twosComplement($value);
|
||||
}
|
||||
|
||||
$result = $this->toDecimal($value);
|
||||
|
||||
return $negative ? $this->neg($result) : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $number A positive, binary number.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private function twosComplement(string $number) : string
|
||||
{
|
||||
$xor = \str_repeat("\xff", \strlen($number));
|
||||
|
||||
$number ^= $xor;
|
||||
|
||||
for ($i = \strlen($number) - 1; $i >= 0; $i--) {
|
||||
$byte = \ord($number[$i]);
|
||||
|
||||
if (++$byte !== 256) {
|
||||
$number[$i] = \chr($byte);
|
||||
break;
|
||||
}
|
||||
|
||||
$number[$i] = "\x00";
|
||||
|
||||
if ($i === 0) {
|
||||
$number = "\x01" . $number;
|
||||
}
|
||||
}
|
||||
|
||||
return $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a decimal number to a binary string.
|
||||
*
|
||||
* @param string $number The number to convert, positive or zero, only digits.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private function toBinary(string $number) : string
|
||||
{
|
||||
$result = '';
|
||||
|
||||
while ($number !== '0') {
|
||||
[$number, $remainder] = $this->divQR($number, '256');
|
||||
$result .= \chr((int) $remainder);
|
||||
}
|
||||
|
||||
return \strrev($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the positive decimal representation of a binary number.
|
||||
*
|
||||
* @param string $bytes The bytes representing the number.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private function toDecimal(string $bytes) : string
|
||||
{
|
||||
$result = '0';
|
||||
$power = '1';
|
||||
|
||||
for ($i = \strlen($bytes) - 1; $i >= 0; $i--) {
|
||||
$index = \ord($bytes[$i]);
|
||||
|
||||
if ($index !== 0) {
|
||||
$result = $this->add($result, ($index === 1)
|
||||
? $power
|
||||
: $this->mul($power, (string) $index)
|
||||
);
|
||||
}
|
||||
|
||||
if ($i !== 0) {
|
||||
$power = $this->mul($power, '256');
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
73
vendor/brick/math/src/Internal/Calculator/BcMathCalculator.php
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Internal\Calculator;
|
||||
|
||||
use Brick\Math\Internal\Calculator;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* Calculator implementation built around the bcmath library.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final readonly class BcMathCalculator extends Calculator
|
||||
{
|
||||
#[Override]
|
||||
public function add(string $a, string $b) : string
|
||||
{
|
||||
return \bcadd($a, $b, 0);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function sub(string $a, string $b) : string
|
||||
{
|
||||
return \bcsub($a, $b, 0);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function mul(string $a, string $b) : string
|
||||
{
|
||||
return \bcmul($a, $b, 0);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function divQ(string $a, string $b) : string
|
||||
{
|
||||
return \bcdiv($a, $b, 0);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function divR(string $a, string $b) : string
|
||||
{
|
||||
return \bcmod($a, $b, 0);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function divQR(string $a, string $b) : array
|
||||
{
|
||||
$q = \bcdiv($a, $b, 0);
|
||||
$r = \bcmod($a, $b, 0);
|
||||
|
||||
return [$q, $r];
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function pow(string $a, int $e) : string
|
||||
{
|
||||
return \bcpow($a, (string) $e, 0);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function modPow(string $base, string $exp, string $mod) : string
|
||||
{
|
||||
return \bcpowmod($base, $exp, $mod, 0);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function sqrt(string $n) : string
|
||||
{
|
||||
return \bcsqrt($n, 0);
|
||||
}
|
||||
}
|
||||
128
vendor/brick/math/src/Internal/Calculator/GmpCalculator.php
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Internal\Calculator;
|
||||
|
||||
use Brick\Math\Internal\Calculator;
|
||||
use GMP;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* Calculator implementation built around the GMP library.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final readonly class GmpCalculator extends Calculator
|
||||
{
|
||||
#[Override]
|
||||
public function add(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_add($a, $b));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function sub(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_sub($a, $b));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function mul(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_mul($a, $b));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function divQ(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_div_q($a, $b));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function divR(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_div_r($a, $b));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function divQR(string $a, string $b) : array
|
||||
{
|
||||
[$q, $r] = \gmp_div_qr($a, $b);
|
||||
|
||||
/**
|
||||
* @var GMP $q
|
||||
* @var GMP $r
|
||||
*/
|
||||
return [
|
||||
\gmp_strval($q),
|
||||
\gmp_strval($r)
|
||||
];
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function pow(string $a, int $e) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_pow($a, $e));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function modInverse(string $x, string $m) : ?string
|
||||
{
|
||||
$result = \gmp_invert($x, $m);
|
||||
|
||||
if ($result === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return \gmp_strval($result);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function modPow(string $base, string $exp, string $mod) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_powm($base, $exp, $mod));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function gcd(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_gcd($a, $b));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function fromBase(string $number, int $base) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_init($number, $base));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function toBase(string $number, int $base) : string
|
||||
{
|
||||
return \gmp_strval($number, $base);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function and(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_and($a, $b));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function or(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_or($a, $b));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function xor(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_xor($a, $b));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function sqrt(string $n) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_sqrt($n));
|
||||
}
|
||||
}
|
||||
603
vendor/brick/math/src/Internal/Calculator/NativeCalculator.php
vendored
Normal file
@ -0,0 +1,603 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Internal\Calculator;
|
||||
|
||||
use Brick\Math\Internal\Calculator;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* Calculator implementation using only native PHP code.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final readonly class NativeCalculator extends Calculator
|
||||
{
|
||||
/**
|
||||
* The max number of digits the platform can natively add, subtract, multiply or divide without overflow.
|
||||
* For multiplication, this represents the max sum of the lengths of both operands.
|
||||
*
|
||||
* In addition, it is assumed that an extra digit can hold a carry (1) without overflowing.
|
||||
* Example: 32-bit: max number 1,999,999,999 (9 digits + carry)
|
||||
* 64-bit: max number 1,999,999,999,999,999,999 (18 digits + carry)
|
||||
*/
|
||||
private int $maxDigits;
|
||||
|
||||
/**
|
||||
* @pure
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->maxDigits = match (PHP_INT_SIZE) {
|
||||
4 => 9,
|
||||
8 => 18,
|
||||
};
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function add(string $a, string $b) : string
|
||||
{
|
||||
/**
|
||||
* @var numeric-string $a
|
||||
* @var numeric-string $b
|
||||
*/
|
||||
$result = $a + $b;
|
||||
|
||||
if (is_int($result)) {
|
||||
return (string) $result;
|
||||
}
|
||||
|
||||
if ($a === '0') {
|
||||
return $b;
|
||||
}
|
||||
|
||||
if ($b === '0') {
|
||||
return $a;
|
||||
}
|
||||
|
||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||
|
||||
$result = $aNeg === $bNeg ? $this->doAdd($aDig, $bDig) : $this->doSub($aDig, $bDig);
|
||||
|
||||
if ($aNeg) {
|
||||
$result = $this->neg($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function sub(string $a, string $b) : string
|
||||
{
|
||||
return $this->add($a, $this->neg($b));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function mul(string $a, string $b) : string
|
||||
{
|
||||
/**
|
||||
* @var numeric-string $a
|
||||
* @var numeric-string $b
|
||||
*/
|
||||
$result = $a * $b;
|
||||
|
||||
if (is_int($result)) {
|
||||
return (string) $result;
|
||||
}
|
||||
|
||||
if ($a === '0' || $b === '0') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
if ($a === '1') {
|
||||
return $b;
|
||||
}
|
||||
|
||||
if ($b === '1') {
|
||||
return $a;
|
||||
}
|
||||
|
||||
if ($a === '-1') {
|
||||
return $this->neg($b);
|
||||
}
|
||||
|
||||
if ($b === '-1') {
|
||||
return $this->neg($a);
|
||||
}
|
||||
|
||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||
|
||||
$result = $this->doMul($aDig, $bDig);
|
||||
|
||||
if ($aNeg !== $bNeg) {
|
||||
$result = $this->neg($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function divQ(string $a, string $b) : string
|
||||
{
|
||||
return $this->divQR($a, $b)[0];
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function divR(string $a, string $b): string
|
||||
{
|
||||
return $this->divQR($a, $b)[1];
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function divQR(string $a, string $b) : array
|
||||
{
|
||||
if ($a === '0') {
|
||||
return ['0', '0'];
|
||||
}
|
||||
|
||||
if ($a === $b) {
|
||||
return ['1', '0'];
|
||||
}
|
||||
|
||||
if ($b === '1') {
|
||||
return [$a, '0'];
|
||||
}
|
||||
|
||||
if ($b === '-1') {
|
||||
return [$this->neg($a), '0'];
|
||||
}
|
||||
|
||||
/** @var numeric-string $a */
|
||||
$na = $a * 1; // cast to number
|
||||
|
||||
if (is_int($na)) {
|
||||
/** @var numeric-string $b */
|
||||
$nb = $b * 1;
|
||||
|
||||
if (is_int($nb)) {
|
||||
// the only division that may overflow is PHP_INT_MIN / -1,
|
||||
// which cannot happen here as we've already handled a divisor of -1 above.
|
||||
$q = intdiv($na, $nb);
|
||||
$r = $na % $nb;
|
||||
|
||||
return [
|
||||
(string) $q,
|
||||
(string) $r
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||
|
||||
[$q, $r] = $this->doDiv($aDig, $bDig);
|
||||
|
||||
if ($aNeg !== $bNeg) {
|
||||
$q = $this->neg($q);
|
||||
}
|
||||
|
||||
if ($aNeg) {
|
||||
$r = $this->neg($r);
|
||||
}
|
||||
|
||||
return [$q, $r];
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function pow(string $a, int $e) : string
|
||||
{
|
||||
if ($e === 0) {
|
||||
return '1';
|
||||
}
|
||||
|
||||
if ($e === 1) {
|
||||
return $a;
|
||||
}
|
||||
|
||||
$odd = $e % 2;
|
||||
$e -= $odd;
|
||||
|
||||
$aa = $this->mul($a, $a);
|
||||
|
||||
$result = $this->pow($aa, $e / 2);
|
||||
|
||||
if ($odd === 1) {
|
||||
$result = $this->mul($result, $a);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/
|
||||
*/
|
||||
#[Override]
|
||||
public function modPow(string $base, string $exp, string $mod) : string
|
||||
{
|
||||
// special case: the algorithm below fails with 0 power 0 mod 1 (returns 1 instead of 0)
|
||||
if ($base === '0' && $exp === '0' && $mod === '1') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
// special case: the algorithm below fails with power 0 mod 1 (returns 1 instead of 0)
|
||||
if ($exp === '0' && $mod === '1') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
$x = $base;
|
||||
|
||||
$res = '1';
|
||||
|
||||
// numbers are positive, so we can use remainder instead of modulo
|
||||
$x = $this->divR($x, $mod);
|
||||
|
||||
while ($exp !== '0') {
|
||||
if (in_array($exp[-1], ['1', '3', '5', '7', '9'])) { // odd
|
||||
$res = $this->divR($this->mul($res, $x), $mod);
|
||||
}
|
||||
|
||||
$exp = $this->divQ($exp, '2');
|
||||
$x = $this->divR($this->mul($x, $x), $mod);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapted from https://cp-algorithms.com/num_methods/roots_newton.html
|
||||
*/
|
||||
#[Override]
|
||||
public function sqrt(string $n) : string
|
||||
{
|
||||
if ($n === '0') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
// initial approximation
|
||||
$x = \str_repeat('9', \intdiv(\strlen($n), 2) ?: 1);
|
||||
|
||||
$decreased = false;
|
||||
|
||||
for (;;) {
|
||||
$nx = $this->divQ($this->add($x, $this->divQ($n, $x)), '2');
|
||||
|
||||
if ($x === $nx || $this->cmp($nx, $x) > 0 && $decreased) {
|
||||
break;
|
||||
}
|
||||
|
||||
$decreased = $this->cmp($nx, $x) < 0;
|
||||
$x = $nx;
|
||||
}
|
||||
|
||||
return $x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the addition of two non-signed large integers.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private function doAdd(string $a, string $b) : string
|
||||
{
|
||||
[$a, $b, $length] = $this->pad($a, $b);
|
||||
|
||||
$carry = 0;
|
||||
$result = '';
|
||||
|
||||
for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) {
|
||||
$blockLength = $this->maxDigits;
|
||||
|
||||
if ($i < 0) {
|
||||
$blockLength += $i;
|
||||
$i = 0;
|
||||
}
|
||||
|
||||
/** @var numeric-string $blockA */
|
||||
$blockA = \substr($a, $i, $blockLength);
|
||||
|
||||
/** @var numeric-string $blockB */
|
||||
$blockB = \substr($b, $i, $blockLength);
|
||||
|
||||
$sum = (string) ($blockA + $blockB + $carry);
|
||||
$sumLength = \strlen($sum);
|
||||
|
||||
if ($sumLength > $blockLength) {
|
||||
$sum = \substr($sum, 1);
|
||||
$carry = 1;
|
||||
} else {
|
||||
if ($sumLength < $blockLength) {
|
||||
$sum = \str_repeat('0', $blockLength - $sumLength) . $sum;
|
||||
}
|
||||
$carry = 0;
|
||||
}
|
||||
|
||||
$result = $sum . $result;
|
||||
|
||||
if ($i === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($carry === 1) {
|
||||
$result = '1' . $result;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the subtraction of two non-signed large integers.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private function doSub(string $a, string $b) : string
|
||||
{
|
||||
if ($a === $b) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
// Ensure that we always subtract to a positive result: biggest minus smallest.
|
||||
$cmp = $this->doCmp($a, $b);
|
||||
|
||||
$invert = ($cmp === -1);
|
||||
|
||||
if ($invert) {
|
||||
$c = $a;
|
||||
$a = $b;
|
||||
$b = $c;
|
||||
}
|
||||
|
||||
[$a, $b, $length] = $this->pad($a, $b);
|
||||
|
||||
$carry = 0;
|
||||
$result = '';
|
||||
|
||||
$complement = 10 ** $this->maxDigits;
|
||||
|
||||
for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) {
|
||||
$blockLength = $this->maxDigits;
|
||||
|
||||
if ($i < 0) {
|
||||
$blockLength += $i;
|
||||
$i = 0;
|
||||
}
|
||||
|
||||
/** @var numeric-string $blockA */
|
||||
$blockA = \substr($a, $i, $blockLength);
|
||||
|
||||
/** @var numeric-string $blockB */
|
||||
$blockB = \substr($b, $i, $blockLength);
|
||||
|
||||
$sum = $blockA - $blockB - $carry;
|
||||
|
||||
if ($sum < 0) {
|
||||
$sum += $complement;
|
||||
$carry = 1;
|
||||
} else {
|
||||
$carry = 0;
|
||||
}
|
||||
|
||||
$sum = (string) $sum;
|
||||
$sumLength = \strlen($sum);
|
||||
|
||||
if ($sumLength < $blockLength) {
|
||||
$sum = \str_repeat('0', $blockLength - $sumLength) . $sum;
|
||||
}
|
||||
|
||||
$result = $sum . $result;
|
||||
|
||||
if ($i === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Carry cannot be 1 when the loop ends, as a > b
|
||||
assert($carry === 0);
|
||||
|
||||
$result = \ltrim($result, '0');
|
||||
|
||||
if ($invert) {
|
||||
$result = $this->neg($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the multiplication of two non-signed large integers.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private function doMul(string $a, string $b) : string
|
||||
{
|
||||
$x = \strlen($a);
|
||||
$y = \strlen($b);
|
||||
|
||||
$maxDigits = \intdiv($this->maxDigits, 2);
|
||||
$complement = 10 ** $maxDigits;
|
||||
|
||||
$result = '0';
|
||||
|
||||
for ($i = $x - $maxDigits;; $i -= $maxDigits) {
|
||||
$blockALength = $maxDigits;
|
||||
|
||||
if ($i < 0) {
|
||||
$blockALength += $i;
|
||||
$i = 0;
|
||||
}
|
||||
|
||||
$blockA = (int) \substr($a, $i, $blockALength);
|
||||
|
||||
$line = '';
|
||||
$carry = 0;
|
||||
|
||||
for ($j = $y - $maxDigits;; $j -= $maxDigits) {
|
||||
$blockBLength = $maxDigits;
|
||||
|
||||
if ($j < 0) {
|
||||
$blockBLength += $j;
|
||||
$j = 0;
|
||||
}
|
||||
|
||||
$blockB = (int) \substr($b, $j, $blockBLength);
|
||||
|
||||
$mul = $blockA * $blockB + $carry;
|
||||
$value = $mul % $complement;
|
||||
$carry = ($mul - $value) / $complement;
|
||||
|
||||
$value = (string) $value;
|
||||
$value = \str_pad($value, $maxDigits, '0', STR_PAD_LEFT);
|
||||
|
||||
$line = $value . $line;
|
||||
|
||||
if ($j === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($carry !== 0) {
|
||||
$line = $carry . $line;
|
||||
}
|
||||
|
||||
$line = \ltrim($line, '0');
|
||||
|
||||
if ($line !== '') {
|
||||
$line .= \str_repeat('0', $x - $blockALength - $i);
|
||||
$result = $this->add($result, $line);
|
||||
}
|
||||
|
||||
if ($i === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the division of two non-signed large integers.
|
||||
*
|
||||
* @return string[] The quotient and remainder.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private function doDiv(string $a, string $b) : array
|
||||
{
|
||||
$cmp = $this->doCmp($a, $b);
|
||||
|
||||
if ($cmp === -1) {
|
||||
return ['0', $a];
|
||||
}
|
||||
|
||||
$x = \strlen($a);
|
||||
$y = \strlen($b);
|
||||
|
||||
// we now know that a >= b && x >= y
|
||||
|
||||
$q = '0'; // quotient
|
||||
$r = $a; // remainder
|
||||
$z = $y; // focus length, always $y or $y+1
|
||||
|
||||
/** @var numeric-string $b */
|
||||
$nb = $b * 1; // cast to number
|
||||
// performance optimization in cases where the remainder will never cause int overflow
|
||||
if (is_int(($nb - 1) * 10 + 9)) {
|
||||
$r = (int) \substr($a, 0, $z - 1);
|
||||
|
||||
for ($i = $z - 1; $i < $x; $i++) {
|
||||
$n = $r * 10 + (int) $a[$i];
|
||||
/** @var int $nb */
|
||||
$q .= \intdiv($n, $nb);
|
||||
$r = $n % $nb;
|
||||
}
|
||||
|
||||
return [\ltrim($q, '0') ?: '0', (string) $r];
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
$focus = \substr($a, 0, $z);
|
||||
|
||||
$cmp = $this->doCmp($focus, $b);
|
||||
|
||||
if ($cmp === -1) {
|
||||
if ($z === $x) { // remainder < dividend
|
||||
break;
|
||||
}
|
||||
|
||||
$z++;
|
||||
}
|
||||
|
||||
$zeros = \str_repeat('0', $x - $z);
|
||||
|
||||
$q = $this->add($q, '1' . $zeros);
|
||||
$a = $this->sub($a, $b . $zeros);
|
||||
|
||||
$r = $a;
|
||||
|
||||
if ($r === '0') { // remainder == 0
|
||||
break;
|
||||
}
|
||||
|
||||
$x = \strlen($a);
|
||||
|
||||
if ($x < $y) { // remainder < dividend
|
||||
break;
|
||||
}
|
||||
|
||||
$z = $y;
|
||||
}
|
||||
|
||||
return [$q, $r];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two non-signed large numbers.
|
||||
*
|
||||
* @return -1|0|1
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private function doCmp(string $a, string $b) : int
|
||||
{
|
||||
$x = \strlen($a);
|
||||
$y = \strlen($b);
|
||||
|
||||
$cmp = $x <=> $y;
|
||||
|
||||
if ($cmp !== 0) {
|
||||
return $cmp;
|
||||
}
|
||||
|
||||
return \strcmp($a, $b) <=> 0; // enforce -1|0|1
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads the left of one of the given numbers with zeros if necessary to make both numbers the same length.
|
||||
*
|
||||
* The numbers must only consist of digits, without leading minus sign.
|
||||
*
|
||||
* @return array{string, string, int}
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
private function pad(string $a, string $b) : array
|
||||
{
|
||||
$x = \strlen($a);
|
||||
$y = \strlen($b);
|
||||
|
||||
if ($x > $y) {
|
||||
$b = \str_repeat('0', $x - $y) . $b;
|
||||
|
||||
return [$a, $b, $x];
|
||||
}
|
||||
|
||||
if ($x < $y) {
|
||||
$a = \str_repeat('0', $y - $x) . $a;
|
||||
|
||||
return [$a, $b, $y];
|
||||
}
|
||||
|
||||
return [$a, $b, $x];
|
||||
}
|
||||
}
|
||||
73
vendor/brick/math/src/Internal/CalculatorRegistry.php
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Internal;
|
||||
|
||||
use function extension_loaded;
|
||||
|
||||
/**
|
||||
* Stores the current Calculator instance used by BigNumber classes.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CalculatorRegistry
|
||||
{
|
||||
/**
|
||||
* The Calculator instance in use.
|
||||
*/
|
||||
private static ?Calculator $instance = null;
|
||||
|
||||
/**
|
||||
* Sets the Calculator instance to use.
|
||||
*
|
||||
* An instance is typically set only in unit tests: autodetect is usually the best option.
|
||||
*
|
||||
* @param Calculator|null $calculator The calculator instance, or null to revert to autodetect.
|
||||
*/
|
||||
final public static function set(?Calculator $calculator) : void
|
||||
{
|
||||
self::$instance = $calculator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Calculator instance to use.
|
||||
*
|
||||
* If none has been explicitly set, the fastest available implementation will be returned.
|
||||
*
|
||||
* Note: even though this method is not technically pure, it is considered pure when used in a normal context, when
|
||||
* only relying on autodetect.
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
final public static function get() : Calculator
|
||||
{
|
||||
/** @phpstan-ignore impure.staticPropertyAccess */
|
||||
if (self::$instance === null) {
|
||||
/** @phpstan-ignore impure.propertyAssign */
|
||||
self::$instance = self::detect();
|
||||
}
|
||||
|
||||
/** @phpstan-ignore impure.staticPropertyAccess */
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fastest available Calculator implementation.
|
||||
*
|
||||
* @pure
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private static function detect() : Calculator
|
||||
{
|
||||
if (extension_loaded('gmp')) {
|
||||
return new Calculator\GmpCalculator();
|
||||
}
|
||||
|
||||
if (extension_loaded('bcmath')) {
|
||||
return new Calculator\BcMathCalculator();
|
||||
}
|
||||
|
||||
return new Calculator\NativeCalculator();
|
||||
}
|
||||
}
|
||||
98
vendor/brick/math/src/RoundingMode.php
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math;
|
||||
|
||||
/**
|
||||
* Specifies a rounding behavior for numerical operations capable of discarding precision.
|
||||
*
|
||||
* Each rounding mode indicates how the least significant returned digit of a rounded result
|
||||
* is to be calculated. If fewer digits are returned than the digits needed to represent the
|
||||
* exact numerical result, the discarded digits will be referred to as the discarded fraction
|
||||
* regardless the digits' contribution to the value of the number. In other words, considered
|
||||
* as a numerical value, the discarded fraction could have an absolute value greater than one.
|
||||
*/
|
||||
enum RoundingMode
|
||||
{
|
||||
/**
|
||||
* Asserts that the requested operation has an exact result, hence no rounding is necessary.
|
||||
*
|
||||
* If this rounding mode is specified on an operation that yields a result that
|
||||
* cannot be represented at the requested scale, a RoundingNecessaryException is thrown.
|
||||
*/
|
||||
case UNNECESSARY;
|
||||
|
||||
/**
|
||||
* Rounds away from zero.
|
||||
*
|
||||
* Always increments the digit prior to a nonzero discarded fraction.
|
||||
* Note that this rounding mode never decreases the magnitude of the calculated value.
|
||||
*/
|
||||
case UP;
|
||||
|
||||
/**
|
||||
* Rounds towards zero.
|
||||
*
|
||||
* Never increments the digit prior to a discarded fraction (i.e., truncates).
|
||||
* Note that this rounding mode never increases the magnitude of the calculated value.
|
||||
*/
|
||||
case DOWN;
|
||||
|
||||
/**
|
||||
* Rounds towards positive infinity.
|
||||
*
|
||||
* If the result is positive, behaves as for UP; if negative, behaves as for DOWN.
|
||||
* Note that this rounding mode never decreases the calculated value.
|
||||
*/
|
||||
case CEILING;
|
||||
|
||||
/**
|
||||
* Rounds towards negative infinity.
|
||||
*
|
||||
* If the result is positive, behave as for DOWN; if negative, behave as for UP.
|
||||
* Note that this rounding mode never increases the calculated value.
|
||||
*/
|
||||
case FLOOR;
|
||||
|
||||
/**
|
||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.
|
||||
*
|
||||
* Behaves as for UP if the discarded fraction is >= 0.5; otherwise, behaves as for DOWN.
|
||||
* Note that this is the rounding mode commonly taught at school.
|
||||
*/
|
||||
case HALF_UP;
|
||||
|
||||
/**
|
||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.
|
||||
*
|
||||
* Behaves as for UP if the discarded fraction is > 0.5; otherwise, behaves as for DOWN.
|
||||
*/
|
||||
case HALF_DOWN;
|
||||
|
||||
/**
|
||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards positive infinity.
|
||||
*
|
||||
* If the result is positive, behaves as for HALF_UP; if negative, behaves as for HALF_DOWN.
|
||||
*/
|
||||
case HALF_CEILING;
|
||||
|
||||
/**
|
||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards negative infinity.
|
||||
*
|
||||
* If the result is positive, behaves as for HALF_DOWN; if negative, behaves as for HALF_UP.
|
||||
*/
|
||||
case HALF_FLOOR;
|
||||
|
||||
/**
|
||||
* Rounds towards the "nearest neighbor" unless both neighbors are equidistant, in which case rounds towards the even neighbor.
|
||||
*
|
||||
* Behaves as for HALF_UP if the digit to the left of the discarded fraction is odd;
|
||||
* behaves as for HALF_DOWN if it's even.
|
||||
*
|
||||
* Note that this is the rounding mode that statistically minimizes
|
||||
* cumulative error when applied repeatedly over a sequence of calculations.
|
||||
* It is sometimes known as "Banker's rounding", and is chiefly used in the USA.
|
||||
*/
|
||||
case HALF_EVEN;
|
||||
}
|
||||
579
vendor/composer/ClassLoader.php
vendored
Normal file
@ -0,0 +1,579 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see https://www.php-fig.org/psr/psr-0/
|
||||
* @see https://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
/** @var \Closure(string):void */
|
||||
private static $includeFile;
|
||||
|
||||
/** @var string|null */
|
||||
private $vendorDir;
|
||||
|
||||
// PSR-4
|
||||
/**
|
||||
* @var array<string, array<string, int>>
|
||||
*/
|
||||
private $prefixLengthsPsr4 = array();
|
||||
/**
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
private $prefixDirsPsr4 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
/**
|
||||
* List of PSR-0 prefixes
|
||||
*
|
||||
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
|
||||
*
|
||||
* @var array<string, array<string, list<string>>>
|
||||
*/
|
||||
private $prefixesPsr0 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
/** @var bool */
|
||||
private $useIncludePath = false;
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private $classMap = array();
|
||||
|
||||
/** @var bool */
|
||||
private $classMapAuthoritative = false;
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private $missingClasses = array();
|
||||
|
||||
/** @var string|null */
|
||||
private $apcuPrefix;
|
||||
|
||||
/**
|
||||
* @var array<string, self>
|
||||
*/
|
||||
private static $registeredLoaders = array();
|
||||
|
||||
/**
|
||||
* @param string|null $vendorDir
|
||||
*/
|
||||
public function __construct($vendorDir = null)
|
||||
{
|
||||
$this->vendorDir = $vendorDir;
|
||||
self::initializeIncludeClosure();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string> Array of classname => path
|
||||
*/
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $classMap Class to filename map
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 base directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
|
||||
if (null === $this->vendorDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($prepend) {
|
||||
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
|
||||
} else {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
self::$registeredLoaders[$this->vendorDir] = $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
|
||||
if (null !== $this->vendorDir) {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return true|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
$includeFile = self::$includeFile;
|
||||
$includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently registered loaders keyed by their corresponding vendor directories.
|
||||
*
|
||||
* @return array<string, self>
|
||||
*/
|
||||
public static function getRegisteredLoaders()
|
||||
{
|
||||
return self::$registeredLoaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $ext
|
||||
* @return string|false
|
||||
*/
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath . '\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private static function initializeIncludeClosure()
|
||||
{
|
||||
if (self::$includeFile !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*
|
||||
* @param string $file
|
||||
* @return void
|
||||
*/
|
||||
self::$includeFile = \Closure::bind(static function($file) {
|
||||
include $file;
|
||||
}, null, null);
|
||||
}
|
||||
}
|
||||
396
vendor/composer/InstalledVersions.php
vendored
Normal file
@ -0,0 +1,396 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
/**
|
||||
* This class is copied in every Composer installed project and available to all
|
||||
*
|
||||
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
|
||||
*
|
||||
* To require its presence, you can require `composer-runtime-api ^2.0`
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class InstalledVersions
|
||||
{
|
||||
/**
|
||||
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
|
||||
* @internal
|
||||
*/
|
||||
private static $selfDir = null;
|
||||
|
||||
/**
|
||||
* @var mixed[]|null
|
||||
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private static $installedIsLocalDir;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private static $canGetVendors;
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static $installedByVendor = array();
|
||||
|
||||
/**
|
||||
* Returns a list of all package names which are present, either by being installed, replaced or provided
|
||||
*
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackages()
|
||||
{
|
||||
$packages = array();
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
$packages[] = array_keys($installed['versions']);
|
||||
}
|
||||
|
||||
if (1 === \count($packages)) {
|
||||
return $packages[0];
|
||||
}
|
||||
|
||||
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all package names with a specific type e.g. 'library'
|
||||
*
|
||||
* @param string $type
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackagesByType($type)
|
||||
{
|
||||
$packagesByType = array();
|
||||
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
foreach ($installed['versions'] as $name => $package) {
|
||||
if (isset($package['type']) && $package['type'] === $type) {
|
||||
$packagesByType[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $packagesByType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package is installed
|
||||
*
|
||||
* This also returns true if the package name is provided or replaced by another package
|
||||
*
|
||||
* @param string $packageName
|
||||
* @param bool $includeDevRequirements
|
||||
* @return bool
|
||||
*/
|
||||
public static function isInstalled($packageName, $includeDevRequirements = true)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (isset($installed['versions'][$packageName])) {
|
||||
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package satisfies a version constraint
|
||||
*
|
||||
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
|
||||
*
|
||||
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
|
||||
*
|
||||
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
|
||||
* @param string $packageName
|
||||
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
|
||||
* @return bool
|
||||
*/
|
||||
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||
{
|
||||
$constraint = $parser->parseConstraints((string) $constraint);
|
||||
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||
|
||||
return $provided->matches($constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a version constraint representing all the range(s) which are installed for a given package
|
||||
*
|
||||
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
|
||||
* whether a given version of a package is installed, and not just whether it exists
|
||||
*
|
||||
* @param string $packageName
|
||||
* @return string Version constraint usable with composer/semver
|
||||
*/
|
||||
public static function getVersionRanges($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ranges = array();
|
||||
if (isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
|
||||
}
|
||||
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
|
||||
}
|
||||
if (array_key_exists('provided', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
|
||||
}
|
||||
|
||||
return implode(' || ', $ranges);
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getPrettyVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
|
||||
*/
|
||||
public static function getReference($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['reference'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['reference'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
|
||||
*/
|
||||
public static function getInstallPath($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
|
||||
*/
|
||||
public static function getRootPackage()
|
||||
{
|
||||
$installed = self::getInstalled();
|
||||
|
||||
return $installed[0]['root'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw installed.php data for custom implementations
|
||||
*
|
||||
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
|
||||
* @return array[]
|
||||
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
|
||||
*/
|
||||
public static function getRawData()
|
||||
{
|
||||
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
self::$installed = include __DIR__ . '/installed.php';
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
return self::$installed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw data of all installed.php which are currently loaded for custom implementations
|
||||
*
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
public static function getAllRawData()
|
||||
{
|
||||
return self::getInstalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets you reload the static array from another file
|
||||
*
|
||||
* This is only useful for complex integrations in which a project needs to use
|
||||
* this class but then also needs to execute another project's autoloader in process,
|
||||
* and wants to ensure both projects have access to their version of installed.php.
|
||||
*
|
||||
* A typical case would be PHPUnit, where it would need to make sure it reads all
|
||||
* the data it needs from this class, then call reload() with
|
||||
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
|
||||
* the project in which it runs can then also use this class safely, without
|
||||
* interference between PHPUnit's dependencies and the project's dependencies.
|
||||
*
|
||||
* @param array[] $data A vendor/composer/installed.php data set
|
||||
* @return void
|
||||
*
|
||||
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
|
||||
*/
|
||||
public static function reload($data)
|
||||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
|
||||
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
|
||||
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
|
||||
// so we have to assume it does not, and that may result in duplicate data being returned when listing
|
||||
// all installed packages for example
|
||||
self::$installedIsLocalDir = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private static function getSelfDir()
|
||||
{
|
||||
if (self::$selfDir === null) {
|
||||
self::$selfDir = strtr(__DIR__, '\\', '/');
|
||||
}
|
||||
|
||||
return self::$selfDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static function getInstalled()
|
||||
{
|
||||
if (null === self::$canGetVendors) {
|
||||
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
|
||||
}
|
||||
|
||||
$installed = array();
|
||||
$copiedLocalDir = false;
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
$selfDir = self::getSelfDir();
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
$vendorDir = strtr($vendorDir, '\\', '/');
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require $vendorDir.'/composer/installed.php';
|
||||
self::$installedByVendor[$vendorDir] = $required;
|
||||
$installed[] = $required;
|
||||
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
|
||||
self::$installed = $required;
|
||||
self::$installedIsLocalDir = true;
|
||||
}
|
||||
}
|
||||
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
|
||||
$copiedLocalDir = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require __DIR__ . '/installed.php';
|
||||
self::$installed = $required;
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
if (self::$installed !== array() && !$copiedLocalDir) {
|
||||
$installed[] = self::$installed;
|
||||
}
|
||||
|
||||
return $installed;
|
||||
}
|
||||
}
|
||||
21
vendor/composer/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
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.
|
||||
|
||||
16
vendor/composer/autoload_classmap.php
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
'GPBMetadata\\GrpcGcp' => $vendorDir . '/google/grpc-gcp/src/generated/GPBMetadata/GrpcGcp.php',
|
||||
'Grpc\\Gcp\\AffinityConfig' => $vendorDir . '/google/grpc-gcp/src/generated/Grpc/Gcp/AffinityConfig.php',
|
||||
'Grpc\\Gcp\\AffinityConfig_Command' => $vendorDir . '/google/grpc-gcp/src/generated/Grpc/Gcp/AffinityConfig_Command.php',
|
||||
'Grpc\\Gcp\\ApiConfig' => $vendorDir . '/google/grpc-gcp/src/generated/Grpc/Gcp/ApiConfig.php',
|
||||
'Grpc\\Gcp\\ChannelPoolConfig' => $vendorDir . '/google/grpc-gcp/src/generated/Grpc/Gcp/ChannelPoolConfig.php',
|
||||
'Grpc\\Gcp\\MethodConfig' => $vendorDir . '/google/grpc-gcp/src/generated/Grpc/Gcp/MethodConfig.php',
|
||||
);
|
||||
13
vendor/composer/autoload_files.php
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
// autoload_files.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
|
||||
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
|
||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||
'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php',
|
||||
);
|
||||
9
vendor/composer/autoload_namespaces.php
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
43
vendor/composer/autoload_psr4.php
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'),
|
||||
'Ramsey\\Collection\\' => array($vendorDir . '/ramsey/collection/src'),
|
||||
'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
|
||||
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
|
||||
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
|
||||
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
|
||||
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
|
||||
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
|
||||
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
|
||||
'Grpc\\Gcp\\' => array($vendorDir . '/google/grpc-gcp/src'),
|
||||
'Grpc\\' => array($vendorDir . '/grpc/grpc/src/lib'),
|
||||
'Google\\Type\\' => array($vendorDir . '/google/common-protos/src/Type'),
|
||||
'Google\\Rpc\\' => array($vendorDir . '/google/common-protos/src/Rpc'),
|
||||
'Google\\Protobuf\\' => array($vendorDir . '/google/protobuf/src/Google/Protobuf'),
|
||||
'Google\\LongRunning\\' => array($vendorDir . '/google/longrunning/src/LongRunning'),
|
||||
'Google\\Iam\\' => array($vendorDir . '/google/common-protos/src/Iam'),
|
||||
'Google\\Cloud\\Dialogflow\\' => array($vendorDir . '/google/cloud-dialogflow/src'),
|
||||
'Google\\Cloud\\' => array($vendorDir . '/google/common-protos/src/Cloud'),
|
||||
'Google\\Auth\\' => array($vendorDir . '/google/auth/src'),
|
||||
'Google\\Api\\' => array($vendorDir . '/google/common-protos/src/Api'),
|
||||
'Google\\ApiCore\\LongRunning\\' => array($vendorDir . '/google/longrunning/src/ApiCore/LongRunning'),
|
||||
'Google\\ApiCore\\' => array($vendorDir . '/google/gax/src'),
|
||||
'GPBMetadata\\Google\\Type\\' => array($vendorDir . '/google/common-protos/metadata/Type'),
|
||||
'GPBMetadata\\Google\\Rpc\\' => array($vendorDir . '/google/common-protos/metadata/Rpc'),
|
||||
'GPBMetadata\\Google\\Protobuf\\' => array($vendorDir . '/google/protobuf/src/GPBMetadata/Google/Protobuf'),
|
||||
'GPBMetadata\\Google\\Longrunning\\' => array($vendorDir . '/google/longrunning/metadata/Longrunning'),
|
||||
'GPBMetadata\\Google\\Logging\\' => array($vendorDir . '/google/common-protos/metadata/Logging'),
|
||||
'GPBMetadata\\Google\\Iam\\' => array($vendorDir . '/google/common-protos/metadata/Iam'),
|
||||
'GPBMetadata\\Google\\Cloud\\Dialogflow\\' => array($vendorDir . '/google/cloud-dialogflow/metadata'),
|
||||
'GPBMetadata\\Google\\Cloud\\' => array($vendorDir . '/google/common-protos/metadata/Cloud'),
|
||||
'GPBMetadata\\Google\\Api\\' => array($vendorDir . '/google/common-protos/metadata/Api'),
|
||||
'GPBMetadata\\ApiCore\\' => array($vendorDir . '/google/gax/metadata/ApiCore'),
|
||||
'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'),
|
||||
'Brick\\Math\\' => array($vendorDir . '/brick/math/src'),
|
||||
);
|
||||
50
vendor/composer/autoload_real.php
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit2be695aecbd4dcc8ecce8c4845e9f2d7
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit2be695aecbd4dcc8ecce8c4845e9f2d7', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit2be695aecbd4dcc8ecce8c4845e9f2d7', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit2be695aecbd4dcc8ecce8c4845e9f2d7::getInitializer($loader));
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
$filesToLoad = \Composer\Autoload\ComposerStaticInit2be695aecbd4dcc8ecce8c4845e9f2d7::$files;
|
||||
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
|
||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
||||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
|
||||
|
||||
require $file;
|
||||
}
|
||||
}, null, null);
|
||||
foreach ($filesToLoad as $fileIdentifier => $file) {
|
||||
$requireFile($fileIdentifier, $file);
|
||||
}
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
227
vendor/composer/autoload_static.php
vendored
Normal file
@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit2be695aecbd4dcc8ecce8c4845e9f2d7
|
||||
{
|
||||
public static $files = array (
|
||||
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
|
||||
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
|
||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||
'e39a8b23c42d4e1452234d762b03835a' => __DIR__ . '/..' . '/ramsey/uuid/src/functions.php',
|
||||
);
|
||||
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'R' =>
|
||||
array (
|
||||
'Ramsey\\Uuid\\' => 12,
|
||||
'Ramsey\\Collection\\' => 18,
|
||||
),
|
||||
'P' =>
|
||||
array (
|
||||
'Psr\\Log\\' => 8,
|
||||
'Psr\\Http\\Message\\' => 17,
|
||||
'Psr\\Http\\Client\\' => 16,
|
||||
'Psr\\Cache\\' => 10,
|
||||
),
|
||||
'G' =>
|
||||
array (
|
||||
'GuzzleHttp\\Psr7\\' => 16,
|
||||
'GuzzleHttp\\Promise\\' => 19,
|
||||
'GuzzleHttp\\' => 11,
|
||||
'Grpc\\Gcp\\' => 9,
|
||||
'Grpc\\' => 5,
|
||||
'Google\\Type\\' => 12,
|
||||
'Google\\Rpc\\' => 11,
|
||||
'Google\\Protobuf\\' => 16,
|
||||
'Google\\LongRunning\\' => 19,
|
||||
'Google\\Iam\\' => 11,
|
||||
'Google\\Cloud\\Dialogflow\\' => 24,
|
||||
'Google\\Cloud\\' => 13,
|
||||
'Google\\Auth\\' => 12,
|
||||
'Google\\Api\\' => 11,
|
||||
'Google\\ApiCore\\LongRunning\\' => 27,
|
||||
'Google\\ApiCore\\' => 15,
|
||||
'GPBMetadata\\Google\\Type\\' => 24,
|
||||
'GPBMetadata\\Google\\Rpc\\' => 23,
|
||||
'GPBMetadata\\Google\\Protobuf\\' => 28,
|
||||
'GPBMetadata\\Google\\Longrunning\\' => 31,
|
||||
'GPBMetadata\\Google\\Logging\\' => 27,
|
||||
'GPBMetadata\\Google\\Iam\\' => 23,
|
||||
'GPBMetadata\\Google\\Cloud\\Dialogflow\\' => 36,
|
||||
'GPBMetadata\\Google\\Cloud\\' => 25,
|
||||
'GPBMetadata\\Google\\Api\\' => 23,
|
||||
'GPBMetadata\\ApiCore\\' => 20,
|
||||
),
|
||||
'F' =>
|
||||
array (
|
||||
'Firebase\\JWT\\' => 13,
|
||||
),
|
||||
'B' =>
|
||||
array (
|
||||
'Brick\\Math\\' => 11,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'Ramsey\\Uuid\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/ramsey/uuid/src',
|
||||
),
|
||||
'Ramsey\\Collection\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/ramsey/collection/src',
|
||||
),
|
||||
'Psr\\Log\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/log/src',
|
||||
),
|
||||
'Psr\\Http\\Message\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/http-factory/src',
|
||||
1 => __DIR__ . '/..' . '/psr/http-message/src',
|
||||
),
|
||||
'Psr\\Http\\Client\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/http-client/src',
|
||||
),
|
||||
'Psr\\Cache\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/cache/src',
|
||||
),
|
||||
'GuzzleHttp\\Psr7\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
|
||||
),
|
||||
'GuzzleHttp\\Promise\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
|
||||
),
|
||||
'GuzzleHttp\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
|
||||
),
|
||||
'Grpc\\Gcp\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/grpc-gcp/src',
|
||||
),
|
||||
'Grpc\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/grpc/grpc/src/lib',
|
||||
),
|
||||
'Google\\Type\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/common-protos/src/Type',
|
||||
),
|
||||
'Google\\Rpc\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/common-protos/src/Rpc',
|
||||
),
|
||||
'Google\\Protobuf\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/protobuf/src/Google/Protobuf',
|
||||
),
|
||||
'Google\\LongRunning\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/longrunning/src/LongRunning',
|
||||
),
|
||||
'Google\\Iam\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/common-protos/src/Iam',
|
||||
),
|
||||
'Google\\Cloud\\Dialogflow\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/cloud-dialogflow/src',
|
||||
),
|
||||
'Google\\Cloud\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/common-protos/src/Cloud',
|
||||
),
|
||||
'Google\\Auth\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/auth/src',
|
||||
),
|
||||
'Google\\Api\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/common-protos/src/Api',
|
||||
),
|
||||
'Google\\ApiCore\\LongRunning\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/longrunning/src/ApiCore/LongRunning',
|
||||
),
|
||||
'Google\\ApiCore\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/gax/src',
|
||||
),
|
||||
'GPBMetadata\\Google\\Type\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/common-protos/metadata/Type',
|
||||
),
|
||||
'GPBMetadata\\Google\\Rpc\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/common-protos/metadata/Rpc',
|
||||
),
|
||||
'GPBMetadata\\Google\\Protobuf\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/protobuf/src/GPBMetadata/Google/Protobuf',
|
||||
),
|
||||
'GPBMetadata\\Google\\Longrunning\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/longrunning/metadata/Longrunning',
|
||||
),
|
||||
'GPBMetadata\\Google\\Logging\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/common-protos/metadata/Logging',
|
||||
),
|
||||
'GPBMetadata\\Google\\Iam\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/common-protos/metadata/Iam',
|
||||
),
|
||||
'GPBMetadata\\Google\\Cloud\\Dialogflow\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/cloud-dialogflow/metadata',
|
||||
),
|
||||
'GPBMetadata\\Google\\Cloud\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/common-protos/metadata/Cloud',
|
||||
),
|
||||
'GPBMetadata\\Google\\Api\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/common-protos/metadata/Api',
|
||||
),
|
||||
'GPBMetadata\\ApiCore\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/google/gax/metadata/ApiCore',
|
||||
),
|
||||
'Firebase\\JWT\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/firebase/php-jwt/src',
|
||||
),
|
||||
'Brick\\Math\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/brick/math/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
'GPBMetadata\\GrpcGcp' => __DIR__ . '/..' . '/google/grpc-gcp/src/generated/GPBMetadata/GrpcGcp.php',
|
||||
'Grpc\\Gcp\\AffinityConfig' => __DIR__ . '/..' . '/google/grpc-gcp/src/generated/Grpc/Gcp/AffinityConfig.php',
|
||||
'Grpc\\Gcp\\AffinityConfig_Command' => __DIR__ . '/..' . '/google/grpc-gcp/src/generated/Grpc/Gcp/AffinityConfig_Command.php',
|
||||
'Grpc\\Gcp\\ApiConfig' => __DIR__ . '/..' . '/google/grpc-gcp/src/generated/Grpc/Gcp/ApiConfig.php',
|
||||
'Grpc\\Gcp\\ChannelPoolConfig' => __DIR__ . '/..' . '/google/grpc-gcp/src/generated/Grpc/Gcp/ChannelPoolConfig.php',
|
||||
'Grpc\\Gcp\\MethodConfig' => __DIR__ . '/..' . '/google/grpc-gcp/src/generated/Grpc/Gcp/MethodConfig.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit2be695aecbd4dcc8ecce8c4845e9f2d7::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit2be695aecbd4dcc8ecce8c4845e9f2d7::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInit2be695aecbd4dcc8ecce8c4845e9f2d7::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||
1448
vendor/composer/installed.json
vendored
Normal file
245
vendor/composer/installed.php
vendored
Normal file
@ -0,0 +1,245 @@
|
||||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => '__root__',
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => '2e0e56a9271c9b4eb7c6014320398d8ef346bd90',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev' => true,
|
||||
),
|
||||
'versions' => array(
|
||||
'__root__' => array(
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => '2e0e56a9271c9b4eb7c6014320398d8ef346bd90',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'brick/math' => array(
|
||||
'pretty_version' => '0.14.0',
|
||||
'version' => '0.14.0.0',
|
||||
'reference' => '113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../brick/math',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'firebase/php-jwt' => array(
|
||||
'pretty_version' => 'v6.11.1',
|
||||
'version' => '6.11.1.0',
|
||||
'reference' => 'd1e91ecf8c598d073d0995afa8cd5c75c6e19e66',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../firebase/php-jwt',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'google/auth' => array(
|
||||
'pretty_version' => 'v1.48.1',
|
||||
'version' => '1.48.1.0',
|
||||
'reference' => '023f41a2c80fb98a493dfb9dffcab643481a7ab0',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../google/auth',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'google/cloud-dialogflow' => array(
|
||||
'pretty_version' => 'v2.2.0',
|
||||
'version' => '2.2.0.0',
|
||||
'reference' => 'b536e04b66e518505dbc0266c7f288f4d692cf7a',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../google/cloud-dialogflow',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'google/common-protos' => array(
|
||||
'pretty_version' => '4.12.4',
|
||||
'version' => '4.12.4.0',
|
||||
'reference' => '0127156899af0df2681bd42024c60bd5360d64e3',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../google/common-protos',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'google/gax' => array(
|
||||
'pretty_version' => 'v1.38.0',
|
||||
'version' => '1.38.0.0',
|
||||
'reference' => '0e1bce4a30722e85485bbb132b2fa811d66b397b',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../google/gax',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'google/grpc-gcp' => array(
|
||||
'pretty_version' => 'v0.4.1',
|
||||
'version' => '0.4.1.0',
|
||||
'reference' => 'e585b7721bbe806ef45b5c52ae43dfc2bff89968',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../google/grpc-gcp',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'google/longrunning' => array(
|
||||
'pretty_version' => '0.5.0',
|
||||
'version' => '0.5.0.0',
|
||||
'reference' => '715519ab4aaf3c4268adb2b551ee0f34135c8c5f',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../google/longrunning',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'google/protobuf' => array(
|
||||
'pretty_version' => 'v4.32.1',
|
||||
'version' => '4.32.1.0',
|
||||
'reference' => 'c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../google/protobuf',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'grpc/grpc' => array(
|
||||
'pretty_version' => '1.74.0',
|
||||
'version' => '1.74.0.0',
|
||||
'reference' => '32bf4dba256d60d395582fb6e4e8d3936bcdb713',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../grpc/grpc',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'guzzlehttp/guzzle' => array(
|
||||
'pretty_version' => '7.10.0',
|
||||
'version' => '7.10.0.0',
|
||||
'reference' => 'b51ac707cfa420b7bfd4e4d5e510ba8008e822b4',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'guzzlehttp/promises' => array(
|
||||
'pretty_version' => '2.3.0',
|
||||
'version' => '2.3.0.0',
|
||||
'reference' => '481557b130ef3790cf82b713667b43030dc9c957',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../guzzlehttp/promises',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'guzzlehttp/psr7' => array(
|
||||
'pretty_version' => '2.8.0',
|
||||
'version' => '2.8.0.0',
|
||||
'reference' => '21dc724a0583619cd1652f673303492272778051',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../guzzlehttp/psr7',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/cache' => array(
|
||||
'pretty_version' => '3.0.0',
|
||||
'version' => '3.0.0.0',
|
||||
'reference' => 'aa5030cfa5405eccfdcb1083ce040c2cb8d253bf',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/cache',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/http-client' => array(
|
||||
'pretty_version' => '1.0.3',
|
||||
'version' => '1.0.3.0',
|
||||
'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/http-client',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/http-client-implementation' => array(
|
||||
'dev_requirement' => false,
|
||||
'provided' => array(
|
||||
0 => '1.0',
|
||||
),
|
||||
),
|
||||
'psr/http-factory' => array(
|
||||
'pretty_version' => '1.1.0',
|
||||
'version' => '1.1.0.0',
|
||||
'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/http-factory',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/http-factory-implementation' => array(
|
||||
'dev_requirement' => false,
|
||||
'provided' => array(
|
||||
0 => '1.0',
|
||||
),
|
||||
),
|
||||
'psr/http-message' => array(
|
||||
'pretty_version' => '2.0',
|
||||
'version' => '2.0.0.0',
|
||||
'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/http-message',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/http-message-implementation' => array(
|
||||
'dev_requirement' => false,
|
||||
'provided' => array(
|
||||
0 => '1.0',
|
||||
),
|
||||
),
|
||||
'psr/log' => array(
|
||||
'pretty_version' => '3.0.2',
|
||||
'version' => '3.0.2.0',
|
||||
'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/log',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'ralouphie/getallheaders' => array(
|
||||
'pretty_version' => '3.0.3',
|
||||
'version' => '3.0.3.0',
|
||||
'reference' => '120b605dfeb996808c31b6477290a714d356e822',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../ralouphie/getallheaders',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'ramsey/collection' => array(
|
||||
'pretty_version' => '2.1.1',
|
||||
'version' => '2.1.1.0',
|
||||
'reference' => '344572933ad0181accbf4ba763e85a0306a8c5e2',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../ramsey/collection',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'ramsey/uuid' => array(
|
||||
'pretty_version' => '4.9.1',
|
||||
'version' => '4.9.1.0',
|
||||
'reference' => '81f941f6f729b1e3ceea61d9d014f8b6c6800440',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../ramsey/uuid',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'rhumsaa/uuid' => array(
|
||||
'dev_requirement' => false,
|
||||
'replaced' => array(
|
||||
0 => '4.9.1',
|
||||
),
|
||||
),
|
||||
'symfony/deprecation-contracts' => array(
|
||||
'pretty_version' => 'v3.6.0',
|
||||
'version' => '3.6.0.0',
|
||||
'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
25
vendor/composer/platform_check.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
// platform_check.php @generated by Composer
|
||||
|
||||
$issues = array();
|
||||
|
||||
if (!(PHP_VERSION_ID >= 80200)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.2.0". You are running ' . PHP_VERSION . '.';
|
||||
}
|
||||
|
||||
if ($issues) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
|
||||
} elseif (!headers_sent()) {
|
||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
throw new \RuntimeException(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues)
|
||||
);
|
||||
}
|
||||
205
vendor/firebase/php-jwt/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,205 @@
|
||||
# Changelog
|
||||
|
||||
## [6.11.1](https://github.com/firebase/php-jwt/compare/v6.11.0...v6.11.1) (2025-04-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update error text for consistency ([#528](https://github.com/firebase/php-jwt/issues/528)) ([c11113a](https://github.com/firebase/php-jwt/commit/c11113afa13265e016a669e75494b9203b8a7775))
|
||||
|
||||
## [6.11.0](https://github.com/firebase/php-jwt/compare/v6.10.2...v6.11.0) (2025-01-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support octet typed JWK ([#587](https://github.com/firebase/php-jwt/issues/587)) ([7cb8a26](https://github.com/firebase/php-jwt/commit/7cb8a265fa81edf2fa6ef8098f5bc5ae573c33ad))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* refactor constructor Key to use PHP 8.0 syntax ([#577](https://github.com/firebase/php-jwt/issues/577)) ([29fa2ce](https://github.com/firebase/php-jwt/commit/29fa2ce9e0582cd397711eec1e80c05ce20fabca))
|
||||
|
||||
## [6.10.2](https://github.com/firebase/php-jwt/compare/v6.10.1...v6.10.2) (2024-11-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Mitigate PHP8.4 deprecation warnings ([#570](https://github.com/firebase/php-jwt/issues/570)) ([76808fa](https://github.com/firebase/php-jwt/commit/76808fa227f3811aa5cdb3bf81233714b799a5b5))
|
||||
* support php 8.4 ([#583](https://github.com/firebase/php-jwt/issues/583)) ([e3d68b0](https://github.com/firebase/php-jwt/commit/e3d68b044421339443c74199edd020e03fb1887e))
|
||||
|
||||
## [6.10.1](https://github.com/firebase/php-jwt/compare/v6.10.0...v6.10.1) (2024-05-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ensure ratelimit expiry is set every time ([#556](https://github.com/firebase/php-jwt/issues/556)) ([09cb208](https://github.com/firebase/php-jwt/commit/09cb2081c2c3bc0f61e2f2a5fbea5741f7498648))
|
||||
* ratelimit cache expiration ([#550](https://github.com/firebase/php-jwt/issues/550)) ([dda7250](https://github.com/firebase/php-jwt/commit/dda725033585ece30ff8cae8937320d7e9f18bae))
|
||||
|
||||
## [6.10.0](https://github.com/firebase/php-jwt/compare/v6.9.0...v6.10.0) (2023-11-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allow typ header override ([#546](https://github.com/firebase/php-jwt/issues/546)) ([79cb30b](https://github.com/firebase/php-jwt/commit/79cb30b729a22931b2fbd6b53f20629a83031ba9))
|
||||
|
||||
## [6.9.0](https://github.com/firebase/php-jwt/compare/v6.8.1...v6.9.0) (2023-10-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add payload to jwt exception ([#521](https://github.com/firebase/php-jwt/issues/521)) ([175edf9](https://github.com/firebase/php-jwt/commit/175edf958bb61922ec135b2333acf5622f2238a2))
|
||||
|
||||
## [6.8.1](https://github.com/firebase/php-jwt/compare/v6.8.0...v6.8.1) (2023-07-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* accept float claims but round down to ignore them ([#492](https://github.com/firebase/php-jwt/issues/492)) ([3936842](https://github.com/firebase/php-jwt/commit/39368423beeaacb3002afa7dcb75baebf204fe7e))
|
||||
* different BeforeValidException messages for nbf and iat ([#526](https://github.com/firebase/php-jwt/issues/526)) ([0a53cf2](https://github.com/firebase/php-jwt/commit/0a53cf2986e45c2bcbf1a269f313ebf56a154ee4))
|
||||
|
||||
## [6.8.0](https://github.com/firebase/php-jwt/compare/v6.7.0...v6.8.0) (2023-06-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for P-384 curve ([#515](https://github.com/firebase/php-jwt/issues/515)) ([5de4323](https://github.com/firebase/php-jwt/commit/5de4323f4baf4d70bca8663bd87682a69c656c3d))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* handle invalid http responses ([#508](https://github.com/firebase/php-jwt/issues/508)) ([91c39c7](https://github.com/firebase/php-jwt/commit/91c39c72b22fc3e1191e574089552c1f2041c718))
|
||||
|
||||
## [6.7.0](https://github.com/firebase/php-jwt/compare/v6.6.0...v6.7.0) (2023-06-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ed25519 support to JWK (public keys) ([#452](https://github.com/firebase/php-jwt/issues/452)) ([e53979a](https://github.com/firebase/php-jwt/commit/e53979abae927de916a75b9d239cfda8ce32be2a))
|
||||
|
||||
## [6.6.0](https://github.com/firebase/php-jwt/compare/v6.5.0...v6.6.0) (2023-06-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allow get headers when decoding token ([#442](https://github.com/firebase/php-jwt/issues/442)) ([fb85f47](https://github.com/firebase/php-jwt/commit/fb85f47cfaeffdd94faf8defdf07164abcdad6c3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* only check iat if nbf is not used ([#493](https://github.com/firebase/php-jwt/issues/493)) ([398ccd2](https://github.com/firebase/php-jwt/commit/398ccd25ea12fa84b9e4f1085d5ff448c21ec797))
|
||||
|
||||
## [6.5.0](https://github.com/firebase/php-jwt/compare/v6.4.0...v6.5.0) (2023-05-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow KID of '0' ([#505](https://github.com/firebase/php-jwt/issues/505)) ([9dc46a9](https://github.com/firebase/php-jwt/commit/9dc46a9c3e5801294249cfd2554c5363c9f9326a))
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* drop support for PHP 7.3 ([#495](https://github.com/firebase/php-jwt/issues/495))
|
||||
|
||||
## [6.4.0](https://github.com/firebase/php-jwt/compare/v6.3.2...v6.4.0) (2023-02-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for W3C ES256K ([#462](https://github.com/firebase/php-jwt/issues/462)) ([213924f](https://github.com/firebase/php-jwt/commit/213924f51936291fbbca99158b11bd4ae56c2c95))
|
||||
* improve caching by only decoding jwks when necessary ([#486](https://github.com/firebase/php-jwt/issues/486)) ([78d3ed1](https://github.com/firebase/php-jwt/commit/78d3ed1073553f7d0bbffa6c2010009a0d483d5c))
|
||||
|
||||
## [6.3.2](https://github.com/firebase/php-jwt/compare/v6.3.1...v6.3.2) (2022-11-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check kid before using as array index ([bad1b04](https://github.com/firebase/php-jwt/commit/bad1b040d0c736bbf86814c6b5ae614f517cf7bd))
|
||||
|
||||
## [6.3.1](https://github.com/firebase/php-jwt/compare/v6.3.0...v6.3.1) (2022-11-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* casing of GET for PSR compat ([#451](https://github.com/firebase/php-jwt/issues/451)) ([60b52b7](https://github.com/firebase/php-jwt/commit/60b52b71978790eafcf3b95cfbd83db0439e8d22))
|
||||
* string interpolation format for php 8.2 ([#446](https://github.com/firebase/php-jwt/issues/446)) ([2e07d8a](https://github.com/firebase/php-jwt/commit/2e07d8a1524d12b69b110ad649f17461d068b8f2))
|
||||
|
||||
## 6.3.0 / 2022-07-15
|
||||
|
||||
- Added ES256 support to JWK parsing ([#399](https://github.com/firebase/php-jwt/pull/399))
|
||||
- Fixed potential caching error in `CachedKeySet` by caching jwks as strings ([#435](https://github.com/firebase/php-jwt/pull/435))
|
||||
|
||||
## 6.2.0 / 2022-05-14
|
||||
|
||||
- Added `CachedKeySet` ([#397](https://github.com/firebase/php-jwt/pull/397))
|
||||
- Added `$defaultAlg` parameter to `JWT::parseKey` and `JWT::parseKeySet` ([#426](https://github.com/firebase/php-jwt/pull/426)).
|
||||
|
||||
## 6.1.0 / 2022-03-23
|
||||
|
||||
- Drop support for PHP 5.3, 5.4, 5.5, 5.6, and 7.0
|
||||
- Add parameter typing and return types where possible
|
||||
|
||||
## 6.0.0 / 2022-01-24
|
||||
|
||||
- **Backwards-Compatibility Breaking Changes**: See the [Release Notes](https://github.com/firebase/php-jwt/releases/tag/v6.0.0) for more information.
|
||||
- New Key object to prevent key/algorithm type confusion (#365)
|
||||
- Add JWK support (#273)
|
||||
- Add ES256 support (#256)
|
||||
- Add ES384 support (#324)
|
||||
- Add Ed25519 support (#343)
|
||||
|
||||
## 5.0.0 / 2017-06-26
|
||||
- Support RS384 and RS512.
|
||||
See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)!
|
||||
- Add an example for RS256 openssl.
|
||||
See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)!
|
||||
- Detect invalid Base64 encoding in signature.
|
||||
See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)!
|
||||
- Update `JWT::verify` to handle OpenSSL errors.
|
||||
See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)!
|
||||
- Add `array` type hinting to `decode` method
|
||||
See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)!
|
||||
- Add all JSON error types.
|
||||
See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)!
|
||||
- Bugfix 'kid' not in given key list.
|
||||
See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)!
|
||||
- Miscellaneous cleanup, documentation and test fixes.
|
||||
See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115),
|
||||
[#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and
|
||||
[#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman),
|
||||
[@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)!
|
||||
|
||||
## 4.0.0 / 2016-07-17
|
||||
- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)!
|
||||
- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)!
|
||||
- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)!
|
||||
- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)!
|
||||
|
||||
## 3.0.0 / 2015-07-22
|
||||
- Minimum PHP version updated from `5.2.0` to `5.3.0`.
|
||||
- Add `\Firebase\JWT` namespace. See
|
||||
[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to
|
||||
[@Dashron](https://github.com/Dashron)!
|
||||
- Require a non-empty key to decode and verify a JWT. See
|
||||
[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to
|
||||
[@sjones608](https://github.com/sjones608)!
|
||||
- Cleaner documentation blocks in the code. See
|
||||
[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to
|
||||
[@johanderuijter](https://github.com/johanderuijter)!
|
||||
|
||||
## 2.2.0 / 2015-06-22
|
||||
- Add support for adding custom, optional JWT headers to `JWT::encode()`. See
|
||||
[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to
|
||||
[@mcocaro](https://github.com/mcocaro)!
|
||||
|
||||
## 2.1.0 / 2015-05-20
|
||||
- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew
|
||||
between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)!
|
||||
- Add support for passing an object implementing the `ArrayAccess` interface for
|
||||
`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)!
|
||||
|
||||
## 2.0.0 / 2015-04-01
|
||||
- **Note**: It is strongly recommended that you update to > v2.0.0 to address
|
||||
known security vulnerabilities in prior versions when both symmetric and
|
||||
asymmetric keys are used together.
|
||||
- Update signature for `JWT::decode(...)` to require an array of supported
|
||||
algorithms to use when verifying token signatures.
|
||||
30
vendor/firebase/php-jwt/LICENSE
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
Copyright (c) 2011, Neuman Vong
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of other
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
425
vendor/firebase/php-jwt/README.md
vendored
Normal file
@ -0,0 +1,425 @@
|
||||

|
||||
[](https://packagist.org/packages/firebase/php-jwt)
|
||||
[](https://packagist.org/packages/firebase/php-jwt)
|
||||
[](https://packagist.org/packages/firebase/php-jwt)
|
||||
|
||||
PHP-JWT
|
||||
=======
|
||||
A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to [RFC 7519](https://tools.ietf.org/html/rfc7519).
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Use composer to manage your dependencies and download PHP-JWT:
|
||||
|
||||
```bash
|
||||
composer require firebase/php-jwt
|
||||
```
|
||||
|
||||
Optionally, install the `paragonie/sodium_compat` package from composer if your
|
||||
php env does not have libsodium installed:
|
||||
|
||||
```bash
|
||||
composer require paragonie/sodium_compat
|
||||
```
|
||||
|
||||
Example
|
||||
-------
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
|
||||
$key = 'example_key';
|
||||
$payload = [
|
||||
'iss' => 'http://example.org',
|
||||
'aud' => 'http://example.com',
|
||||
'iat' => 1356999524,
|
||||
'nbf' => 1357000000
|
||||
];
|
||||
|
||||
/**
|
||||
* IMPORTANT:
|
||||
* You must specify supported algorithms for your application. See
|
||||
* https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40
|
||||
* for a list of spec-compliant algorithms.
|
||||
*/
|
||||
$jwt = JWT::encode($payload, $key, 'HS256');
|
||||
$decoded = JWT::decode($jwt, new Key($key, 'HS256'));
|
||||
print_r($decoded);
|
||||
|
||||
// Pass a stdClass in as the third parameter to get the decoded header values
|
||||
$headers = new stdClass();
|
||||
$decoded = JWT::decode($jwt, new Key($key, 'HS256'), $headers);
|
||||
print_r($headers);
|
||||
|
||||
/*
|
||||
NOTE: This will now be an object instead of an associative array. To get
|
||||
an associative array, you will need to cast it as such:
|
||||
*/
|
||||
|
||||
$decoded_array = (array) $decoded;
|
||||
|
||||
/**
|
||||
* You can add a leeway to account for when there is a clock skew times between
|
||||
* the signing and verifying servers. It is recommended that this leeway should
|
||||
* not be bigger than a few minutes.
|
||||
*
|
||||
* Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef
|
||||
*/
|
||||
JWT::$leeway = 60; // $leeway in seconds
|
||||
$decoded = JWT::decode($jwt, new Key($key, 'HS256'));
|
||||
```
|
||||
Example encode/decode headers
|
||||
-------
|
||||
Decoding the JWT headers without verifying the JWT first is NOT recommended, and is not supported by
|
||||
this library. This is because without verifying the JWT, the header values could have been tampered with.
|
||||
Any value pulled from an unverified header should be treated as if it could be any string sent in from an
|
||||
attacker. If this is something you still want to do in your application for whatever reason, it's possible to
|
||||
decode the header values manually simply by calling `json_decode` and `base64_decode` on the JWT
|
||||
header part:
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
|
||||
$key = 'example_key';
|
||||
$payload = [
|
||||
'iss' => 'http://example.org',
|
||||
'aud' => 'http://example.com',
|
||||
'iat' => 1356999524,
|
||||
'nbf' => 1357000000
|
||||
];
|
||||
|
||||
$headers = [
|
||||
'x-forwarded-for' => 'www.google.com'
|
||||
];
|
||||
|
||||
// Encode headers in the JWT string
|
||||
$jwt = JWT::encode($payload, $key, 'HS256', null, $headers);
|
||||
|
||||
// Decode headers from the JWT string WITHOUT validation
|
||||
// **IMPORTANT**: This operation is vulnerable to attacks, as the JWT has not yet been verified.
|
||||
// These headers could be any value sent by an attacker.
|
||||
list($headersB64, $payloadB64, $sig) = explode('.', $jwt);
|
||||
$decoded = json_decode(base64_decode($headersB64), true);
|
||||
|
||||
print_r($decoded);
|
||||
```
|
||||
Example with RS256 (openssl)
|
||||
----------------------------
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
|
||||
$privateKey = <<<EOD
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAuzWHNM5f+amCjQztc5QTfJfzCC5J4nuW+L/aOxZ4f8J3Frew
|
||||
M2c/dufrnmedsApb0By7WhaHlcqCh/ScAPyJhzkPYLae7bTVro3hok0zDITR8F6S
|
||||
JGL42JAEUk+ILkPI+DONM0+3vzk6Kvfe548tu4czCuqU8BGVOlnp6IqBHhAswNMM
|
||||
78pos/2z0CjPM4tbeXqSTTbNkXRboxjU29vSopcT51koWOgiTf3C7nJUoMWZHZI5
|
||||
HqnIhPAG9yv8HAgNk6CMk2CadVHDo4IxjxTzTTqo1SCSH2pooJl9O8at6kkRYsrZ
|
||||
WwsKlOFE2LUce7ObnXsYihStBUDoeBQlGG/BwQIDAQABAoIBAFtGaOqNKGwggn9k
|
||||
6yzr6GhZ6Wt2rh1Xpq8XUz514UBhPxD7dFRLpbzCrLVpzY80LbmVGJ9+1pJozyWc
|
||||
VKeCeUdNwbqkr240Oe7GTFmGjDoxU+5/HX/SJYPpC8JZ9oqgEA87iz+WQX9hVoP2
|
||||
oF6EB4ckDvXmk8FMwVZW2l2/kd5mrEVbDaXKxhvUDf52iVD+sGIlTif7mBgR99/b
|
||||
c3qiCnxCMmfYUnT2eh7Vv2LhCR/G9S6C3R4lA71rEyiU3KgsGfg0d82/XWXbegJW
|
||||
h3QbWNtQLxTuIvLq5aAryV3PfaHlPgdgK0ft6ocU2de2FagFka3nfVEyC7IUsNTK
|
||||
bq6nhAECgYEA7d/0DPOIaItl/8BWKyCuAHMss47j0wlGbBSHdJIiS55akMvnAG0M
|
||||
39y22Qqfzh1at9kBFeYeFIIU82ZLF3xOcE3z6pJZ4Dyvx4BYdXH77odo9uVK9s1l
|
||||
3T3BlMcqd1hvZLMS7dviyH79jZo4CXSHiKzc7pQ2YfK5eKxKqONeXuECgYEAyXlG
|
||||
vonaus/YTb1IBei9HwaccnQ/1HRn6MvfDjb7JJDIBhNClGPt6xRlzBbSZ73c2QEC
|
||||
6Fu9h36K/HZ2qcLd2bXiNyhIV7b6tVKk+0Psoj0dL9EbhsD1OsmE1nTPyAc9XZbb
|
||||
OPYxy+dpBCUA8/1U9+uiFoCa7mIbWcSQ+39gHuECgYAz82pQfct30aH4JiBrkNqP
|
||||
nJfRq05UY70uk5k1u0ikLTRoVS/hJu/d4E1Kv4hBMqYCavFSwAwnvHUo51lVCr/y
|
||||
xQOVYlsgnwBg2MX4+GjmIkqpSVCC8D7j/73MaWb746OIYZervQ8dbKahi2HbpsiG
|
||||
8AHcVSA/agxZr38qvWV54QKBgCD5TlDE8x18AuTGQ9FjxAAd7uD0kbXNz2vUYg9L
|
||||
hFL5tyL3aAAtUrUUw4xhd9IuysRhW/53dU+FsG2dXdJu6CxHjlyEpUJl2iZu/j15
|
||||
YnMzGWHIEX8+eWRDsw/+Ujtko/B7TinGcWPz3cYl4EAOiCeDUyXnqnO1btCEUU44
|
||||
DJ1BAoGBAJuPD27ErTSVtId90+M4zFPNibFP50KprVdc8CR37BE7r8vuGgNYXmnI
|
||||
RLnGP9p3pVgFCktORuYS2J/6t84I3+A17nEoB4xvhTLeAinAW/uTQOUmNicOP4Ek
|
||||
2MsLL2kHgL8bLTmvXV4FX+PXphrDKg1XxzOYn0otuoqdAQrkK4og
|
||||
-----END RSA PRIVATE KEY-----
|
||||
EOD;
|
||||
|
||||
$publicKey = <<<EOD
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzWHNM5f+amCjQztc5QT
|
||||
fJfzCC5J4nuW+L/aOxZ4f8J3FrewM2c/dufrnmedsApb0By7WhaHlcqCh/ScAPyJ
|
||||
hzkPYLae7bTVro3hok0zDITR8F6SJGL42JAEUk+ILkPI+DONM0+3vzk6Kvfe548t
|
||||
u4czCuqU8BGVOlnp6IqBHhAswNMM78pos/2z0CjPM4tbeXqSTTbNkXRboxjU29vS
|
||||
opcT51koWOgiTf3C7nJUoMWZHZI5HqnIhPAG9yv8HAgNk6CMk2CadVHDo4IxjxTz
|
||||
TTqo1SCSH2pooJl9O8at6kkRYsrZWwsKlOFE2LUce7ObnXsYihStBUDoeBQlGG/B
|
||||
wQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
EOD;
|
||||
|
||||
$payload = [
|
||||
'iss' => 'example.org',
|
||||
'aud' => 'example.com',
|
||||
'iat' => 1356999524,
|
||||
'nbf' => 1357000000
|
||||
];
|
||||
|
||||
$jwt = JWT::encode($payload, $privateKey, 'RS256');
|
||||
echo "Encode:\n" . print_r($jwt, true) . "\n";
|
||||
|
||||
$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256'));
|
||||
|
||||
/*
|
||||
NOTE: This will now be an object instead of an associative array. To get
|
||||
an associative array, you will need to cast it as such:
|
||||
*/
|
||||
|
||||
$decoded_array = (array) $decoded;
|
||||
echo "Decode:\n" . print_r($decoded_array, true) . "\n";
|
||||
```
|
||||
|
||||
Example with a passphrase
|
||||
-------------------------
|
||||
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
|
||||
// Your passphrase
|
||||
$passphrase = '[YOUR_PASSPHRASE]';
|
||||
|
||||
// Your private key file with passphrase
|
||||
// Can be generated with "ssh-keygen -t rsa -m pem"
|
||||
$privateKeyFile = '/path/to/key-with-passphrase.pem';
|
||||
|
||||
// Create a private key of type "resource"
|
||||
$privateKey = openssl_pkey_get_private(
|
||||
file_get_contents($privateKeyFile),
|
||||
$passphrase
|
||||
);
|
||||
|
||||
$payload = [
|
||||
'iss' => 'example.org',
|
||||
'aud' => 'example.com',
|
||||
'iat' => 1356999524,
|
||||
'nbf' => 1357000000
|
||||
];
|
||||
|
||||
$jwt = JWT::encode($payload, $privateKey, 'RS256');
|
||||
echo "Encode:\n" . print_r($jwt, true) . "\n";
|
||||
|
||||
// Get public key from the private key, or pull from from a file.
|
||||
$publicKey = openssl_pkey_get_details($privateKey)['key'];
|
||||
|
||||
$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256'));
|
||||
echo "Decode:\n" . print_r((array) $decoded, true) . "\n";
|
||||
```
|
||||
|
||||
Example with EdDSA (libsodium and Ed25519 signature)
|
||||
----------------------------
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
|
||||
// Public and private keys are expected to be Base64 encoded. The last
|
||||
// non-empty line is used so that keys can be generated with
|
||||
// sodium_crypto_sign_keypair(). The secret keys generated by other tools may
|
||||
// need to be adjusted to match the input expected by libsodium.
|
||||
|
||||
$keyPair = sodium_crypto_sign_keypair();
|
||||
|
||||
$privateKey = base64_encode(sodium_crypto_sign_secretkey($keyPair));
|
||||
|
||||
$publicKey = base64_encode(sodium_crypto_sign_publickey($keyPair));
|
||||
|
||||
$payload = [
|
||||
'iss' => 'example.org',
|
||||
'aud' => 'example.com',
|
||||
'iat' => 1356999524,
|
||||
'nbf' => 1357000000
|
||||
];
|
||||
|
||||
$jwt = JWT::encode($payload, $privateKey, 'EdDSA');
|
||||
echo "Encode:\n" . print_r($jwt, true) . "\n";
|
||||
|
||||
$decoded = JWT::decode($jwt, new Key($publicKey, 'EdDSA'));
|
||||
echo "Decode:\n" . print_r((array) $decoded, true) . "\n";
|
||||
````
|
||||
|
||||
Example with multiple keys
|
||||
--------------------------
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
|
||||
// Example RSA keys from previous example
|
||||
// $privateKey1 = '...';
|
||||
// $publicKey1 = '...';
|
||||
|
||||
// Example EdDSA keys from previous example
|
||||
// $privateKey2 = '...';
|
||||
// $publicKey2 = '...';
|
||||
|
||||
$payload = [
|
||||
'iss' => 'example.org',
|
||||
'aud' => 'example.com',
|
||||
'iat' => 1356999524,
|
||||
'nbf' => 1357000000
|
||||
];
|
||||
|
||||
$jwt1 = JWT::encode($payload, $privateKey1, 'RS256', 'kid1');
|
||||
$jwt2 = JWT::encode($payload, $privateKey2, 'EdDSA', 'kid2');
|
||||
echo "Encode 1:\n" . print_r($jwt1, true) . "\n";
|
||||
echo "Encode 2:\n" . print_r($jwt2, true) . "\n";
|
||||
|
||||
$keys = [
|
||||
'kid1' => new Key($publicKey1, 'RS256'),
|
||||
'kid2' => new Key($publicKey2, 'EdDSA'),
|
||||
];
|
||||
|
||||
$decoded1 = JWT::decode($jwt1, $keys);
|
||||
$decoded2 = JWT::decode($jwt2, $keys);
|
||||
|
||||
echo "Decode 1:\n" . print_r((array) $decoded1, true) . "\n";
|
||||
echo "Decode 2:\n" . print_r((array) $decoded2, true) . "\n";
|
||||
```
|
||||
|
||||
Using JWKs
|
||||
----------
|
||||
|
||||
```php
|
||||
use Firebase\JWT\JWK;
|
||||
use Firebase\JWT\JWT;
|
||||
|
||||
// Set of keys. The "keys" key is required. For example, the JSON response to
|
||||
// this endpoint: https://www.gstatic.com/iap/verify/public_key-jwk
|
||||
$jwks = ['keys' => []];
|
||||
|
||||
// JWK::parseKeySet($jwks) returns an associative array of **kid** to Firebase\JWT\Key
|
||||
// objects. Pass this as the second parameter to JWT::decode.
|
||||
JWT::decode($jwt, JWK::parseKeySet($jwks));
|
||||
```
|
||||
|
||||
Using Cached Key Sets
|
||||
---------------------
|
||||
|
||||
The `CachedKeySet` class can be used to fetch and cache JWKS (JSON Web Key Sets) from a public URI.
|
||||
This has the following advantages:
|
||||
|
||||
1. The results are cached for performance.
|
||||
2. If an unrecognized key is requested, the cache is refreshed, to accomodate for key rotation.
|
||||
3. If rate limiting is enabled, the JWKS URI will not make more than 10 requests a second.
|
||||
|
||||
```php
|
||||
use Firebase\JWT\CachedKeySet;
|
||||
use Firebase\JWT\JWT;
|
||||
|
||||
// The URI for the JWKS you wish to cache the results from
|
||||
$jwksUri = 'https://www.gstatic.com/iap/verify/public_key-jwk';
|
||||
|
||||
// Create an HTTP client (can be any PSR-7 compatible HTTP client)
|
||||
$httpClient = new GuzzleHttp\Client();
|
||||
|
||||
// Create an HTTP request factory (can be any PSR-17 compatible HTTP request factory)
|
||||
$httpFactory = new GuzzleHttp\Psr\HttpFactory();
|
||||
|
||||
// Create a cache item pool (can be any PSR-6 compatible cache item pool)
|
||||
$cacheItemPool = Phpfastcache\CacheManager::getInstance('files');
|
||||
|
||||
$keySet = new CachedKeySet(
|
||||
$jwksUri,
|
||||
$httpClient,
|
||||
$httpFactory,
|
||||
$cacheItemPool,
|
||||
null, // $expiresAfter int seconds to set the JWKS to expire
|
||||
true // $rateLimit true to enable rate limit of 10 RPS on lookup of invalid keys
|
||||
);
|
||||
|
||||
$jwt = 'eyJhbGci...'; // Some JWT signed by a key from the $jwkUri above
|
||||
$decoded = JWT::decode($jwt, $keySet);
|
||||
```
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
|
||||
#### Exception Handling
|
||||
|
||||
When a call to `JWT::decode` is invalid, it will throw one of the following exceptions:
|
||||
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\SignatureInvalidException;
|
||||
use Firebase\JWT\BeforeValidException;
|
||||
use Firebase\JWT\ExpiredException;
|
||||
use DomainException;
|
||||
use InvalidArgumentException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
try {
|
||||
$decoded = JWT::decode($jwt, $keys);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
// provided key/key-array is empty or malformed.
|
||||
} catch (DomainException $e) {
|
||||
// provided algorithm is unsupported OR
|
||||
// provided key is invalid OR
|
||||
// unknown error thrown in openSSL or libsodium OR
|
||||
// libsodium is required but not available.
|
||||
} catch (SignatureInvalidException $e) {
|
||||
// provided JWT signature verification failed.
|
||||
} catch (BeforeValidException $e) {
|
||||
// provided JWT is trying to be used before "nbf" claim OR
|
||||
// provided JWT is trying to be used before "iat" claim.
|
||||
} catch (ExpiredException $e) {
|
||||
// provided JWT is trying to be used after "exp" claim.
|
||||
} catch (UnexpectedValueException $e) {
|
||||
// provided JWT is malformed OR
|
||||
// provided JWT is missing an algorithm / using an unsupported algorithm OR
|
||||
// provided JWT algorithm does not match provided key OR
|
||||
// provided key ID in key/key-array is empty or invalid.
|
||||
}
|
||||
```
|
||||
|
||||
All exceptions in the `Firebase\JWT` namespace extend `UnexpectedValueException`, and can be simplified
|
||||
like this:
|
||||
|
||||
```php
|
||||
use Firebase\JWT\JWT;
|
||||
use UnexpectedValueException;
|
||||
try {
|
||||
$decoded = JWT::decode($jwt, $keys);
|
||||
} catch (LogicException $e) {
|
||||
// errors having to do with environmental setup or malformed JWT Keys
|
||||
} catch (UnexpectedValueException $e) {
|
||||
// errors having to do with JWT signature and claims
|
||||
}
|
||||
```
|
||||
|
||||
#### Casting to array
|
||||
|
||||
The return value of `JWT::decode` is the generic PHP object `stdClass`. If you'd like to handle with arrays
|
||||
instead, you can do the following:
|
||||
|
||||
```php
|
||||
// return type is stdClass
|
||||
$decoded = JWT::decode($jwt, $keys);
|
||||
|
||||
// cast to array
|
||||
$decoded = json_decode(json_encode($decoded), true);
|
||||
```
|
||||
|
||||
Tests
|
||||
-----
|
||||
Run the tests using phpunit:
|
||||
|
||||
```bash
|
||||
$ pear install PHPUnit
|
||||
$ phpunit --configuration phpunit.xml.dist
|
||||
PHPUnit 3.7.10 by Sebastian Bergmann.
|
||||
.....
|
||||
Time: 0 seconds, Memory: 2.50Mb
|
||||
OK (5 tests, 5 assertions)
|
||||
```
|
||||
|
||||
New Lines in private keys
|
||||
-----
|
||||
|
||||
If your private key contains `\n` characters, be sure to wrap it in double quotes `""`
|
||||
and not single quotes `''` in order to properly interpret the escaped characters.
|
||||
|
||||
License
|
||||
-------
|
||||
[3-Clause BSD](http://opensource.org/licenses/BSD-3-Clause).
|
||||
42
vendor/firebase/php-jwt/composer.json
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "firebase/php-jwt",
|
||||
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
|
||||
"homepage": "https://github.com/firebase/php-jwt",
|
||||
"keywords": [
|
||||
"php",
|
||||
"jwt"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Neuman Vong",
|
||||
"email": "neuman+pear@twilio.com",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Anant Narayanan",
|
||||
"email": "anant@php.net",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"require": {
|
||||
"php": "^8.0"
|
||||
},
|
||||
"suggest": {
|
||||
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present",
|
||||
"ext-sodium": "Support EdDSA (Ed25519) signatures"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Firebase\\JWT\\": "src"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"guzzlehttp/guzzle": "^7.4",
|
||||
"phpspec/prophecy-phpunit": "^2.0",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"psr/cache": "^2.0||^3.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0"
|
||||
}
|
||||
}
|
||||
18
vendor/firebase/php-jwt/src/BeforeValidException.php
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Firebase\JWT;
|
||||
|
||||
class BeforeValidException extends \UnexpectedValueException implements JWTExceptionWithPayloadInterface
|
||||
{
|
||||
private object $payload;
|
||||
|
||||
public function setPayload(object $payload): void
|
||||
{
|
||||
$this->payload = $payload;
|
||||
}
|
||||
|
||||
public function getPayload(): object
|
||||
{
|
||||
return $this->payload;
|
||||
}
|
||||
}
|
||||
274
vendor/firebase/php-jwt/src/CachedKeySet.php
vendored
Normal file
@ -0,0 +1,274 @@
|
||||
<?php
|
||||
|
||||
namespace Firebase\JWT;
|
||||
|
||||
use ArrayAccess;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use OutOfBoundsException;
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use RuntimeException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* @implements ArrayAccess<string, Key>
|
||||
*/
|
||||
class CachedKeySet implements ArrayAccess
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $jwksUri;
|
||||
/**
|
||||
* @var ClientInterface
|
||||
*/
|
||||
private $httpClient;
|
||||
/**
|
||||
* @var RequestFactoryInterface
|
||||
*/
|
||||
private $httpFactory;
|
||||
/**
|
||||
* @var CacheItemPoolInterface
|
||||
*/
|
||||
private $cache;
|
||||
/**
|
||||
* @var ?int
|
||||
*/
|
||||
private $expiresAfter;
|
||||
/**
|
||||
* @var ?CacheItemInterface
|
||||
*/
|
||||
private $cacheItem;
|
||||
/**
|
||||
* @var array<string, array<mixed>>
|
||||
*/
|
||||
private $keySet;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $cacheKey;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $cacheKeyPrefix = 'jwks';
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $maxKeyLength = 64;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $rateLimit;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $rateLimitCacheKey;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $maxCallsPerMinute = 10;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $defaultAlg;
|
||||
|
||||
public function __construct(
|
||||
string $jwksUri,
|
||||
ClientInterface $httpClient,
|
||||
RequestFactoryInterface $httpFactory,
|
||||
CacheItemPoolInterface $cache,
|
||||
?int $expiresAfter = null,
|
||||
bool $rateLimit = false,
|
||||
?string $defaultAlg = null
|
||||
) {
|
||||
$this->jwksUri = $jwksUri;
|
||||
$this->httpClient = $httpClient;
|
||||
$this->httpFactory = $httpFactory;
|
||||
$this->cache = $cache;
|
||||
$this->expiresAfter = $expiresAfter;
|
||||
$this->rateLimit = $rateLimit;
|
||||
$this->defaultAlg = $defaultAlg;
|
||||
$this->setCacheKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $keyId
|
||||
* @return Key
|
||||
*/
|
||||
public function offsetGet($keyId): Key
|
||||
{
|
||||
if (!$this->keyIdExists($keyId)) {
|
||||
throw new OutOfBoundsException('Key ID not found');
|
||||
}
|
||||
return JWK::parseKey($this->keySet[$keyId], $this->defaultAlg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $keyId
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($keyId): bool
|
||||
{
|
||||
return $this->keyIdExists($keyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @param Key $value
|
||||
*/
|
||||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
throw new LogicException('Method not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
*/
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
throw new LogicException('Method not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
private function formatJwksForCache(string $jwks): array
|
||||
{
|
||||
$jwks = json_decode($jwks, true);
|
||||
|
||||
if (!isset($jwks['keys'])) {
|
||||
throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
|
||||
}
|
||||
|
||||
if (empty($jwks['keys'])) {
|
||||
throw new InvalidArgumentException('JWK Set did not contain any keys');
|
||||
}
|
||||
|
||||
$keys = [];
|
||||
foreach ($jwks['keys'] as $k => $v) {
|
||||
$kid = isset($v['kid']) ? $v['kid'] : $k;
|
||||
$keys[(string) $kid] = $v;
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
private function keyIdExists(string $keyId): bool
|
||||
{
|
||||
if (null === $this->keySet) {
|
||||
$item = $this->getCacheItem();
|
||||
// Try to load keys from cache
|
||||
if ($item->isHit()) {
|
||||
// item found! retrieve it
|
||||
$this->keySet = $item->get();
|
||||
// If the cached item is a string, the JWKS response was cached (previous behavior).
|
||||
// Parse this into expected format array<kid, jwk> instead.
|
||||
if (\is_string($this->keySet)) {
|
||||
$this->keySet = $this->formatJwksForCache($this->keySet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($this->keySet[$keyId])) {
|
||||
if ($this->rateLimitExceeded()) {
|
||||
return false;
|
||||
}
|
||||
$request = $this->httpFactory->createRequest('GET', $this->jwksUri);
|
||||
$jwksResponse = $this->httpClient->sendRequest($request);
|
||||
if ($jwksResponse->getStatusCode() !== 200) {
|
||||
throw new UnexpectedValueException(
|
||||
\sprintf('HTTP Error: %d %s for URI "%s"',
|
||||
$jwksResponse->getStatusCode(),
|
||||
$jwksResponse->getReasonPhrase(),
|
||||
$this->jwksUri,
|
||||
),
|
||||
$jwksResponse->getStatusCode()
|
||||
);
|
||||
}
|
||||
$this->keySet = $this->formatJwksForCache((string) $jwksResponse->getBody());
|
||||
|
||||
if (!isset($this->keySet[$keyId])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$item = $this->getCacheItem();
|
||||
$item->set($this->keySet);
|
||||
if ($this->expiresAfter) {
|
||||
$item->expiresAfter($this->expiresAfter);
|
||||
}
|
||||
$this->cache->save($item);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function rateLimitExceeded(): bool
|
||||
{
|
||||
if (!$this->rateLimit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cacheItem = $this->cache->getItem($this->rateLimitCacheKey);
|
||||
|
||||
$cacheItemData = [];
|
||||
if ($cacheItem->isHit() && \is_array($data = $cacheItem->get())) {
|
||||
$cacheItemData = $data;
|
||||
}
|
||||
|
||||
$callsPerMinute = $cacheItemData['callsPerMinute'] ?? 0;
|
||||
$expiry = $cacheItemData['expiry'] ?? new \DateTime('+60 seconds', new \DateTimeZone('UTC'));
|
||||
|
||||
if (++$callsPerMinute > $this->maxCallsPerMinute) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$cacheItem->set(['expiry' => $expiry, 'callsPerMinute' => $callsPerMinute]);
|
||||
$cacheItem->expiresAt($expiry);
|
||||
$this->cache->save($cacheItem);
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getCacheItem(): CacheItemInterface
|
||||
{
|
||||
if (\is_null($this->cacheItem)) {
|
||||
$this->cacheItem = $this->cache->getItem($this->cacheKey);
|
||||
}
|
||||
|
||||
return $this->cacheItem;
|
||||
}
|
||||
|
||||
private function setCacheKeys(): void
|
||||
{
|
||||
if (empty($this->jwksUri)) {
|
||||
throw new RuntimeException('JWKS URI is empty');
|
||||
}
|
||||
|
||||
// ensure we do not have illegal characters
|
||||
$key = preg_replace('|[^a-zA-Z0-9_\.!]|', '', $this->jwksUri);
|
||||
|
||||
// add prefix
|
||||
$key = $this->cacheKeyPrefix . $key;
|
||||
|
||||
// Hash keys if they exceed $maxKeyLength of 64
|
||||
if (\strlen($key) > $this->maxKeyLength) {
|
||||
$key = substr(hash('sha256', $key), 0, $this->maxKeyLength);
|
||||
}
|
||||
|
||||
$this->cacheKey = $key;
|
||||
|
||||
if ($this->rateLimit) {
|
||||
// add prefix
|
||||
$rateLimitKey = $this->cacheKeyPrefix . 'ratelimit' . $key;
|
||||
|
||||
// Hash keys if they exceed $maxKeyLength of 64
|
||||
if (\strlen($rateLimitKey) > $this->maxKeyLength) {
|
||||
$rateLimitKey = substr(hash('sha256', $rateLimitKey), 0, $this->maxKeyLength);
|
||||
}
|
||||
|
||||
$this->rateLimitCacheKey = $rateLimitKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
vendor/firebase/php-jwt/src/ExpiredException.php
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Firebase\JWT;
|
||||
|
||||
class ExpiredException extends \UnexpectedValueException implements JWTExceptionWithPayloadInterface
|
||||
{
|
||||
private object $payload;
|
||||
|
||||
public function setPayload(object $payload): void
|
||||
{
|
||||
$this->payload = $payload;
|
||||
}
|
||||
|
||||
public function getPayload(): object
|
||||
{
|
||||
return $this->payload;
|
||||
}
|
||||
}
|
||||
355
vendor/firebase/php-jwt/src/JWK.php
vendored
Normal file
@ -0,0 +1,355 @@
|
||||
<?php
|
||||
|
||||
namespace Firebase\JWT;
|
||||
|
||||
use DomainException;
|
||||
use InvalidArgumentException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* JSON Web Key implementation, based on this spec:
|
||||
* https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category Authentication
|
||||
* @package Authentication_JWT
|
||||
* @author Bui Sy Nguyen <nguyenbs@gmail.com>
|
||||
* @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
|
||||
* @link https://github.com/firebase/php-jwt
|
||||
*/
|
||||
class JWK
|
||||
{
|
||||
private const OID = '1.2.840.10045.2.1';
|
||||
private const ASN1_OBJECT_IDENTIFIER = 0x06;
|
||||
private const ASN1_SEQUENCE = 0x10; // also defined in JWT
|
||||
private const ASN1_BIT_STRING = 0x03;
|
||||
private const EC_CURVES = [
|
||||
'P-256' => '1.2.840.10045.3.1.7', // Len: 64
|
||||
'secp256k1' => '1.3.132.0.10', // Len: 64
|
||||
'P-384' => '1.3.132.0.34', // Len: 96
|
||||
// 'P-521' => '1.3.132.0.35', // Len: 132 (not supported)
|
||||
];
|
||||
|
||||
// For keys with "kty" equal to "OKP" (Octet Key Pair), the "crv" parameter must contain the key subtype.
|
||||
// This library supports the following subtypes:
|
||||
private const OKP_SUBTYPES = [
|
||||
'Ed25519' => true, // RFC 8037
|
||||
];
|
||||
|
||||
/**
|
||||
* Parse a set of JWK keys
|
||||
*
|
||||
* @param array<mixed> $jwks The JSON Web Key Set as an associative array
|
||||
* @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the
|
||||
* JSON Web Key Set
|
||||
*
|
||||
* @return array<string, Key> An associative array of key IDs (kid) to Key objects
|
||||
*
|
||||
* @throws InvalidArgumentException Provided JWK Set is empty
|
||||
* @throws UnexpectedValueException Provided JWK Set was invalid
|
||||
* @throws DomainException OpenSSL failure
|
||||
*
|
||||
* @uses parseKey
|
||||
*/
|
||||
public static function parseKeySet(array $jwks, ?string $defaultAlg = null): array
|
||||
{
|
||||
$keys = [];
|
||||
|
||||
if (!isset($jwks['keys'])) {
|
||||
throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
|
||||
}
|
||||
|
||||
if (empty($jwks['keys'])) {
|
||||
throw new InvalidArgumentException('JWK Set did not contain any keys');
|
||||
}
|
||||
|
||||
foreach ($jwks['keys'] as $k => $v) {
|
||||
$kid = isset($v['kid']) ? $v['kid'] : $k;
|
||||
if ($key = self::parseKey($v, $defaultAlg)) {
|
||||
$keys[(string) $kid] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === \count($keys)) {
|
||||
throw new UnexpectedValueException('No supported algorithms found in JWK Set');
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a JWK key
|
||||
*
|
||||
* @param array<mixed> $jwk An individual JWK
|
||||
* @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the
|
||||
* JSON Web Key Set
|
||||
*
|
||||
* @return Key The key object for the JWK
|
||||
*
|
||||
* @throws InvalidArgumentException Provided JWK is empty
|
||||
* @throws UnexpectedValueException Provided JWK was invalid
|
||||
* @throws DomainException OpenSSL failure
|
||||
*
|
||||
* @uses createPemFromModulusAndExponent
|
||||
*/
|
||||
public static function parseKey(array $jwk, ?string $defaultAlg = null): ?Key
|
||||
{
|
||||
if (empty($jwk)) {
|
||||
throw new InvalidArgumentException('JWK must not be empty');
|
||||
}
|
||||
|
||||
if (!isset($jwk['kty'])) {
|
||||
throw new UnexpectedValueException('JWK must contain a "kty" parameter');
|
||||
}
|
||||
|
||||
if (!isset($jwk['alg'])) {
|
||||
if (\is_null($defaultAlg)) {
|
||||
// The "alg" parameter is optional in a KTY, but an algorithm is required
|
||||
// for parsing in this library. Use the $defaultAlg parameter when parsing the
|
||||
// key set in order to prevent this error.
|
||||
// @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4
|
||||
throw new UnexpectedValueException('JWK must contain an "alg" parameter');
|
||||
}
|
||||
$jwk['alg'] = $defaultAlg;
|
||||
}
|
||||
|
||||
switch ($jwk['kty']) {
|
||||
case 'RSA':
|
||||
if (!empty($jwk['d'])) {
|
||||
throw new UnexpectedValueException('RSA private keys are not supported');
|
||||
}
|
||||
if (!isset($jwk['n']) || !isset($jwk['e'])) {
|
||||
throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"');
|
||||
}
|
||||
|
||||
$pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']);
|
||||
$publicKey = \openssl_pkey_get_public($pem);
|
||||
if (false === $publicKey) {
|
||||
throw new DomainException(
|
||||
'OpenSSL error: ' . \openssl_error_string()
|
||||
);
|
||||
}
|
||||
return new Key($publicKey, $jwk['alg']);
|
||||
case 'EC':
|
||||
if (isset($jwk['d'])) {
|
||||
// The key is actually a private key
|
||||
throw new UnexpectedValueException('Key data must be for a public key');
|
||||
}
|
||||
|
||||
if (empty($jwk['crv'])) {
|
||||
throw new UnexpectedValueException('crv not set');
|
||||
}
|
||||
|
||||
if (!isset(self::EC_CURVES[$jwk['crv']])) {
|
||||
throw new DomainException('Unrecognised or unsupported EC curve');
|
||||
}
|
||||
|
||||
if (empty($jwk['x']) || empty($jwk['y'])) {
|
||||
throw new UnexpectedValueException('x and y not set');
|
||||
}
|
||||
|
||||
$publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']);
|
||||
return new Key($publicKey, $jwk['alg']);
|
||||
case 'OKP':
|
||||
if (isset($jwk['d'])) {
|
||||
// The key is actually a private key
|
||||
throw new UnexpectedValueException('Key data must be for a public key');
|
||||
}
|
||||
|
||||
if (!isset($jwk['crv'])) {
|
||||
throw new UnexpectedValueException('crv not set');
|
||||
}
|
||||
|
||||
if (empty(self::OKP_SUBTYPES[$jwk['crv']])) {
|
||||
throw new DomainException('Unrecognised or unsupported OKP key subtype');
|
||||
}
|
||||
|
||||
if (empty($jwk['x'])) {
|
||||
throw new UnexpectedValueException('x not set');
|
||||
}
|
||||
|
||||
// This library works internally with EdDSA keys (Ed25519) encoded in standard base64.
|
||||
$publicKey = JWT::convertBase64urlToBase64($jwk['x']);
|
||||
return new Key($publicKey, $jwk['alg']);
|
||||
case 'oct':
|
||||
if (!isset($jwk['k'])) {
|
||||
throw new UnexpectedValueException('k not set');
|
||||
}
|
||||
|
||||
return new Key(JWT::urlsafeB64Decode($jwk['k']), $jwk['alg']);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the EC JWK values to pem format.
|
||||
*
|
||||
* @param string $crv The EC curve (only P-256 & P-384 is supported)
|
||||
* @param string $x The EC x-coordinate
|
||||
* @param string $y The EC y-coordinate
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function createPemFromCrvAndXYCoordinates(string $crv, string $x, string $y): string
|
||||
{
|
||||
$pem =
|
||||
self::encodeDER(
|
||||
self::ASN1_SEQUENCE,
|
||||
self::encodeDER(
|
||||
self::ASN1_SEQUENCE,
|
||||
self::encodeDER(
|
||||
self::ASN1_OBJECT_IDENTIFIER,
|
||||
self::encodeOID(self::OID)
|
||||
)
|
||||
. self::encodeDER(
|
||||
self::ASN1_OBJECT_IDENTIFIER,
|
||||
self::encodeOID(self::EC_CURVES[$crv])
|
||||
)
|
||||
) .
|
||||
self::encodeDER(
|
||||
self::ASN1_BIT_STRING,
|
||||
\chr(0x00) . \chr(0x04)
|
||||
. JWT::urlsafeB64Decode($x)
|
||||
. JWT::urlsafeB64Decode($y)
|
||||
)
|
||||
);
|
||||
|
||||
return \sprintf(
|
||||
"-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n",
|
||||
wordwrap(base64_encode($pem), 64, "\n", true)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a public key represented in PEM format from RSA modulus and exponent information
|
||||
*
|
||||
* @param string $n The RSA modulus encoded in Base64
|
||||
* @param string $e The RSA exponent encoded in Base64
|
||||
*
|
||||
* @return string The RSA public key represented in PEM format
|
||||
*
|
||||
* @uses encodeLength
|
||||
*/
|
||||
private static function createPemFromModulusAndExponent(
|
||||
string $n,
|
||||
string $e
|
||||
): string {
|
||||
$mod = JWT::urlsafeB64Decode($n);
|
||||
$exp = JWT::urlsafeB64Decode($e);
|
||||
|
||||
$modulus = \pack('Ca*a*', 2, self::encodeLength(\strlen($mod)), $mod);
|
||||
$publicExponent = \pack('Ca*a*', 2, self::encodeLength(\strlen($exp)), $exp);
|
||||
|
||||
$rsaPublicKey = \pack(
|
||||
'Ca*a*a*',
|
||||
48,
|
||||
self::encodeLength(\strlen($modulus) + \strlen($publicExponent)),
|
||||
$modulus,
|
||||
$publicExponent
|
||||
);
|
||||
|
||||
// sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption.
|
||||
$rsaOID = \pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
|
||||
$rsaPublicKey = \chr(0) . $rsaPublicKey;
|
||||
$rsaPublicKey = \chr(3) . self::encodeLength(\strlen($rsaPublicKey)) . $rsaPublicKey;
|
||||
|
||||
$rsaPublicKey = \pack(
|
||||
'Ca*a*',
|
||||
48,
|
||||
self::encodeLength(\strlen($rsaOID . $rsaPublicKey)),
|
||||
$rsaOID . $rsaPublicKey
|
||||
);
|
||||
|
||||
return "-----BEGIN PUBLIC KEY-----\r\n" .
|
||||
\chunk_split(\base64_encode($rsaPublicKey), 64) .
|
||||
'-----END PUBLIC KEY-----';
|
||||
}
|
||||
|
||||
/**
|
||||
* DER-encode the length
|
||||
*
|
||||
* DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
|
||||
* {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
|
||||
*
|
||||
* @param int $length
|
||||
* @return string
|
||||
*/
|
||||
private static function encodeLength(int $length): string
|
||||
{
|
||||
if ($length <= 0x7F) {
|
||||
return \chr($length);
|
||||
}
|
||||
|
||||
$temp = \ltrim(\pack('N', $length), \chr(0));
|
||||
|
||||
return \pack('Ca*', 0x80 | \strlen($temp), $temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a value into a DER object.
|
||||
* Also defined in Firebase\JWT\JWT
|
||||
*
|
||||
* @param int $type DER tag
|
||||
* @param string $value the value to encode
|
||||
* @return string the encoded object
|
||||
*/
|
||||
private static function encodeDER(int $type, string $value): string
|
||||
{
|
||||
$tag_header = 0;
|
||||
if ($type === self::ASN1_SEQUENCE) {
|
||||
$tag_header |= 0x20;
|
||||
}
|
||||
|
||||
// Type
|
||||
$der = \chr($tag_header | $type);
|
||||
|
||||
// Length
|
||||
$der .= \chr(\strlen($value));
|
||||
|
||||
return $der . $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a string into a DER-encoded OID.
|
||||
*
|
||||
* @param string $oid the OID string
|
||||
* @return string the binary DER-encoded OID
|
||||
*/
|
||||
private static function encodeOID(string $oid): string
|
||||
{
|
||||
$octets = explode('.', $oid);
|
||||
|
||||
// Get the first octet
|
||||
$first = (int) array_shift($octets);
|
||||
$second = (int) array_shift($octets);
|
||||
$oid = \chr($first * 40 + $second);
|
||||
|
||||
// Iterate over subsequent octets
|
||||
foreach ($octets as $octet) {
|
||||
if ($octet == 0) {
|
||||
$oid .= \chr(0x00);
|
||||
continue;
|
||||
}
|
||||
$bin = '';
|
||||
|
||||
while ($octet) {
|
||||
$bin .= \chr(0x80 | ($octet & 0x7f));
|
||||
$octet >>= 7;
|
||||
}
|
||||
$bin[0] = $bin[0] & \chr(0x7f);
|
||||
|
||||
// Convert to big endian if necessary
|
||||
if (pack('V', 65534) == pack('L', 65534)) {
|
||||
$oid .= strrev($bin);
|
||||
} else {
|
||||
$oid .= $bin;
|
||||
}
|
||||
}
|
||||
|
||||
return $oid;
|
||||
}
|
||||
}
|
||||
667
vendor/firebase/php-jwt/src/JWT.php
vendored
Normal file
@ -0,0 +1,667 @@
|
||||
<?php
|
||||
|
||||
namespace Firebase\JWT;
|
||||
|
||||
use ArrayAccess;
|
||||
use DateTime;
|
||||
use DomainException;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use OpenSSLAsymmetricKey;
|
||||
use OpenSSLCertificate;
|
||||
use stdClass;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* JSON Web Token implementation, based on this spec:
|
||||
* https://tools.ietf.org/html/rfc7519
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category Authentication
|
||||
* @package Authentication_JWT
|
||||
* @author Neuman Vong <neuman@twilio.com>
|
||||
* @author Anant Narayanan <anant@php.net>
|
||||
* @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
|
||||
* @link https://github.com/firebase/php-jwt
|
||||
*/
|
||||
class JWT
|
||||
{
|
||||
private const ASN1_INTEGER = 0x02;
|
||||
private const ASN1_SEQUENCE = 0x10;
|
||||
private const ASN1_BIT_STRING = 0x03;
|
||||
|
||||
/**
|
||||
* When checking nbf, iat or expiration times,
|
||||
* we want to provide some extra leeway time to
|
||||
* account for clock skew.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public static $leeway = 0;
|
||||
|
||||
/**
|
||||
* Allow the current timestamp to be specified.
|
||||
* Useful for fixing a value within unit testing.
|
||||
* Will default to PHP time() value if null.
|
||||
*
|
||||
* @var ?int
|
||||
*/
|
||||
public static $timestamp = null;
|
||||
|
||||
/**
|
||||
* @var array<string, string[]>
|
||||
*/
|
||||
public static $supported_algs = [
|
||||
'ES384' => ['openssl', 'SHA384'],
|
||||
'ES256' => ['openssl', 'SHA256'],
|
||||
'ES256K' => ['openssl', 'SHA256'],
|
||||
'HS256' => ['hash_hmac', 'SHA256'],
|
||||
'HS384' => ['hash_hmac', 'SHA384'],
|
||||
'HS512' => ['hash_hmac', 'SHA512'],
|
||||
'RS256' => ['openssl', 'SHA256'],
|
||||
'RS384' => ['openssl', 'SHA384'],
|
||||
'RS512' => ['openssl', 'SHA512'],
|
||||
'EdDSA' => ['sodium_crypto', 'EdDSA'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Decodes a JWT string into a PHP object.
|
||||
*
|
||||
* @param string $jwt The JWT
|
||||
* @param Key|ArrayAccess<string,Key>|array<string,Key> $keyOrKeyArray The Key or associative array of key IDs
|
||||
* (kid) to Key objects.
|
||||
* If the algorithm used is asymmetric, this is
|
||||
* the public key.
|
||||
* Each Key object contains an algorithm and
|
||||
* matching key.
|
||||
* Supported algorithms are 'ES384','ES256',
|
||||
* 'HS256', 'HS384', 'HS512', 'RS256', 'RS384'
|
||||
* and 'RS512'.
|
||||
* @param stdClass $headers Optional. Populates stdClass with headers.
|
||||
*
|
||||
* @return stdClass The JWT's payload as a PHP object
|
||||
*
|
||||
* @throws InvalidArgumentException Provided key/key-array was empty or malformed
|
||||
* @throws DomainException Provided JWT is malformed
|
||||
* @throws UnexpectedValueException Provided JWT was invalid
|
||||
* @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed
|
||||
* @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf'
|
||||
* @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat'
|
||||
* @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim
|
||||
*
|
||||
* @uses jsonDecode
|
||||
* @uses urlsafeB64Decode
|
||||
*/
|
||||
public static function decode(
|
||||
string $jwt,
|
||||
$keyOrKeyArray,
|
||||
?stdClass &$headers = null
|
||||
): stdClass {
|
||||
// Validate JWT
|
||||
$timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp;
|
||||
|
||||
if (empty($keyOrKeyArray)) {
|
||||
throw new InvalidArgumentException('Key may not be empty');
|
||||
}
|
||||
$tks = \explode('.', $jwt);
|
||||
if (\count($tks) !== 3) {
|
||||
throw new UnexpectedValueException('Wrong number of segments');
|
||||
}
|
||||
list($headb64, $bodyb64, $cryptob64) = $tks;
|
||||
$headerRaw = static::urlsafeB64Decode($headb64);
|
||||
if (null === ($header = static::jsonDecode($headerRaw))) {
|
||||
throw new UnexpectedValueException('Invalid header encoding');
|
||||
}
|
||||
if ($headers !== null) {
|
||||
$headers = $header;
|
||||
}
|
||||
$payloadRaw = static::urlsafeB64Decode($bodyb64);
|
||||
if (null === ($payload = static::jsonDecode($payloadRaw))) {
|
||||
throw new UnexpectedValueException('Invalid claims encoding');
|
||||
}
|
||||
if (\is_array($payload)) {
|
||||
// prevent PHP Fatal Error in edge-cases when payload is empty array
|
||||
$payload = (object) $payload;
|
||||
}
|
||||
if (!$payload instanceof stdClass) {
|
||||
throw new UnexpectedValueException('Payload must be a JSON object');
|
||||
}
|
||||
$sig = static::urlsafeB64Decode($cryptob64);
|
||||
if (empty($header->alg)) {
|
||||
throw new UnexpectedValueException('Empty algorithm');
|
||||
}
|
||||
if (empty(static::$supported_algs[$header->alg])) {
|
||||
throw new UnexpectedValueException('Algorithm not supported');
|
||||
}
|
||||
|
||||
$key = self::getKey($keyOrKeyArray, property_exists($header, 'kid') ? $header->kid : null);
|
||||
|
||||
// Check the algorithm
|
||||
if (!self::constantTimeEquals($key->getAlgorithm(), $header->alg)) {
|
||||
// See issue #351
|
||||
throw new UnexpectedValueException('Incorrect key for this algorithm');
|
||||
}
|
||||
if (\in_array($header->alg, ['ES256', 'ES256K', 'ES384'], true)) {
|
||||
// OpenSSL expects an ASN.1 DER sequence for ES256/ES256K/ES384 signatures
|
||||
$sig = self::signatureToDER($sig);
|
||||
}
|
||||
if (!self::verify("{$headb64}.{$bodyb64}", $sig, $key->getKeyMaterial(), $header->alg)) {
|
||||
throw new SignatureInvalidException('Signature verification failed');
|
||||
}
|
||||
|
||||
// Check the nbf if it is defined. This is the time that the
|
||||
// token can actually be used. If it's not yet that time, abort.
|
||||
if (isset($payload->nbf) && floor($payload->nbf) > ($timestamp + static::$leeway)) {
|
||||
$ex = new BeforeValidException(
|
||||
'Cannot handle token with nbf prior to ' . \date(DateTime::ISO8601, (int) floor($payload->nbf))
|
||||
);
|
||||
$ex->setPayload($payload);
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
// Check that this token has been created before 'now'. This prevents
|
||||
// using tokens that have been created for later use (and haven't
|
||||
// correctly used the nbf claim).
|
||||
if (!isset($payload->nbf) && isset($payload->iat) && floor($payload->iat) > ($timestamp + static::$leeway)) {
|
||||
$ex = new BeforeValidException(
|
||||
'Cannot handle token with iat prior to ' . \date(DateTime::ISO8601, (int) floor($payload->iat))
|
||||
);
|
||||
$ex->setPayload($payload);
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
// Check if this token has expired.
|
||||
if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
|
||||
$ex = new ExpiredException('Expired token');
|
||||
$ex->setPayload($payload);
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts and signs a PHP array into a JWT string.
|
||||
*
|
||||
* @param array<mixed> $payload PHP array
|
||||
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
|
||||
* @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256',
|
||||
* 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
|
||||
* @param string $keyId
|
||||
* @param array<string, string> $head An array with header elements to attach
|
||||
*
|
||||
* @return string A signed JWT
|
||||
*
|
||||
* @uses jsonEncode
|
||||
* @uses urlsafeB64Encode
|
||||
*/
|
||||
public static function encode(
|
||||
array $payload,
|
||||
$key,
|
||||
string $alg,
|
||||
?string $keyId = null,
|
||||
?array $head = null
|
||||
): string {
|
||||
$header = ['typ' => 'JWT'];
|
||||
if (isset($head)) {
|
||||
$header = \array_merge($header, $head);
|
||||
}
|
||||
$header['alg'] = $alg;
|
||||
if ($keyId !== null) {
|
||||
$header['kid'] = $keyId;
|
||||
}
|
||||
$segments = [];
|
||||
$segments[] = static::urlsafeB64Encode((string) static::jsonEncode($header));
|
||||
$segments[] = static::urlsafeB64Encode((string) static::jsonEncode($payload));
|
||||
$signing_input = \implode('.', $segments);
|
||||
|
||||
$signature = static::sign($signing_input, $key, $alg);
|
||||
$segments[] = static::urlsafeB64Encode($signature);
|
||||
|
||||
return \implode('.', $segments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a string with a given key and algorithm.
|
||||
*
|
||||
* @param string $msg The message to sign
|
||||
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
|
||||
* @param string $alg Supported algorithms are 'EdDSA', 'ES384', 'ES256', 'ES256K', 'HS256',
|
||||
* 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
|
||||
*
|
||||
* @return string An encrypted message
|
||||
*
|
||||
* @throws DomainException Unsupported algorithm or bad key was specified
|
||||
*/
|
||||
public static function sign(
|
||||
string $msg,
|
||||
$key,
|
||||
string $alg
|
||||
): string {
|
||||
if (empty(static::$supported_algs[$alg])) {
|
||||
throw new DomainException('Algorithm not supported');
|
||||
}
|
||||
list($function, $algorithm) = static::$supported_algs[$alg];
|
||||
switch ($function) {
|
||||
case 'hash_hmac':
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException('key must be a string when using hmac');
|
||||
}
|
||||
return \hash_hmac($algorithm, $msg, $key, true);
|
||||
case 'openssl':
|
||||
$signature = '';
|
||||
if (!\is_resource($key) && !openssl_pkey_get_private($key)) {
|
||||
throw new DomainException('OpenSSL unable to validate key');
|
||||
}
|
||||
$success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line
|
||||
if (!$success) {
|
||||
throw new DomainException('OpenSSL unable to sign data');
|
||||
}
|
||||
if ($alg === 'ES256' || $alg === 'ES256K') {
|
||||
$signature = self::signatureFromDER($signature, 256);
|
||||
} elseif ($alg === 'ES384') {
|
||||
$signature = self::signatureFromDER($signature, 384);
|
||||
}
|
||||
return $signature;
|
||||
case 'sodium_crypto':
|
||||
if (!\function_exists('sodium_crypto_sign_detached')) {
|
||||
throw new DomainException('libsodium is not available');
|
||||
}
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException('key must be a string when using EdDSA');
|
||||
}
|
||||
try {
|
||||
// The last non-empty line is used as the key.
|
||||
$lines = array_filter(explode("\n", $key));
|
||||
$key = base64_decode((string) end($lines));
|
||||
if (\strlen($key) === 0) {
|
||||
throw new DomainException('Key cannot be empty string');
|
||||
}
|
||||
return sodium_crypto_sign_detached($msg, $key);
|
||||
} catch (Exception $e) {
|
||||
throw new DomainException($e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
throw new DomainException('Algorithm not supported');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a signature with the message, key and method. Not all methods
|
||||
* are symmetric, so we must have a separate verify and sign method.
|
||||
*
|
||||
* @param string $msg The original message (header and body)
|
||||
* @param string $signature The original signature
|
||||
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For Ed*, ES*, HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey
|
||||
* @param string $alg The algorithm
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws DomainException Invalid Algorithm, bad key, or OpenSSL failure
|
||||
*/
|
||||
private static function verify(
|
||||
string $msg,
|
||||
string $signature,
|
||||
$keyMaterial,
|
||||
string $alg
|
||||
): bool {
|
||||
if (empty(static::$supported_algs[$alg])) {
|
||||
throw new DomainException('Algorithm not supported');
|
||||
}
|
||||
|
||||
list($function, $algorithm) = static::$supported_algs[$alg];
|
||||
switch ($function) {
|
||||
case 'openssl':
|
||||
$success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); // @phpstan-ignore-line
|
||||
if ($success === 1) {
|
||||
return true;
|
||||
}
|
||||
if ($success === 0) {
|
||||
return false;
|
||||
}
|
||||
// returns 1 on success, 0 on failure, -1 on error.
|
||||
throw new DomainException(
|
||||
'OpenSSL error: ' . \openssl_error_string()
|
||||
);
|
||||
case 'sodium_crypto':
|
||||
if (!\function_exists('sodium_crypto_sign_verify_detached')) {
|
||||
throw new DomainException('libsodium is not available');
|
||||
}
|
||||
if (!\is_string($keyMaterial)) {
|
||||
throw new InvalidArgumentException('key must be a string when using EdDSA');
|
||||
}
|
||||
try {
|
||||
// The last non-empty line is used as the key.
|
||||
$lines = array_filter(explode("\n", $keyMaterial));
|
||||
$key = base64_decode((string) end($lines));
|
||||
if (\strlen($key) === 0) {
|
||||
throw new DomainException('Key cannot be empty string');
|
||||
}
|
||||
if (\strlen($signature) === 0) {
|
||||
throw new DomainException('Signature cannot be empty string');
|
||||
}
|
||||
return sodium_crypto_sign_verify_detached($signature, $msg, $key);
|
||||
} catch (Exception $e) {
|
||||
throw new DomainException($e->getMessage(), 0, $e);
|
||||
}
|
||||
case 'hash_hmac':
|
||||
default:
|
||||
if (!\is_string($keyMaterial)) {
|
||||
throw new InvalidArgumentException('key must be a string when using hmac');
|
||||
}
|
||||
$hash = \hash_hmac($algorithm, $msg, $keyMaterial, true);
|
||||
return self::constantTimeEquals($hash, $signature);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a JSON string into a PHP object.
|
||||
*
|
||||
* @param string $input JSON string
|
||||
*
|
||||
* @return mixed The decoded JSON string
|
||||
*
|
||||
* @throws DomainException Provided string was invalid JSON
|
||||
*/
|
||||
public static function jsonDecode(string $input)
|
||||
{
|
||||
$obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
|
||||
|
||||
if ($errno = \json_last_error()) {
|
||||
self::handleJsonError($errno);
|
||||
} elseif ($obj === null && $input !== 'null') {
|
||||
throw new DomainException('Null result with non-null input');
|
||||
}
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a PHP array into a JSON string.
|
||||
*
|
||||
* @param array<mixed> $input A PHP array
|
||||
*
|
||||
* @return string JSON representation of the PHP array
|
||||
*
|
||||
* @throws DomainException Provided object could not be encoded to valid JSON
|
||||
*/
|
||||
public static function jsonEncode(array $input): string
|
||||
{
|
||||
$json = \json_encode($input, \JSON_UNESCAPED_SLASHES);
|
||||
if ($errno = \json_last_error()) {
|
||||
self::handleJsonError($errno);
|
||||
} elseif ($json === 'null') {
|
||||
throw new DomainException('Null result with non-null input');
|
||||
}
|
||||
if ($json === false) {
|
||||
throw new DomainException('Provided object could not be encoded to valid JSON');
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a string with URL-safe Base64.
|
||||
*
|
||||
* @param string $input A Base64 encoded string
|
||||
*
|
||||
* @return string A decoded string
|
||||
*
|
||||
* @throws InvalidArgumentException invalid base64 characters
|
||||
*/
|
||||
public static function urlsafeB64Decode(string $input): string
|
||||
{
|
||||
return \base64_decode(self::convertBase64UrlToBase64($input));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string in the base64url (URL-safe Base64) encoding to standard base64.
|
||||
*
|
||||
* @param string $input A Base64 encoded string with URL-safe characters (-_ and no padding)
|
||||
*
|
||||
* @return string A Base64 encoded string with standard characters (+/) and padding (=), when
|
||||
* needed.
|
||||
*
|
||||
* @see https://www.rfc-editor.org/rfc/rfc4648
|
||||
*/
|
||||
public static function convertBase64UrlToBase64(string $input): string
|
||||
{
|
||||
$remainder = \strlen($input) % 4;
|
||||
if ($remainder) {
|
||||
$padlen = 4 - $remainder;
|
||||
$input .= \str_repeat('=', $padlen);
|
||||
}
|
||||
return \strtr($input, '-_', '+/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a string with URL-safe Base64.
|
||||
*
|
||||
* @param string $input The string you want encoded
|
||||
*
|
||||
* @return string The base64 encode of what you passed in
|
||||
*/
|
||||
public static function urlsafeB64Encode(string $input): string
|
||||
{
|
||||
return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if an algorithm has been provided for each Key
|
||||
*
|
||||
* @param Key|ArrayAccess<string,Key>|array<string,Key> $keyOrKeyArray
|
||||
* @param string|null $kid
|
||||
*
|
||||
* @throws UnexpectedValueException
|
||||
*
|
||||
* @return Key
|
||||
*/
|
||||
private static function getKey(
|
||||
$keyOrKeyArray,
|
||||
?string $kid
|
||||
): Key {
|
||||
if ($keyOrKeyArray instanceof Key) {
|
||||
return $keyOrKeyArray;
|
||||
}
|
||||
|
||||
if (empty($kid) && $kid !== '0') {
|
||||
throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
|
||||
}
|
||||
|
||||
if ($keyOrKeyArray instanceof CachedKeySet) {
|
||||
// Skip "isset" check, as this will automatically refresh if not set
|
||||
return $keyOrKeyArray[$kid];
|
||||
}
|
||||
|
||||
if (!isset($keyOrKeyArray[$kid])) {
|
||||
throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
|
||||
}
|
||||
|
||||
return $keyOrKeyArray[$kid];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $left The string of known length to compare against
|
||||
* @param string $right The user-supplied string
|
||||
* @return bool
|
||||
*/
|
||||
public static function constantTimeEquals(string $left, string $right): bool
|
||||
{
|
||||
if (\function_exists('hash_equals')) {
|
||||
return \hash_equals($left, $right);
|
||||
}
|
||||
$len = \min(self::safeStrlen($left), self::safeStrlen($right));
|
||||
|
||||
$status = 0;
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
$status |= (\ord($left[$i]) ^ \ord($right[$i]));
|
||||
}
|
||||
$status |= (self::safeStrlen($left) ^ self::safeStrlen($right));
|
||||
|
||||
return ($status === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a JSON error.
|
||||
*
|
||||
* @param int $errno An error number from json_last_error()
|
||||
*
|
||||
* @throws DomainException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function handleJsonError(int $errno): void
|
||||
{
|
||||
$messages = [
|
||||
JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
|
||||
JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
|
||||
JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
|
||||
JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
|
||||
JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3
|
||||
];
|
||||
throw new DomainException(
|
||||
isset($messages[$errno])
|
||||
? $messages[$errno]
|
||||
: 'Unknown JSON error: ' . $errno
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of bytes in cryptographic strings.
|
||||
*
|
||||
* @param string $str
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function safeStrlen(string $str): int
|
||||
{
|
||||
if (\function_exists('mb_strlen')) {
|
||||
return \mb_strlen($str, '8bit');
|
||||
}
|
||||
return \strlen($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an ECDSA signature to an ASN.1 DER sequence
|
||||
*
|
||||
* @param string $sig The ECDSA signature to convert
|
||||
* @return string The encoded DER object
|
||||
*/
|
||||
private static function signatureToDER(string $sig): string
|
||||
{
|
||||
// Separate the signature into r-value and s-value
|
||||
$length = max(1, (int) (\strlen($sig) / 2));
|
||||
list($r, $s) = \str_split($sig, $length);
|
||||
|
||||
// Trim leading zeros
|
||||
$r = \ltrim($r, "\x00");
|
||||
$s = \ltrim($s, "\x00");
|
||||
|
||||
// Convert r-value and s-value from unsigned big-endian integers to
|
||||
// signed two's complement
|
||||
if (\ord($r[0]) > 0x7f) {
|
||||
$r = "\x00" . $r;
|
||||
}
|
||||
if (\ord($s[0]) > 0x7f) {
|
||||
$s = "\x00" . $s;
|
||||
}
|
||||
|
||||
return self::encodeDER(
|
||||
self::ASN1_SEQUENCE,
|
||||
self::encodeDER(self::ASN1_INTEGER, $r) .
|
||||
self::encodeDER(self::ASN1_INTEGER, $s)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a value into a DER object.
|
||||
*
|
||||
* @param int $type DER tag
|
||||
* @param string $value the value to encode
|
||||
*
|
||||
* @return string the encoded object
|
||||
*/
|
||||
private static function encodeDER(int $type, string $value): string
|
||||
{
|
||||
$tag_header = 0;
|
||||
if ($type === self::ASN1_SEQUENCE) {
|
||||
$tag_header |= 0x20;
|
||||
}
|
||||
|
||||
// Type
|
||||
$der = \chr($tag_header | $type);
|
||||
|
||||
// Length
|
||||
$der .= \chr(\strlen($value));
|
||||
|
||||
return $der . $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes signature from a DER object.
|
||||
*
|
||||
* @param string $der binary signature in DER format
|
||||
* @param int $keySize the number of bits in the key
|
||||
*
|
||||
* @return string the signature
|
||||
*/
|
||||
private static function signatureFromDER(string $der, int $keySize): string
|
||||
{
|
||||
// OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE
|
||||
list($offset, $_) = self::readDER($der);
|
||||
list($offset, $r) = self::readDER($der, $offset);
|
||||
list($offset, $s) = self::readDER($der, $offset);
|
||||
|
||||
// Convert r-value and s-value from signed two's compliment to unsigned
|
||||
// big-endian integers
|
||||
$r = \ltrim($r, "\x00");
|
||||
$s = \ltrim($s, "\x00");
|
||||
|
||||
// Pad out r and s so that they are $keySize bits long
|
||||
$r = \str_pad($r, $keySize / 8, "\x00", STR_PAD_LEFT);
|
||||
$s = \str_pad($s, $keySize / 8, "\x00", STR_PAD_LEFT);
|
||||
|
||||
return $r . $s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads binary DER-encoded data and decodes into a single object
|
||||
*
|
||||
* @param string $der the binary data in DER format
|
||||
* @param int $offset the offset of the data stream containing the object
|
||||
* to decode
|
||||
*
|
||||
* @return array{int, string|null} the new offset and the decoded object
|
||||
*/
|
||||
private static function readDER(string $der, int $offset = 0): array
|
||||
{
|
||||
$pos = $offset;
|
||||
$size = \strlen($der);
|
||||
$constructed = (\ord($der[$pos]) >> 5) & 0x01;
|
||||
$type = \ord($der[$pos++]) & 0x1f;
|
||||
|
||||
// Length
|
||||
$len = \ord($der[$pos++]);
|
||||
if ($len & 0x80) {
|
||||
$n = $len & 0x1f;
|
||||
$len = 0;
|
||||
while ($n-- && $pos < $size) {
|
||||
$len = ($len << 8) | \ord($der[$pos++]);
|
||||
}
|
||||
}
|
||||
|
||||
// Value
|
||||
if ($type === self::ASN1_BIT_STRING) {
|
||||
$pos++; // Skip the first contents octet (padding indicator)
|
||||
$data = \substr($der, $pos, $len - 1);
|
||||
$pos += $len - 1;
|
||||
} elseif (!$constructed) {
|
||||
$data = \substr($der, $pos, $len);
|
||||
$pos += $len;
|
||||
} else {
|
||||
$data = null;
|
||||
}
|
||||
|
||||
return [$pos, $data];
|
||||
}
|
||||
}
|
||||
20
vendor/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
namespace Firebase\JWT;
|
||||
|
||||
interface JWTExceptionWithPayloadInterface
|
||||
{
|
||||
/**
|
||||
* Get the payload that caused this exception.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function getPayload(): object;
|
||||
|
||||
/**
|
||||
* Get the payload that caused this exception.
|
||||
*
|
||||
* @param object $payload
|
||||
* @return void
|
||||
*/
|
||||
public function setPayload(object $payload): void;
|
||||
}
|
||||
55
vendor/firebase/php-jwt/src/Key.php
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Firebase\JWT;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use OpenSSLAsymmetricKey;
|
||||
use OpenSSLCertificate;
|
||||
use TypeError;
|
||||
|
||||
class Key
|
||||
{
|
||||
/**
|
||||
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial
|
||||
* @param string $algorithm
|
||||
*/
|
||||
public function __construct(
|
||||
private $keyMaterial,
|
||||
private string $algorithm
|
||||
) {
|
||||
if (
|
||||
!\is_string($keyMaterial)
|
||||
&& !$keyMaterial instanceof OpenSSLAsymmetricKey
|
||||
&& !$keyMaterial instanceof OpenSSLCertificate
|
||||
&& !\is_resource($keyMaterial)
|
||||
) {
|
||||
throw new TypeError('Key material must be a string, resource, or OpenSSLAsymmetricKey');
|
||||
}
|
||||
|
||||
if (empty($keyMaterial)) {
|
||||
throw new InvalidArgumentException('Key material must not be empty');
|
||||
}
|
||||
|
||||
if (empty($algorithm)) {
|
||||
throw new InvalidArgumentException('Algorithm must not be empty');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the algorithm valid for this key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAlgorithm(): string
|
||||
{
|
||||
return $this->algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate
|
||||
*/
|
||||
public function getKeyMaterial()
|
||||
{
|
||||
return $this->keyMaterial;
|
||||
}
|
||||
}
|
||||
7
vendor/firebase/php-jwt/src/SignatureInvalidException.php
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Firebase\JWT;
|
||||
|
||||
class SignatureInvalidException extends \UnexpectedValueException
|
||||
{
|
||||
}
|
||||
7
vendor/google/auth/.repo-metadata.json
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"language": "php",
|
||||
"distribution_name": "google/auth",
|
||||
"release_level": "stable",
|
||||
"client_documentation": "https://cloud.google.com/php/docs/reference/auth/latest",
|
||||
"library_type": "CORE"
|
||||
}
|
||||
202
vendor/google/auth/COPYING
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2015 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
203
vendor/google/auth/LICENSE
vendored
Normal file
@ -0,0 +1,203 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
368
vendor/google/auth/README.md
vendored
Normal file
@ -0,0 +1,368 @@
|
||||
# Google Auth Library for PHP
|
||||
|
||||
<a href="https://cloud.google.com/php/docs/reference/auth/latest">Reference Docs</a>
|
||||
|
||||
## Description
|
||||
|
||||
This is Google's officially supported PHP client library for using OAuth 2.0
|
||||
authorization and authentication with Google APIs.
|
||||
|
||||
### Installing via Composer
|
||||
|
||||
The recommended way to install the google auth library is through
|
||||
[Composer](http://getcomposer.org).
|
||||
|
||||
```bash
|
||||
# Install Composer
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
```
|
||||
|
||||
Next, run the Composer command to install the latest stable version:
|
||||
|
||||
```bash
|
||||
composer.phar require google/auth
|
||||
```
|
||||
|
||||
## Application Default Credentials
|
||||
|
||||
This library provides an implementation of
|
||||
[Application Default Credentials (ADC)][application default credentials] for PHP.
|
||||
|
||||
Application Default Credentials provides a simple way to get authorization
|
||||
credentials for use in calling Google APIs, and is
|
||||
the recommended approach to authorize calls to Cloud APIs.
|
||||
|
||||
**Important**: If you accept a credential configuration (credential JSON/File/Stream) from an
|
||||
external source for authentication to Google Cloud Platform, you must validate it before providing
|
||||
it to any Google API or library. Providing an unvalidated credential configuration to Google APIs
|
||||
can compromise the security of your systems and data. For more information, refer to
|
||||
[Validate credential configurations from external sources][externally-sourced-credentials].
|
||||
|
||||
[externally-sourced-credentials]: https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
|
||||
|
||||
### Set up ADC
|
||||
|
||||
To use ADC, you must set it up by providing credentials.
|
||||
How you set up ADC depends on the environment where your code is running,
|
||||
and whether you are running code in a test or production environment.
|
||||
|
||||
For more information, see [Set up Application Default Credentials][set-up-adc].
|
||||
|
||||
### Enable the API you want to use
|
||||
|
||||
Before making your API call, you must be sure the API you're calling has been
|
||||
enabled. Go to **APIs & Auth** > **APIs** in the
|
||||
[Google Developers Console][developer console] and enable the APIs you'd like to
|
||||
call. For the example below, you must enable the `Drive API`.
|
||||
|
||||
### Call the APIs
|
||||
|
||||
As long as you update the environment variable below to point to *your* JSON
|
||||
credentials file, the following code should output a list of your Drive files.
|
||||
|
||||
```php
|
||||
use Google\Auth\ApplicationDefaultCredentials;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
|
||||
// specify the path to your application credentials
|
||||
putenv('GOOGLE_APPLICATION_CREDENTIALS=/path/to/my/credentials.json');
|
||||
|
||||
// define the scopes for your API call
|
||||
$scopes = ['https://www.googleapis.com/auth/drive.readonly'];
|
||||
|
||||
// create middleware
|
||||
$middleware = ApplicationDefaultCredentials::getMiddleware($scopes);
|
||||
$stack = HandlerStack::create();
|
||||
$stack->push($middleware);
|
||||
|
||||
// create the HTTP client
|
||||
$client = new Client([
|
||||
'handler' => $stack,
|
||||
'base_uri' => 'https://www.googleapis.com',
|
||||
'auth' => 'google_auth' // authorize all requests
|
||||
]);
|
||||
|
||||
// make the request
|
||||
$response = $client->get('drive/v2/files');
|
||||
|
||||
// show the result!
|
||||
print_r((string) $response->getBody());
|
||||
```
|
||||
|
||||
##### Guzzle 5 Compatibility
|
||||
|
||||
If you are using [Guzzle 5][Guzzle 5], replace the `create middleware` and
|
||||
`create the HTTP Client` steps with the following:
|
||||
|
||||
```php
|
||||
// create the HTTP client
|
||||
$client = new Client([
|
||||
'base_url' => 'https://www.googleapis.com',
|
||||
'auth' => 'google_auth' // authorize all requests
|
||||
]);
|
||||
|
||||
// create subscriber
|
||||
$subscriber = ApplicationDefaultCredentials::getSubscriber($scopes);
|
||||
$client->getEmitter()->attach($subscriber);
|
||||
```
|
||||
|
||||
#### Call using an ID Token
|
||||
If your application is running behind Cloud Run, or using Cloud Identity-Aware
|
||||
Proxy (IAP), you will need to fetch an ID token to access your application. For
|
||||
this, use the static method `getIdTokenMiddleware` on
|
||||
`ApplicationDefaultCredentials`.
|
||||
|
||||
```php
|
||||
use Google\Auth\ApplicationDefaultCredentials;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
|
||||
// specify the path to your application credentials
|
||||
putenv('GOOGLE_APPLICATION_CREDENTIALS=/path/to/my/credentials.json');
|
||||
|
||||
// Provide the ID token audience. This can be a Client ID associated with an IAP application,
|
||||
// Or the URL associated with a CloudRun App
|
||||
// $targetAudience = 'IAP_CLIENT_ID.apps.googleusercontent.com';
|
||||
// $targetAudience = 'https://service-1234-uc.a.run.app';
|
||||
$targetAudience = 'YOUR_ID_TOKEN_AUDIENCE';
|
||||
|
||||
// create middleware
|
||||
$middleware = ApplicationDefaultCredentials::getIdTokenMiddleware($targetAudience);
|
||||
$stack = HandlerStack::create();
|
||||
$stack->push($middleware);
|
||||
|
||||
// create the HTTP client
|
||||
$client = new Client([
|
||||
'handler' => $stack,
|
||||
'auth' => 'google_auth',
|
||||
// Cloud Run, IAP, or custom resource URL
|
||||
'base_uri' => 'https://YOUR_PROTECTED_RESOURCE',
|
||||
]);
|
||||
|
||||
// make the request
|
||||
$response = $client->get('/');
|
||||
|
||||
// show the result!
|
||||
print_r((string) $response->getBody());
|
||||
```
|
||||
|
||||
For invoking Cloud Run services, your service account will need the
|
||||
[`Cloud Run Invoker`](https://cloud.google.com/run/docs/authenticating/service-to-service)
|
||||
IAM permission.
|
||||
|
||||
For invoking Cloud Identity-Aware Proxy, you will need to pass the Client ID
|
||||
used when you set up your protected resource as the target audience. See how to
|
||||
[secure your IAP app with signed headers](https://cloud.google.com/iap/docs/signed-headers-howto).
|
||||
|
||||
#### Call using a specific JSON key
|
||||
If you want to use a specific JSON key instead of using `GOOGLE_APPLICATION_CREDENTIALS` environment variable, you can
|
||||
do this:
|
||||
|
||||
```php
|
||||
use Google\Auth\CredentialsLoader;
|
||||
use Google\Auth\Middleware\AuthTokenMiddleware;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
|
||||
// Define the Google Application Credentials array
|
||||
$jsonKey = ['key' => 'value'];
|
||||
|
||||
// define the scopes for your API call
|
||||
$scopes = ['https://www.googleapis.com/auth/drive.readonly'];
|
||||
|
||||
// Load credentials from JSON containing service account credentials.
|
||||
$creds = new ServiceAccountCredentials($scopes, $jsonKey),
|
||||
|
||||
// For other credentials types, create those classes explicitly using the
|
||||
// "type" field in the JSON key, for example:
|
||||
$creds = match ($jsonKey['type']) {
|
||||
'service_account' => new ServiceAccountCredentials($scope, $jsonKey),
|
||||
'authorized_user' => new UserRefreshCredentials($scope, $jsonKey),
|
||||
default => throw new InvalidArgumentException('This application only supports service account and user account credentials'),
|
||||
};
|
||||
|
||||
// optional caching
|
||||
$creds = new FetchAuthTokenCache($creds, $cacheConfig, $cache);
|
||||
|
||||
// create middleware
|
||||
$middleware = new AuthTokenMiddleware($creds);
|
||||
$stack = HandlerStack::create();
|
||||
$stack->push($middleware);
|
||||
|
||||
// create the HTTP client
|
||||
$client = new Client([
|
||||
'handler' => $stack,
|
||||
'base_uri' => 'https://www.googleapis.com',
|
||||
'auth' => 'google_auth' // authorize all requests
|
||||
]);
|
||||
|
||||
// make the request
|
||||
$response = $client->get('drive/v2/files');
|
||||
|
||||
// show the result!
|
||||
print_r((string) $response->getBody());
|
||||
|
||||
```
|
||||
|
||||
#### Call using Proxy-Authorization Header
|
||||
If your application is behind a proxy such as [Google Cloud IAP][iap-proxy-header],
|
||||
and your application occupies the `Authorization` request header,
|
||||
you can include the ID token in a `Proxy-Authorization: Bearer`
|
||||
header instead. If a valid ID token is found in a `Proxy-Authorization` header,
|
||||
IAP authorizes the request with it. After authorizing the request, IAP passes
|
||||
the Authorization header to your application without processing the content.
|
||||
For this, use the static method `getProxyIdTokenMiddleware` on
|
||||
`ApplicationDefaultCredentials`.
|
||||
|
||||
```php
|
||||
use Google\Auth\ApplicationDefaultCredentials;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
|
||||
// specify the path to your application credentials
|
||||
putenv('GOOGLE_APPLICATION_CREDENTIALS=/path/to/my/credentials.json');
|
||||
|
||||
// Provide the ID token audience. This can be a Client ID associated with an IAP application
|
||||
// $targetAudience = 'IAP_CLIENT_ID.apps.googleusercontent.com';
|
||||
$targetAudience = 'YOUR_ID_TOKEN_AUDIENCE';
|
||||
|
||||
// create middleware
|
||||
$middleware = ApplicationDefaultCredentials::getProxyIdTokenMiddleware($targetAudience);
|
||||
$stack = HandlerStack::create();
|
||||
$stack->push($middleware);
|
||||
|
||||
// create the HTTP client
|
||||
$client = new Client([
|
||||
'handler' => $stack,
|
||||
'auth' => ['username', 'pass'], // auth option handled by your application
|
||||
'proxy_auth' => 'google_auth',
|
||||
]);
|
||||
|
||||
// make the request
|
||||
$response = $client->get('/');
|
||||
|
||||
// show the result!
|
||||
print_r((string) $response->getBody());
|
||||
```
|
||||
|
||||
[iap-proxy-header]: https://cloud.google.com/iap/docs/authentication-howto#authenticating_from_proxy-authorization_header
|
||||
|
||||
#### External credentials (Workload identity federation)
|
||||
|
||||
Using workload identity federation, your application can access Google Cloud resources from Amazon Web Services (AWS),
|
||||
Microsoft Azure or any identity provider that supports OpenID Connect (OIDC).
|
||||
|
||||
Traditionally, applications running outside Google Cloud have used service account keys to access Google Cloud
|
||||
resources. Using identity federation, you can allow your workload to impersonate a service account. This lets you access
|
||||
Google Cloud resources directly, eliminating the maintenance and security burden associated with service account keys.
|
||||
|
||||
Follow the detailed instructions on how to
|
||||
[Configure Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds).
|
||||
|
||||
#### Verifying JWTs
|
||||
|
||||
If you are [using Google ID tokens to authenticate users][google-id-tokens], use
|
||||
the `Google\Auth\AccessToken` class to verify the ID token:
|
||||
|
||||
```php
|
||||
use Google\Auth\AccessToken;
|
||||
|
||||
$auth = new AccessToken();
|
||||
$auth->verify($idToken);
|
||||
```
|
||||
|
||||
If your app is running behind [Google Identity-Aware Proxy][iap-id-tokens]
|
||||
(IAP), you can verify the ID token coming from the IAP server by pointing to the
|
||||
appropriate certificate URL for IAP. This is because IAP signs the ID
|
||||
tokens with a different key than the Google Identity service:
|
||||
|
||||
```php
|
||||
use Google\Auth\AccessToken;
|
||||
|
||||
$auth = new AccessToken();
|
||||
$auth->verify($idToken, [
|
||||
'certsLocation' => AccessToken::IAP_CERT_URL
|
||||
]);
|
||||
```
|
||||
|
||||
[google-id-tokens]: https://developers.google.com/identity/sign-in/web/backend-auth
|
||||
[iap-id-tokens]: https://cloud.google.com/iap/docs/signed-headers-howto
|
||||
|
||||
## Caching
|
||||
Caching is enabled by passing a PSR-6 `CacheItemPoolInterface`
|
||||
instance to the constructor when instantiating the credentials.
|
||||
|
||||
We offer some caching classes out of the box under the `Google\Auth\Cache` namespace.
|
||||
|
||||
```php
|
||||
use Google\Auth\ApplicationDefaultCredentials;
|
||||
use Google\Auth\Cache\MemoryCacheItemPool;
|
||||
|
||||
// Cache Instance
|
||||
$memoryCache = new MemoryCacheItemPool;
|
||||
|
||||
// Get the credentials
|
||||
// From here, the credentials will cache the access token
|
||||
$middleware = ApplicationDefaultCredentials::getCredentials($scope, cache: $memoryCache);
|
||||
```
|
||||
|
||||
### FileSystemCacheItemPool Cache
|
||||
The `FileSystemCacheItemPool` class is a `PSR-6` compliant cache that stores its
|
||||
serialized objects on disk, caching data between processes and making it possible
|
||||
to use data between different requests.
|
||||
|
||||
```php
|
||||
use Google\Auth\Cache\FileSystemCacheItemPool;
|
||||
use Google\Auth\ApplicationDefaultCredentials;
|
||||
|
||||
// Create a Cache pool instance
|
||||
$cache = new FileSystemCacheItemPool(__DIR__ . '/cache');
|
||||
|
||||
// Pass your Cache to the Auth Library
|
||||
$credentials = ApplicationDefaultCredentials::getCredentials($scope, cache: $cache);
|
||||
|
||||
// This token will be cached and be able to be used for the next request
|
||||
$token = $credentials->fetchAuthToken();
|
||||
```
|
||||
|
||||
### Integrating with a third party cache
|
||||
You can use a third party that follows the `PSR-6` interface of your choice.
|
||||
|
||||
```php
|
||||
// run "composer require symfony/cache"
|
||||
use Google\Auth\ApplicationDefaultCredentials;
|
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
|
||||
// Create the cache instance
|
||||
$filesystemCache = new FilesystemAdapter();
|
||||
|
||||
// Create Get the credentials
|
||||
$credentials = ApplicationDefaultCredentials::getCredentials($targetAudience, cache: $filesystemCache);
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This library is licensed under Apache 2.0. Full license text is
|
||||
available in [COPYING][copying].
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING][contributing].
|
||||
|
||||
## Support
|
||||
|
||||
Please
|
||||
[report bugs at the project on Github](https://github.com/google/google-auth-library-php/issues). Don't
|
||||
hesitate to
|
||||
[ask questions](http://stackoverflow.com/questions/tagged/google-auth-library-php)
|
||||
about the client or APIs on [StackOverflow](http://stackoverflow.com).
|
||||
|
||||
[google-apis-php-client]: https://github.com/google/google-api-php-client
|
||||
[application default credentials]: https://cloud.google.com/docs/authentication/application-default-credentials
|
||||
[contributing]: https://github.com/google/google-auth-library-php/tree/main/.github/CONTRIBUTING.md
|
||||
[copying]: https://github.com/google/google-auth-library-php/tree/main/COPYING
|
||||
[Guzzle]: https://github.com/guzzle/guzzle
|
||||
[Guzzle 5]: http://docs.guzzlephp.org/en/5.3
|
||||
[developer console]: https://console.developers.google.com
|
||||
[set-up-adc]: https://cloud.google.com/docs/authentication/provide-credentials-adc
|
||||
7
vendor/google/auth/SECURITY.md
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
# Security Policy
|
||||
|
||||
To report a security issue, please use [g.co/vulnz](https://g.co/vulnz).
|
||||
|
||||
The Google Security Team will respond within 5 working days of your report on g.co/vulnz.
|
||||
|
||||
We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue.
|
||||
1
vendor/google/auth/VERSION
vendored
Normal file
@ -0,0 +1 @@
|
||||
1.48.1
|
||||
44
vendor/google/auth/composer.json
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "google/auth",
|
||||
"type": "library",
|
||||
"description": "Google Auth Library for PHP",
|
||||
"keywords": ["google", "oauth2", "authentication"],
|
||||
"homepage": "https://github.com/google/google-auth-library-php",
|
||||
"license": "Apache-2.0",
|
||||
"support": {
|
||||
"docs": "https://cloud.google.com/php/docs/reference/auth/latest"
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"firebase/php-jwt": "^6.0",
|
||||
"guzzlehttp/guzzle": "^7.4.5",
|
||||
"guzzlehttp/psr7": "^2.4.5",
|
||||
"psr/http-message": "^1.1||^2.0",
|
||||
"psr/cache": "^2.0||^3.0",
|
||||
"psr/log": "^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"guzzlehttp/promises": "^2.0",
|
||||
"squizlabs/php_codesniffer": "^4.0",
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"phpspec/prophecy-phpunit": "^2.1",
|
||||
"sebastian/comparator": ">=1.2.3",
|
||||
"phpseclib/phpseclib": "^3.0.35",
|
||||
"kelvinmo/simplejwt": "0.7.1",
|
||||
"webmozart/assert": "^1.11",
|
||||
"symfony/process": "^6.0||^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"phpseclib/phpseclib": "May be used in place of OpenSSL for signing strings or for token management. Please require version ^2."
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Google\\Auth\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Google\\Auth\\Tests\\": "tests"
|
||||
}
|
||||
}
|
||||
}
|
||||
473
vendor/google/auth/src/AccessToken.php
vendored
Normal file
@ -0,0 +1,473 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth;
|
||||
|
||||
use DateTime;
|
||||
use Firebase\JWT\ExpiredException;
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
use Firebase\JWT\SignatureInvalidException;
|
||||
use Google\Auth\Cache\MemoryCacheItemPool;
|
||||
use Google\Auth\HttpHandler\HttpClientCache;
|
||||
use Google\Auth\HttpHandler\HttpHandlerFactory;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use GuzzleHttp\Psr7\Utils;
|
||||
use InvalidArgumentException;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
use phpseclib3\Crypt\RSA;
|
||||
use phpseclib3\Math\BigInteger;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use RuntimeException;
|
||||
use SimpleJWT\InvalidTokenException;
|
||||
use SimpleJWT\JWT as SimpleJWT;
|
||||
use SimpleJWT\Keys\KeyFactory;
|
||||
use SimpleJWT\Keys\KeySet;
|
||||
use TypeError;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* Wrapper around Google Access Tokens which provides convenience functions.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
class AccessToken
|
||||
{
|
||||
const FEDERATED_SIGNON_CERT_URL = 'https://www.googleapis.com/oauth2/v3/certs';
|
||||
const IAP_CERT_URL = 'https://www.gstatic.com/iap/verify/public_key-jwk';
|
||||
const IAP_ISSUER = 'https://cloud.google.com/iap';
|
||||
const OAUTH2_ISSUER = 'accounts.google.com';
|
||||
const OAUTH2_ISSUER_HTTPS = 'https://accounts.google.com';
|
||||
const OAUTH2_REVOKE_URI = 'https://oauth2.googleapis.com/revoke';
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
private $httpHandler;
|
||||
|
||||
/**
|
||||
* @var CacheItemPoolInterface
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @param callable|null $httpHandler [optional] An HTTP Handler to deliver PSR-7 requests.
|
||||
* @param CacheItemPoolInterface|null $cache [optional] A PSR-6 compatible cache implementation.
|
||||
*/
|
||||
public function __construct(
|
||||
?callable $httpHandler = null,
|
||||
?CacheItemPoolInterface $cache = null
|
||||
) {
|
||||
$this->httpHandler = $httpHandler
|
||||
?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
|
||||
$this->cache = $cache ?: new MemoryCacheItemPool();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies an id token and returns the authenticated apiLoginTicket.
|
||||
* Throws an exception if the id token is not valid.
|
||||
* The audience parameter can be used to control which id tokens are
|
||||
* accepted. By default, the id token must have been issued to this OAuth2 client.
|
||||
*
|
||||
* @param string $token The JSON Web Token to be verified.
|
||||
* @param array<mixed> $options [optional] {
|
||||
* Configuration options.
|
||||
* @type string $audience The indended recipient of the token.
|
||||
* @type string $issuer The intended issuer of the token.
|
||||
* @type string $cacheKey The cache key of the cached certs. Defaults to
|
||||
* the sha1 of $certsLocation if provided, otherwise is set to
|
||||
* "federated_signon_certs_v3".
|
||||
* @type string $certsLocation The location (remote or local) from which
|
||||
* to retrieve certificates, if not cached. This value should only be
|
||||
* provided in limited circumstances in which you are sure of the
|
||||
* behavior.
|
||||
* @type bool $throwException Whether the function should throw an
|
||||
* exception if the verification fails. This is useful for
|
||||
* determining the reason verification failed.
|
||||
* }
|
||||
* @return array<mixed>|false the token payload, if successful, or false if not.
|
||||
* @throws InvalidArgumentException If certs could not be retrieved from a local file.
|
||||
* @throws InvalidArgumentException If received certs are in an invalid format.
|
||||
* @throws InvalidArgumentException If the cert alg is not supported.
|
||||
* @throws RuntimeException If certs could not be retrieved from a remote location.
|
||||
* @throws UnexpectedValueException If the token issuer does not match.
|
||||
* @throws UnexpectedValueException If the token audience does not match.
|
||||
*/
|
||||
public function verify($token, array $options = [])
|
||||
{
|
||||
$audience = $options['audience'] ?? null;
|
||||
$issuer = $options['issuer'] ?? null;
|
||||
$certsLocation = $options['certsLocation'] ?? self::FEDERATED_SIGNON_CERT_URL;
|
||||
$cacheKey = $options['cacheKey'] ?? $this->getCacheKeyFromCertLocation($certsLocation);
|
||||
$throwException = $options['throwException'] ?? false; // for backwards compatibility
|
||||
|
||||
// Check signature against each available cert.
|
||||
$certs = $this->getCerts($certsLocation, $cacheKey, $options);
|
||||
$alg = $this->determineAlg($certs);
|
||||
if (!in_array($alg, ['RS256', 'ES256'])) {
|
||||
throw new InvalidArgumentException(
|
||||
'unrecognized "alg" in certs, expected ES256 or RS256'
|
||||
);
|
||||
}
|
||||
try {
|
||||
if ($alg == 'RS256') {
|
||||
return $this->verifyRs256($token, $certs, $audience, $issuer);
|
||||
}
|
||||
return $this->verifyEs256($token, $certs, $audience, $issuer);
|
||||
} catch (ExpiredException $e) { // firebase/php-jwt 5+
|
||||
} catch (SignatureInvalidException $e) { // firebase/php-jwt 5+
|
||||
} catch (InvalidTokenException $e) { // simplejwt
|
||||
} catch (InvalidArgumentException $e) {
|
||||
} catch (UnexpectedValueException $e) {
|
||||
}
|
||||
|
||||
if ($throwException) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the expected algorithm to verify by looking at the "alg" key
|
||||
* of the provided certs.
|
||||
*
|
||||
* @param array<mixed> $certs Certificate array according to the JWK spec (see
|
||||
* https://tools.ietf.org/html/rfc7517).
|
||||
* @return string The expected algorithm, such as "ES256" or "RS256".
|
||||
*/
|
||||
private function determineAlg(array $certs)
|
||||
{
|
||||
$alg = null;
|
||||
foreach ($certs as $cert) {
|
||||
if (empty($cert['alg'])) {
|
||||
throw new InvalidArgumentException(
|
||||
'certs expects "alg" to be set'
|
||||
);
|
||||
}
|
||||
$alg = $alg ?: $cert['alg'];
|
||||
|
||||
if ($alg != $cert['alg']) {
|
||||
throw new InvalidArgumentException(
|
||||
'More than one alg detected in certs'
|
||||
);
|
||||
}
|
||||
}
|
||||
return $alg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies an ES256-signed JWT.
|
||||
*
|
||||
* @param string $token The JSON Web Token to be verified.
|
||||
* @param array<mixed> $certs Certificate array according to the JWK spec (see
|
||||
* https://tools.ietf.org/html/rfc7517).
|
||||
* @param string|null $audience If set, returns false if the provided
|
||||
* audience does not match the "aud" claim on the JWT.
|
||||
* @param string|null $issuer If set, returns false if the provided
|
||||
* issuer does not match the "iss" claim on the JWT.
|
||||
* @return array<mixed> the token payload, if successful, or false if not.
|
||||
*/
|
||||
private function verifyEs256($token, array $certs, $audience = null, $issuer = null)
|
||||
{
|
||||
$this->checkSimpleJwt();
|
||||
|
||||
$jwkset = new KeySet();
|
||||
foreach ($certs as $cert) {
|
||||
$jwkset->add(KeyFactory::create($cert, 'php'));
|
||||
}
|
||||
|
||||
// Validate the signature using the key set and ES256 algorithm.
|
||||
$jwt = $this->callSimpleJwtDecode([$token, $jwkset, 'ES256']);
|
||||
$payload = $jwt->getClaims();
|
||||
|
||||
if ($audience) {
|
||||
if (!isset($payload['aud']) || $payload['aud'] != $audience) {
|
||||
throw new UnexpectedValueException('Audience does not match');
|
||||
}
|
||||
}
|
||||
|
||||
// @see https://cloud.google.com/iap/docs/signed-headers-howto#verifying_the_jwt_payload
|
||||
$issuer = $issuer ?: self::IAP_ISSUER;
|
||||
if (!isset($payload['iss']) || $payload['iss'] !== $issuer) {
|
||||
throw new UnexpectedValueException('Issuer does not match');
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies an RS256-signed JWT.
|
||||
*
|
||||
* @param string $token The JSON Web Token to be verified.
|
||||
* @param array<mixed> $certs Certificate array according to the JWK spec (see
|
||||
* https://tools.ietf.org/html/rfc7517).
|
||||
* @param string|null $audience If set, returns false if the provided
|
||||
* audience does not match the "aud" claim on the JWT.
|
||||
* @param string|null $issuer If set, returns false if the provided
|
||||
* issuer does not match the "iss" claim on the JWT.
|
||||
* @return array<mixed> the token payload, if successful, or false if not.
|
||||
*/
|
||||
private function verifyRs256($token, array $certs, $audience = null, $issuer = null)
|
||||
{
|
||||
$this->checkAndInitializePhpsec();
|
||||
$keys = [];
|
||||
foreach ($certs as $cert) {
|
||||
if (empty($cert['kid'])) {
|
||||
throw new InvalidArgumentException(
|
||||
'certs expects "kid" to be set'
|
||||
);
|
||||
}
|
||||
if (empty($cert['n']) || empty($cert['e'])) {
|
||||
throw new InvalidArgumentException(
|
||||
'RSA certs expects "n" and "e" to be set'
|
||||
);
|
||||
}
|
||||
$publicKey = $this->loadPhpsecPublicKey($cert['n'], $cert['e']);
|
||||
|
||||
// create an array of key IDs to certs for the JWT library
|
||||
$keys[$cert['kid']] = new Key($publicKey, 'RS256');
|
||||
}
|
||||
|
||||
$payload = $this->callJwtStatic('decode', [
|
||||
$token,
|
||||
$keys,
|
||||
]);
|
||||
|
||||
if ($audience) {
|
||||
if (!property_exists($payload, 'aud') || $payload->aud != $audience) {
|
||||
throw new UnexpectedValueException('Audience does not match');
|
||||
}
|
||||
}
|
||||
|
||||
// support HTTP and HTTPS issuers
|
||||
// @see https://developers.google.com/identity/sign-in/web/backend-auth
|
||||
$issuers = $issuer ? [$issuer] : [self::OAUTH2_ISSUER, self::OAUTH2_ISSUER_HTTPS];
|
||||
if (!isset($payload->iss) || !in_array($payload->iss, $issuers)) {
|
||||
throw new UnexpectedValueException('Issuer does not match');
|
||||
}
|
||||
|
||||
return (array) $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke an OAuth2 access token or refresh token. This method will revoke the current access
|
||||
* token, if a token isn't provided.
|
||||
*
|
||||
* @param string|array<mixed> $token The token (access token or a refresh token) that should be revoked.
|
||||
* @param array<mixed> $options [optional] Configuration options.
|
||||
* @return bool Returns True if the revocation was successful, otherwise False.
|
||||
*/
|
||||
public function revoke($token, array $options = [])
|
||||
{
|
||||
if (is_array($token)) {
|
||||
if (isset($token['refresh_token'])) {
|
||||
$token = $token['refresh_token'];
|
||||
} else {
|
||||
$token = $token['access_token'];
|
||||
}
|
||||
}
|
||||
|
||||
$body = Utils::streamFor(http_build_query(['token' => $token]));
|
||||
$request = new Request('POST', self::OAUTH2_REVOKE_URI, [
|
||||
'Cache-Control' => 'no-store',
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
], $body);
|
||||
|
||||
$httpHandler = $this->httpHandler;
|
||||
|
||||
$response = $httpHandler($request, $options);
|
||||
|
||||
return $response->getStatusCode() == 200;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets federated sign-on certificates to use for verifying identity tokens.
|
||||
* Returns certs as array structure, where keys are key ids, and values
|
||||
* are PEM encoded certificates.
|
||||
*
|
||||
* @param string $location The location from which to retrieve certs.
|
||||
* @param string $cacheKey The key under which to cache the retrieved certs.
|
||||
* @param array<mixed> $options [optional] Configuration options.
|
||||
* @return array<mixed>
|
||||
* @throws InvalidArgumentException If received certs are in an invalid format.
|
||||
*/
|
||||
private function getCerts($location, $cacheKey, array $options = [])
|
||||
{
|
||||
$cacheItem = $this->cache->getItem($cacheKey);
|
||||
$certs = $cacheItem ? $cacheItem->get() : null;
|
||||
|
||||
$expireTime = null;
|
||||
if (!$certs) {
|
||||
list($certs, $expireTime) = $this->retrieveCertsFromLocation($location, $options);
|
||||
}
|
||||
|
||||
if (!isset($certs['keys'])) {
|
||||
if ($location !== self::IAP_CERT_URL) {
|
||||
throw new InvalidArgumentException(
|
||||
'federated sign-on certs expects "keys" to be set'
|
||||
);
|
||||
}
|
||||
throw new InvalidArgumentException(
|
||||
'certs expects "keys" to be set'
|
||||
);
|
||||
}
|
||||
|
||||
// Push caching off until after verifying certs are in a valid format.
|
||||
// Don't want to cache bad data.
|
||||
if ($expireTime) {
|
||||
$cacheItem->expiresAt(new DateTime($expireTime));
|
||||
$cacheItem->set($certs);
|
||||
$this->cache->save($cacheItem);
|
||||
}
|
||||
|
||||
return $certs['keys'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve and cache a certificates file.
|
||||
*
|
||||
* @param string $url location
|
||||
* @param array<mixed> $options [optional] Configuration options.
|
||||
* @return array{array<mixed>, string}
|
||||
* @throws InvalidArgumentException If certs could not be retrieved from a local file.
|
||||
* @throws RuntimeException If certs could not be retrieved from a remote location.
|
||||
*/
|
||||
private function retrieveCertsFromLocation($url, array $options = [])
|
||||
{
|
||||
// If we're retrieving a local file, just grab it.
|
||||
$expireTime = '+1 hour';
|
||||
if (strpos($url, 'http') !== 0) {
|
||||
if (!file_exists($url)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Failed to retrieve verification certificates from path: %s.',
|
||||
$url
|
||||
));
|
||||
}
|
||||
|
||||
return [
|
||||
json_decode((string) file_get_contents($url), true),
|
||||
$expireTime
|
||||
];
|
||||
}
|
||||
|
||||
$httpHandler = $this->httpHandler;
|
||||
$response = $httpHandler(new Request('GET', $url), $options);
|
||||
|
||||
if ($response->getStatusCode() == 200) {
|
||||
if ($cacheControl = $response->getHeaderLine('Cache-Control')) {
|
||||
array_map(function ($value) use (&$expireTime) {
|
||||
list($key, $value) = explode('=', $value) + [null, null];
|
||||
if (trim($key) == 'max-age') {
|
||||
$expireTime = '+' . $value . ' seconds';
|
||||
}
|
||||
}, explode(',', $cacheControl));
|
||||
}
|
||||
return [
|
||||
json_decode((string) $response->getBody(), true),
|
||||
$expireTime
|
||||
];
|
||||
}
|
||||
|
||||
throw new RuntimeException(sprintf(
|
||||
'Failed to retrieve verification certificates: "%s".',
|
||||
$response->getBody()->getContents()
|
||||
), $response->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function checkAndInitializePhpsec()
|
||||
{
|
||||
if (!class_exists(RSA::class)) {
|
||||
throw new RuntimeException('Please require phpseclib/phpseclib v3 to use this utility.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws TypeError If the key cannot be initialized to a string.
|
||||
*/
|
||||
private function loadPhpsecPublicKey(string $modulus, string $exponent): string
|
||||
{
|
||||
$key = PublicKeyLoader::load([
|
||||
'n' => new BigInteger($this->callJwtStatic('urlsafeB64Decode', [
|
||||
$modulus,
|
||||
]), 256),
|
||||
'e' => new BigInteger($this->callJwtStatic('urlsafeB64Decode', [
|
||||
$exponent
|
||||
]), 256),
|
||||
]);
|
||||
$formattedPublicKey = $key->toString('PKCS8');
|
||||
if (!is_string($formattedPublicKey)) {
|
||||
throw new TypeError('Failed to initialize the key');
|
||||
}
|
||||
return $formattedPublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function checkSimpleJwt()
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
if (!class_exists(SimpleJwt::class)) {
|
||||
throw new RuntimeException('Please require kelvinmo/simplejwt ^0.2 to use this utility.');
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a hook to mock calls to the JWT static methods.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array<mixed> $args
|
||||
* @return mixed
|
||||
*/
|
||||
protected function callJwtStatic($method, array $args = [])
|
||||
{
|
||||
return call_user_func_array([JWT::class, $method], $args); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a hook to mock calls to the JWT static methods.
|
||||
*
|
||||
* @param array<mixed> $args
|
||||
* @return mixed
|
||||
*/
|
||||
protected function callSimpleJwtDecode(array $args = [])
|
||||
{
|
||||
return call_user_func_array([SimpleJwt::class, 'decode'], $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a cache key based on the cert location using sha1 with the
|
||||
* exception of using "federated_signon_certs_v3" to preserve BC.
|
||||
*
|
||||
* @param string $certsLocation
|
||||
* @return string
|
||||
*/
|
||||
private function getCacheKeyFromCertLocation($certsLocation)
|
||||
{
|
||||
$key = $certsLocation === self::FEDERATED_SIGNON_CERT_URL
|
||||
? 'federated_signon_certs_v3'
|
||||
: sha1($certsLocation);
|
||||
|
||||
return 'google_auth_certs_cache|' . $key;
|
||||
}
|
||||
}
|
||||
391
vendor/google/auth/src/ApplicationDefaultCredentials.php
vendored
Normal file
@ -0,0 +1,391 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth;
|
||||
|
||||
use DomainException;
|
||||
use Google\Auth\Credentials\AppIdentityCredentials;
|
||||
use Google\Auth\Credentials\GCECredentials;
|
||||
use Google\Auth\Credentials\ImpersonatedServiceAccountCredentials;
|
||||
use Google\Auth\Credentials\ServiceAccountCredentials;
|
||||
use Google\Auth\Credentials\UserRefreshCredentials;
|
||||
use Google\Auth\HttpHandler\HttpClientCache;
|
||||
use Google\Auth\HttpHandler\HttpHandlerFactory;
|
||||
use Google\Auth\Logging\StdOutLogger;
|
||||
use Google\Auth\Middleware\AuthTokenMiddleware;
|
||||
use Google\Auth\Middleware\ProxyAuthTokenMiddleware;
|
||||
use Google\Auth\Subscriber\AuthTokenSubscriber;
|
||||
use GuzzleHttp\Client;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* ApplicationDefaultCredentials obtains the default credentials for
|
||||
* authorizing a request to a Google service.
|
||||
*
|
||||
* Application Default Credentials are described here:
|
||||
* https://developers.google.com/accounts/docs/application-default-credentials
|
||||
*
|
||||
* This class implements the search for the application default credentials as
|
||||
* described in the link.
|
||||
*
|
||||
* It provides three factory methods:
|
||||
* - #get returns the computed credentials object
|
||||
* - #getSubscriber returns an AuthTokenSubscriber built from the credentials object
|
||||
* - #getMiddleware returns an AuthTokenMiddleware built from the credentials object
|
||||
*
|
||||
* This allows it to be used as follows with GuzzleHttp\Client:
|
||||
*
|
||||
* ```
|
||||
* use Google\Auth\ApplicationDefaultCredentials;
|
||||
* use GuzzleHttp\Client;
|
||||
* use GuzzleHttp\HandlerStack;
|
||||
*
|
||||
* $middleware = ApplicationDefaultCredentials::getMiddleware(
|
||||
* 'https://www.googleapis.com/auth/taskqueue'
|
||||
* );
|
||||
* $stack = HandlerStack::create();
|
||||
* $stack->push($middleware);
|
||||
*
|
||||
* $client = new Client([
|
||||
* 'handler' => $stack,
|
||||
* 'base_uri' => 'https://www.googleapis.com/taskqueue/v1beta2/projects/',
|
||||
* 'auth' => 'google_auth' // authorize all requests
|
||||
* ]);
|
||||
*
|
||||
* $res = $client->get('myproject/taskqueues/myqueue');
|
||||
* ```
|
||||
*/
|
||||
class ApplicationDefaultCredentials
|
||||
{
|
||||
private const SDK_DEBUG_ENV_VAR = 'GOOGLE_SDK_PHP_LOGGING';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Obtains an AuthTokenSubscriber that uses the default FetchAuthTokenInterface
|
||||
* implementation to use in this environment.
|
||||
*
|
||||
* If supplied, $scope is used to in creating the credentials instance if
|
||||
* this does not fallback to the compute engine defaults.
|
||||
*
|
||||
* @param string|string[] $scope the scope of the access request, expressed
|
||||
* either as an Array or as a space-delimited String.
|
||||
* @param callable|null $httpHandler callback which delivers psr7 request
|
||||
* @param array<mixed>|null $cacheConfig configuration for the cache when it's present
|
||||
* @param CacheItemPoolInterface|null $cache A cache implementation, may be
|
||||
* provided if you have one already available for use.
|
||||
* @return AuthTokenSubscriber
|
||||
* @throws DomainException if no implementation can be obtained.
|
||||
*/
|
||||
public static function getSubscriber(// @phpstan-ignore-line
|
||||
$scope = null,
|
||||
?callable $httpHandler = null,
|
||||
?array $cacheConfig = null,
|
||||
?CacheItemPoolInterface $cache = null
|
||||
) {
|
||||
$creds = self::getCredentials($scope, $httpHandler, $cacheConfig, $cache);
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
return new AuthTokenSubscriber($creds, $httpHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an AuthTokenMiddleware that uses the default FetchAuthTokenInterface
|
||||
* implementation to use in this environment.
|
||||
*
|
||||
* If supplied, $scope is used to in creating the credentials instance if
|
||||
* this does not fallback to the compute engine defaults.
|
||||
*
|
||||
* @param string|string[] $scope the scope of the access request, expressed
|
||||
* either as an Array or as a space-delimited String.
|
||||
* @param callable|null $httpHandler callback which delivers psr7 request
|
||||
* @param array<mixed>|null $cacheConfig configuration for the cache when it's present
|
||||
* @param CacheItemPoolInterface|null $cache A cache implementation, may be
|
||||
* provided if you have one already available for use.
|
||||
* @param string $quotaProject specifies a project to bill for access
|
||||
* charges associated with the request.
|
||||
* @return AuthTokenMiddleware
|
||||
* @throws DomainException if no implementation can be obtained.
|
||||
*/
|
||||
public static function getMiddleware(
|
||||
$scope = null,
|
||||
?callable $httpHandler = null,
|
||||
?array $cacheConfig = null,
|
||||
?CacheItemPoolInterface $cache = null,
|
||||
$quotaProject = null
|
||||
) {
|
||||
$creds = self::getCredentials($scope, $httpHandler, $cacheConfig, $cache, $quotaProject);
|
||||
|
||||
return new AuthTokenMiddleware($creds, $httpHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the default FetchAuthTokenInterface implementation to use
|
||||
* in this environment.
|
||||
*
|
||||
* @param string|string[] $scope the scope of the access request, expressed
|
||||
* either as an Array or as a space-delimited String.
|
||||
* @param callable|null $httpHandler callback which delivers psr7 request
|
||||
* @param array<mixed>|null $cacheConfig configuration for the cache when it's present
|
||||
* @param CacheItemPoolInterface|null $cache A cache implementation, may be
|
||||
* provided if you have one already available for use.
|
||||
* @param string|null $quotaProject specifies a project to bill for access
|
||||
* charges associated with the request.
|
||||
* @param string|string[]|null $defaultScope The default scope to use if no
|
||||
* user-defined scopes exist, expressed either as an Array or as a
|
||||
* space-delimited string.
|
||||
* @param string|null $universeDomain Specifies a universe domain to use for the
|
||||
* calling client library.
|
||||
* @param null|false|LoggerInterface $logger A PSR3 compliant LoggerInterface.
|
||||
*
|
||||
* @return FetchAuthTokenInterface
|
||||
* @throws DomainException if no implementation can be obtained.
|
||||
*/
|
||||
public static function getCredentials(
|
||||
$scope = null,
|
||||
?callable $httpHandler = null,
|
||||
?array $cacheConfig = null,
|
||||
?CacheItemPoolInterface $cache = null,
|
||||
$quotaProject = null,
|
||||
$defaultScope = null,
|
||||
?string $universeDomain = null,
|
||||
null|false|LoggerInterface $logger = null,
|
||||
) {
|
||||
$creds = null;
|
||||
$jsonKey = CredentialsLoader::fromEnv()
|
||||
?: CredentialsLoader::fromWellKnownFile();
|
||||
$anyScope = $scope ?: $defaultScope;
|
||||
|
||||
if (!$httpHandler) {
|
||||
if (!($client = HttpClientCache::getHttpClient())) {
|
||||
$client = new Client();
|
||||
HttpClientCache::setHttpClient($client);
|
||||
}
|
||||
|
||||
$httpHandler = HttpHandlerFactory::build($client, $logger);
|
||||
}
|
||||
|
||||
if (is_null($quotaProject)) {
|
||||
// if a quota project isn't specified, try to get one from the env var
|
||||
$quotaProject = CredentialsLoader::quotaProjectFromEnv();
|
||||
}
|
||||
|
||||
if (!is_null($jsonKey)) {
|
||||
if ($quotaProject) {
|
||||
$jsonKey['quota_project_id'] = $quotaProject;
|
||||
}
|
||||
if ($universeDomain) {
|
||||
$jsonKey['universe_domain'] = $universeDomain;
|
||||
}
|
||||
$creds = CredentialsLoader::makeCredentials(
|
||||
$scope,
|
||||
$jsonKey,
|
||||
$defaultScope
|
||||
);
|
||||
} elseif (AppIdentityCredentials::onAppEngine() && !GCECredentials::onAppEngineFlexible()) {
|
||||
$creds = new AppIdentityCredentials($anyScope);
|
||||
} elseif (self::onGce($httpHandler, $cacheConfig, $cache)) {
|
||||
$creds = new GCECredentials(null, $anyScope, null, $quotaProject, null, $universeDomain);
|
||||
$creds->setIsOnGce(true); // save the credentials a trip to the metadata server
|
||||
}
|
||||
|
||||
if (is_null($creds)) {
|
||||
throw new DomainException(self::notFound());
|
||||
}
|
||||
if (!is_null($cache)) {
|
||||
$creds = new FetchAuthTokenCache($creds, $cacheConfig, $cache);
|
||||
}
|
||||
return $creds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an AuthTokenMiddleware which will fetch an ID token to use in the
|
||||
* Authorization header. The middleware is configured with the default
|
||||
* FetchAuthTokenInterface implementation to use in this environment.
|
||||
*
|
||||
* If supplied, $targetAudience is used to set the "aud" on the resulting
|
||||
* ID token.
|
||||
*
|
||||
* @param string $targetAudience The audience for the ID token.
|
||||
* @param callable|null $httpHandler callback which delivers psr7 request
|
||||
* @param array<mixed>|null $cacheConfig configuration for the cache when it's present
|
||||
* @param CacheItemPoolInterface|null $cache A cache implementation, may be
|
||||
* provided if you have one already available for use.
|
||||
* @return AuthTokenMiddleware
|
||||
* @throws DomainException if no implementation can be obtained.
|
||||
*/
|
||||
public static function getIdTokenMiddleware(
|
||||
$targetAudience,
|
||||
?callable $httpHandler = null,
|
||||
?array $cacheConfig = null,
|
||||
?CacheItemPoolInterface $cache = null
|
||||
) {
|
||||
$creds = self::getIdTokenCredentials($targetAudience, $httpHandler, $cacheConfig, $cache);
|
||||
|
||||
return new AuthTokenMiddleware($creds, $httpHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an ProxyAuthTokenMiddleware which will fetch an ID token to use in the
|
||||
* Authorization header. The middleware is configured with the default
|
||||
* FetchAuthTokenInterface implementation to use in this environment.
|
||||
*
|
||||
* If supplied, $targetAudience is used to set the "aud" on the resulting
|
||||
* ID token.
|
||||
*
|
||||
* @param string $targetAudience The audience for the ID token.
|
||||
* @param callable|null $httpHandler callback which delivers psr7 request
|
||||
* @param array<mixed>|null $cacheConfig configuration for the cache when it's present
|
||||
* @param CacheItemPoolInterface|null $cache A cache implementation, may be
|
||||
* provided if you have one already available for use.
|
||||
* @return ProxyAuthTokenMiddleware
|
||||
* @throws DomainException if no implementation can be obtained.
|
||||
*/
|
||||
public static function getProxyIdTokenMiddleware(
|
||||
$targetAudience,
|
||||
?callable $httpHandler = null,
|
||||
?array $cacheConfig = null,
|
||||
?CacheItemPoolInterface $cache = null
|
||||
) {
|
||||
$creds = self::getIdTokenCredentials($targetAudience, $httpHandler, $cacheConfig, $cache);
|
||||
|
||||
return new ProxyAuthTokenMiddleware($creds, $httpHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the default FetchAuthTokenInterface implementation to use
|
||||
* in this environment, configured with a $targetAudience for fetching an ID
|
||||
* token.
|
||||
*
|
||||
* @param string $targetAudience The audience for the ID token.
|
||||
* @param callable|null $httpHandler callback which delivers psr7 request
|
||||
* @param array<mixed>|null $cacheConfig configuration for the cache when it's present
|
||||
* @param CacheItemPoolInterface|null $cache A cache implementation, may be
|
||||
* provided if you have one already available for use.
|
||||
* @return FetchAuthTokenInterface
|
||||
* @throws DomainException if no implementation can be obtained.
|
||||
* @throws InvalidArgumentException if JSON "type" key is invalid
|
||||
*/
|
||||
public static function getIdTokenCredentials(
|
||||
$targetAudience,
|
||||
?callable $httpHandler = null,
|
||||
?array $cacheConfig = null,
|
||||
?CacheItemPoolInterface $cache = null
|
||||
) {
|
||||
$creds = null;
|
||||
$jsonKey = CredentialsLoader::fromEnv()
|
||||
?: CredentialsLoader::fromWellKnownFile();
|
||||
|
||||
if (!$httpHandler) {
|
||||
if (!($client = HttpClientCache::getHttpClient())) {
|
||||
$client = new Client();
|
||||
HttpClientCache::setHttpClient($client);
|
||||
}
|
||||
|
||||
$httpHandler = HttpHandlerFactory::build($client);
|
||||
}
|
||||
|
||||
if (!is_null($jsonKey)) {
|
||||
if (!array_key_exists('type', $jsonKey)) {
|
||||
throw new \InvalidArgumentException('json key is missing the type field');
|
||||
}
|
||||
|
||||
$creds = match ($jsonKey['type']) {
|
||||
'authorized_user' => new UserRefreshCredentials(null, $jsonKey, $targetAudience),
|
||||
'impersonated_service_account' => new ImpersonatedServiceAccountCredentials(null, $jsonKey, $targetAudience),
|
||||
'service_account' => new ServiceAccountCredentials(null, $jsonKey, null, $targetAudience),
|
||||
default => throw new InvalidArgumentException('invalid value in the type field')
|
||||
};
|
||||
} elseif (self::onGce($httpHandler, $cacheConfig, $cache)) {
|
||||
$creds = new GCECredentials(null, null, $targetAudience);
|
||||
$creds->setIsOnGce(true); // save the credentials a trip to the metadata server
|
||||
}
|
||||
|
||||
if (is_null($creds)) {
|
||||
throw new DomainException(self::notFound());
|
||||
}
|
||||
if (!is_null($cache)) {
|
||||
$creds = new FetchAuthTokenCache($creds, $cacheConfig, $cache);
|
||||
}
|
||||
return $creds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a StdOutLogger instance
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return null|LoggerInterface
|
||||
*/
|
||||
public static function getDefaultLogger(): null|LoggerInterface
|
||||
{
|
||||
$loggingFlag = getenv(self::SDK_DEBUG_ENV_VAR);
|
||||
|
||||
// Env var is not set
|
||||
if (empty($loggingFlag)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$loggingFlag = strtolower($loggingFlag);
|
||||
|
||||
// Env Var is not true
|
||||
if ($loggingFlag !== 'true') {
|
||||
if ($loggingFlag !== 'false') {
|
||||
trigger_error('The ' . self::SDK_DEBUG_ENV_VAR . ' is set, but it is set to another value than false or true. Logging is disabled');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return new StdOutLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private static function notFound()
|
||||
{
|
||||
$msg = 'Your default credentials were not found. To set up ';
|
||||
$msg .= 'Application Default Credentials, see ';
|
||||
$msg .= 'https://cloud.google.com/docs/authentication/external/set-up-adc';
|
||||
|
||||
return $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|null $httpHandler
|
||||
* @param array<mixed>|null $cacheConfig
|
||||
* @param CacheItemPoolInterface|null $cache
|
||||
* @return bool
|
||||
*/
|
||||
private static function onGce(
|
||||
?callable $httpHandler = null,
|
||||
?array $cacheConfig = null,
|
||||
?CacheItemPoolInterface $cache = null
|
||||
) {
|
||||
$gceCacheConfig = [];
|
||||
foreach (['lifetime', 'prefix'] as $key) {
|
||||
if (isset($cacheConfig['gce_' . $key])) {
|
||||
$gceCacheConfig[$key] = $cacheConfig['gce_' . $key];
|
||||
}
|
||||
}
|
||||
|
||||
return (new GCECache($gceCacheConfig, $cache))->onGce($httpHandler);
|
||||
}
|
||||
}
|
||||
230
vendor/google/auth/src/Cache/FileSystemCacheItemPool.php
vendored
Normal file
@ -0,0 +1,230 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2024 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\Cache;
|
||||
|
||||
use ErrorException;
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
|
||||
class FileSystemCacheItemPool implements CacheItemPoolInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $cachePath;
|
||||
|
||||
/**
|
||||
* @var array<CacheItemInterface>
|
||||
*/
|
||||
private array $buffer = [];
|
||||
|
||||
/**
|
||||
* Creates a FileSystemCacheItemPool cache that stores values in local storage
|
||||
*
|
||||
* @param string $path The string representation of the path where the cache will store the serialized objects.
|
||||
*/
|
||||
public function __construct(string $path)
|
||||
{
|
||||
$this->cachePath = $path;
|
||||
|
||||
if (is_dir($this->cachePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mkdir($this->cachePath)) {
|
||||
throw new ErrorException("Cache folder couldn't be created.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItem(string $key): CacheItemInterface
|
||||
{
|
||||
if (!$this->validKey($key)) {
|
||||
throw new InvalidArgumentException("The key '$key' is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|");
|
||||
}
|
||||
|
||||
$item = new TypedItem($key);
|
||||
|
||||
$itemPath = $this->cacheFilePath($key);
|
||||
|
||||
if (!file_exists($itemPath)) {
|
||||
return $item;
|
||||
}
|
||||
|
||||
$serializedItem = file_get_contents($itemPath);
|
||||
|
||||
if ($serializedItem === false) {
|
||||
return $item;
|
||||
}
|
||||
|
||||
$item->set(unserialize($serializedItem));
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return iterable<CacheItemInterface> An iterable object containing all the
|
||||
* A traversable collection of Cache Items keyed by the cache keys of
|
||||
* each item. A Cache item will be returned for each key, even if that
|
||||
* key is not found. However, if no keys are specified then an empty
|
||||
* traversable MUST be returned instead.
|
||||
*/
|
||||
public function getItems(array $keys = []): iterable
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$result[$key] = $this->getItem($key);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(CacheItemInterface $item): bool
|
||||
{
|
||||
if (!$this->validKey($item->getKey())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$itemPath = $this->cacheFilePath($item->getKey());
|
||||
$serializedItem = serialize($item->get());
|
||||
|
||||
$result = file_put_contents($itemPath, $serializedItem);
|
||||
|
||||
// 0 bytes write is considered a successful operation
|
||||
if ($result === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasItem(string $key): bool
|
||||
{
|
||||
return $this->getItem($key)->isHit();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear(): bool
|
||||
{
|
||||
$this->buffer = [];
|
||||
|
||||
if (!is_dir($this->cachePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$files = scandir($this->cachePath);
|
||||
if (!$files) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($files as $fileName) {
|
||||
if ($fileName === '.' || $fileName === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!unlink($this->cachePath . '/' . $fileName)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItem(string $key): bool
|
||||
{
|
||||
if (!$this->validKey($key)) {
|
||||
throw new InvalidArgumentException("The key '$key' is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|");
|
||||
}
|
||||
|
||||
$itemPath = $this->cacheFilePath($key);
|
||||
|
||||
if (!file_exists($itemPath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return unlink($itemPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItems(array $keys): bool
|
||||
{
|
||||
$result = true;
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if (!$this->deleteItem($key)) {
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveDeferred(CacheItemInterface $item): bool
|
||||
{
|
||||
array_push($this->buffer, $item);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function commit(): bool
|
||||
{
|
||||
$result = true;
|
||||
|
||||
foreach ($this->buffer as $item) {
|
||||
if (!$this->save($item)) {
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function cacheFilePath(string $key): string
|
||||
{
|
||||
return $this->cachePath . '/' . $key;
|
||||
}
|
||||
|
||||
private function validKey(string $key): bool
|
||||
{
|
||||
return (bool) preg_match('|^[a-zA-Z0-9_\.]+$|', $key);
|
||||
}
|
||||
}
|
||||
24
vendor/google/auth/src/Cache/InvalidArgumentException.php
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2016 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\Cache;
|
||||
|
||||
use Psr\Cache\InvalidArgumentException as PsrInvalidArgumentException;
|
||||
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements PsrInvalidArgumentException
|
||||
{
|
||||
}
|
||||
182
vendor/google/auth/src/Cache/MemoryCacheItemPool.php
vendored
Normal file
@ -0,0 +1,182 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2016 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\Cache;
|
||||
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
|
||||
/**
|
||||
* Simple in-memory cache implementation.
|
||||
*/
|
||||
final class MemoryCacheItemPool implements CacheItemPoolInterface
|
||||
{
|
||||
/**
|
||||
* @var CacheItemInterface[]
|
||||
*/
|
||||
private $items;
|
||||
|
||||
/**
|
||||
* @var CacheItemInterface[]
|
||||
*/
|
||||
private $deferredItems;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return CacheItemInterface The corresponding Cache Item.
|
||||
*/
|
||||
public function getItem($key): CacheItemInterface
|
||||
{
|
||||
return current($this->getItems([$key])); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return iterable<CacheItemInterface>
|
||||
* A traversable collection of Cache Items keyed by the cache keys of
|
||||
* each item. A Cache item will be returned for each key, even if that
|
||||
* key is not found. However, if no keys are specified then an empty
|
||||
* traversable MUST be returned instead.
|
||||
*/
|
||||
public function getItems(array $keys = []): iterable
|
||||
{
|
||||
$items = [];
|
||||
foreach ($keys as $key) {
|
||||
$items[$key] = $this->hasItem($key) ? clone $this->items[$key] : new TypedItem($key);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
* True if item exists in the cache, false otherwise.
|
||||
*/
|
||||
public function hasItem($key): bool
|
||||
{
|
||||
$this->isValidKey($key);
|
||||
|
||||
return isset($this->items[$key]) && $this->items[$key]->isHit();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
* True if the pool was successfully cleared. False if there was an error.
|
||||
*/
|
||||
public function clear(): bool
|
||||
{
|
||||
$this->items = [];
|
||||
$this->deferredItems = [];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
* True if the item was successfully removed. False if there was an error.
|
||||
*/
|
||||
public function deleteItem($key): bool
|
||||
{
|
||||
return $this->deleteItems([$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
* True if the items were successfully removed. False if there was an error.
|
||||
*/
|
||||
public function deleteItems(array $keys): bool
|
||||
{
|
||||
array_walk($keys, [$this, 'isValidKey']);
|
||||
|
||||
foreach ($keys as $key) {
|
||||
unset($this->items[$key]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
* True if the item was successfully persisted. False if there was an error.
|
||||
*/
|
||||
public function save(CacheItemInterface $item): bool
|
||||
{
|
||||
$this->items[$item->getKey()] = $item;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
* False if the item could not be queued or if a commit was attempted and failed. True otherwise.
|
||||
*/
|
||||
public function saveDeferred(CacheItemInterface $item): bool
|
||||
{
|
||||
$this->deferredItems[$item->getKey()] = $item;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return bool
|
||||
* True if all not-yet-saved items were successfully saved or there were none. False otherwise.
|
||||
*/
|
||||
public function commit(): bool
|
||||
{
|
||||
foreach ($this->deferredItems as $item) {
|
||||
$this->save($item);
|
||||
}
|
||||
|
||||
$this->deferredItems = [];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the provided key is valid.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function isValidKey($key)
|
||||
{
|
||||
$invalidCharacters = '{}()/\\\\@:';
|
||||
|
||||
if (!is_string($key) || preg_match("#[$invalidCharacters]#", $key)) {
|
||||
throw new InvalidArgumentException('The provided key is not valid: ' . var_export($key, true));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
248
vendor/google/auth/src/Cache/SysVCacheItemPool.php
vendored
Normal file
@ -0,0 +1,248 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2018 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
namespace Google\Auth\Cache;
|
||||
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
|
||||
/**
|
||||
* SystemV shared memory based CacheItemPool implementation.
|
||||
*
|
||||
* This CacheItemPool implementation can be used among multiple processes, but
|
||||
* it doesn't provide any locking mechanism. If multiple processes write to
|
||||
* this ItemPool, you have to avoid race condition manually in your code.
|
||||
*/
|
||||
class SysVCacheItemPool implements CacheItemPoolInterface
|
||||
{
|
||||
const VAR_KEY = 1;
|
||||
|
||||
const DEFAULT_PROJ = 'A';
|
||||
|
||||
const DEFAULT_MEMSIZE = 10000;
|
||||
|
||||
const DEFAULT_PERM = 0600;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $sysvKey;
|
||||
|
||||
/**
|
||||
* @var CacheItemInterface[]
|
||||
*/
|
||||
private $items;
|
||||
|
||||
/**
|
||||
* @var CacheItemInterface[]
|
||||
*/
|
||||
private $deferredItems;
|
||||
|
||||
/**
|
||||
* @var array<mixed>
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $hasLoadedItems = false;
|
||||
|
||||
/**
|
||||
* Create a SystemV shared memory based CacheItemPool.
|
||||
*
|
||||
* @param array<mixed> $options {
|
||||
* [optional] Configuration options.
|
||||
*
|
||||
* @type int $variableKey The variable key for getting the data from the shared memory. **Defaults to** 1.
|
||||
* @type string $proj The project identifier for ftok. This needs to be a one character string.
|
||||
* **Defaults to** 'A'.
|
||||
* @type int $memsize The memory size in bytes for shm_attach. **Defaults to** 10000.
|
||||
* @type int $perm The permission for shm_attach. **Defaults to** 0600.
|
||||
* }
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if (! extension_loaded('sysvshm')) {
|
||||
throw new \RuntimeException(
|
||||
'sysvshm extension is required to use this ItemPool'
|
||||
);
|
||||
}
|
||||
$this->options = $options + [
|
||||
'variableKey' => self::VAR_KEY,
|
||||
'proj' => self::DEFAULT_PROJ,
|
||||
'memsize' => self::DEFAULT_MEMSIZE,
|
||||
'perm' => self::DEFAULT_PERM
|
||||
];
|
||||
$this->items = [];
|
||||
$this->deferredItems = [];
|
||||
$this->sysvKey = ftok(__FILE__, $this->options['proj']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $key
|
||||
* @return CacheItemInterface
|
||||
*/
|
||||
public function getItem($key): CacheItemInterface
|
||||
{
|
||||
$this->loadItems();
|
||||
return current($this->getItems([$key])); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $keys
|
||||
* @return iterable<CacheItemInterface>
|
||||
*/
|
||||
public function getItems(array $keys = []): iterable
|
||||
{
|
||||
$this->loadItems();
|
||||
$items = [];
|
||||
foreach ($keys as $key) {
|
||||
$items[$key] = $this->hasItem($key) ?
|
||||
clone $this->items[$key] :
|
||||
new TypedItem($key);
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasItem($key): bool
|
||||
{
|
||||
$this->loadItems();
|
||||
return isset($this->items[$key]) && $this->items[$key]->isHit();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear(): bool
|
||||
{
|
||||
$this->items = [];
|
||||
$this->deferredItems = [];
|
||||
return $this->saveCurrentItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItem($key): bool
|
||||
{
|
||||
return $this->deleteItems([$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItems(array $keys): bool
|
||||
{
|
||||
if (!$this->hasLoadedItems) {
|
||||
$this->loadItems();
|
||||
}
|
||||
|
||||
foreach ($keys as $key) {
|
||||
unset($this->items[$key]);
|
||||
}
|
||||
return $this->saveCurrentItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(CacheItemInterface $item): bool
|
||||
{
|
||||
if (!$this->hasLoadedItems) {
|
||||
$this->loadItems();
|
||||
}
|
||||
|
||||
$this->items[$item->getKey()] = $item;
|
||||
return $this->saveCurrentItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveDeferred(CacheItemInterface $item): bool
|
||||
{
|
||||
$this->deferredItems[$item->getKey()] = $item;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function commit(): bool
|
||||
{
|
||||
foreach ($this->deferredItems as $item) {
|
||||
if ($this->save($item) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$this->deferredItems = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current items.
|
||||
*
|
||||
* @return bool true when success, false upon failure
|
||||
*/
|
||||
private function saveCurrentItems()
|
||||
{
|
||||
$shmid = shm_attach(
|
||||
$this->sysvKey,
|
||||
$this->options['memsize'],
|
||||
$this->options['perm']
|
||||
);
|
||||
if ($shmid !== false) {
|
||||
$ret = shm_put_var(
|
||||
$shmid,
|
||||
$this->options['variableKey'],
|
||||
$this->items
|
||||
);
|
||||
shm_detach($shmid);
|
||||
return $ret;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the items from the shared memory.
|
||||
*
|
||||
* @return bool true when success, false upon failure
|
||||
*/
|
||||
private function loadItems()
|
||||
{
|
||||
$shmid = shm_attach(
|
||||
$this->sysvKey,
|
||||
$this->options['memsize'],
|
||||
$this->options['perm']
|
||||
);
|
||||
if ($shmid !== false) {
|
||||
$data = @shm_get_var($shmid, $this->options['variableKey']);
|
||||
if (!empty($data)) {
|
||||
$this->items = $data;
|
||||
} else {
|
||||
$this->items = [];
|
||||
}
|
||||
shm_detach($shmid);
|
||||
$this->hasLoadedItems = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
170
vendor/google/auth/src/Cache/TypedItem.php
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2022 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\Cache;
|
||||
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
|
||||
/**
|
||||
* A cache item.
|
||||
*
|
||||
* This class will be used by MemoryCacheItemPool and SysVCacheItemPool
|
||||
* on PHP 8.0 and above. It is compatible with psr/cache 3.0 (PSR-6).
|
||||
* @see Item for compatiblity with previous versions of PHP.
|
||||
*/
|
||||
final class TypedItem implements CacheItemInterface
|
||||
{
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private mixed $value;
|
||||
|
||||
/**
|
||||
* @var \DateTimeInterface|null
|
||||
*/
|
||||
private ?\DateTimeInterface $expiration;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private bool $isHit = false;
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*/
|
||||
public function __construct(
|
||||
private string $key
|
||||
) {
|
||||
$this->key = $key;
|
||||
$this->expiration = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getKey(): string
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(): mixed
|
||||
{
|
||||
return $this->isHit() ? $this->value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isHit(): bool
|
||||
{
|
||||
if (!$this->isHit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->expiration === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->currentTime()->getTimestamp() < $this->expiration->getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set(mixed $value): static
|
||||
{
|
||||
$this->isHit = true;
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function expiresAt($expiration): static
|
||||
{
|
||||
if ($this->isValidExpiration($expiration)) {
|
||||
$this->expiration = $expiration;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$error = sprintf(
|
||||
'Argument 1 passed to %s::expiresAt() must implement interface DateTimeInterface, %s given',
|
||||
get_class($this),
|
||||
gettype($expiration)
|
||||
);
|
||||
|
||||
throw new \TypeError($error);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function expiresAfter($time): static
|
||||
{
|
||||
if (is_int($time)) {
|
||||
$this->expiration = $this->currentTime()->add(new \DateInterval("PT{$time}S"));
|
||||
} elseif ($time instanceof \DateInterval) {
|
||||
$this->expiration = $this->currentTime()->add($time);
|
||||
} elseif ($time === null) {
|
||||
$this->expiration = $time;
|
||||
} else {
|
||||
$message = 'Argument 1 passed to %s::expiresAfter() must be an ' .
|
||||
'instance of DateInterval or of the type integer, %s given';
|
||||
$error = sprintf($message, get_class($this), gettype($time));
|
||||
|
||||
throw new \TypeError($error);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an expiration is valid based on the rules defined by PSR6.
|
||||
*
|
||||
* @param mixed $expiration
|
||||
* @return bool
|
||||
*/
|
||||
private function isValidExpiration($expiration)
|
||||
{
|
||||
if ($expiration === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// We test for two types here due to the fact the DateTimeInterface
|
||||
// was not introduced until PHP 5.5. Checking for the DateTime type as
|
||||
// well allows us to support 5.4.
|
||||
if ($expiration instanceof \DateTimeInterface) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime
|
||||
*/
|
||||
protected function currentTime()
|
||||
{
|
||||
return new \DateTime('now', new \DateTimeZone('UTC'));
|
||||
}
|
||||
}
|
||||
110
vendor/google/auth/src/CacheTrait.php
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth;
|
||||
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
|
||||
trait CacheTrait
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $maxKeyLength = 64;
|
||||
|
||||
/**
|
||||
* @var array<mixed>
|
||||
*/
|
||||
private $cacheConfig;
|
||||
|
||||
/**
|
||||
* @var ?CacheItemPoolInterface
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* Gets the cached value if it is present in the cache when that is
|
||||
* available.
|
||||
*
|
||||
* @param mixed $k
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function getCachedValue($k)
|
||||
{
|
||||
if (is_null($this->cache)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$key = $this->getFullCacheKey($k);
|
||||
if (is_null($key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cacheItem = $this->cache->getItem($key);
|
||||
if ($cacheItem->isHit()) {
|
||||
return $cacheItem->get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the value in the cache when that is available.
|
||||
*
|
||||
* @param mixed $k
|
||||
* @param mixed $v
|
||||
* @return mixed
|
||||
*/
|
||||
private function setCachedValue($k, $v)
|
||||
{
|
||||
if (is_null($this->cache)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$key = $this->getFullCacheKey($k);
|
||||
if (is_null($key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cacheItem = $this->cache->getItem($key);
|
||||
$cacheItem->set($v);
|
||||
$cacheItem->expiresAfter($this->cacheConfig['lifetime']);
|
||||
return $this->cache->save($cacheItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|string $key
|
||||
* @return null|string
|
||||
*/
|
||||
private function getFullCacheKey($key)
|
||||
{
|
||||
if (is_null($key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$key = ($this->cacheConfig['prefix'] ?? '') . $key;
|
||||
|
||||
// ensure we do not have illegal characters
|
||||
$key = preg_replace('|[^a-zA-Z0-9_\.!]|', '', $key);
|
||||
|
||||
// Hash keys if they exceed $maxKeyLength (defaults to 64)
|
||||
if ($this->maxKeyLength && strlen($key) > $this->maxKeyLength) {
|
||||
$key = substr(hash('sha256', $key), 0, $this->maxKeyLength);
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
375
vendor/google/auth/src/CredentialSource/AwsNativeSource.php
vendored
Normal file
@ -0,0 +1,375 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2023 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\CredentialSource;
|
||||
|
||||
use Google\Auth\ExternalAccountCredentialSourceInterface;
|
||||
use Google\Auth\HttpHandler\HttpClientCache;
|
||||
use Google\Auth\HttpHandler\HttpHandlerFactory;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
|
||||
/**
|
||||
* Authenticates requests using AWS credentials.
|
||||
*/
|
||||
class AwsNativeSource implements ExternalAccountCredentialSourceInterface
|
||||
{
|
||||
private const CRED_VERIFICATION_QUERY = 'Action=GetCallerIdentity&Version=2011-06-15';
|
||||
|
||||
private string $audience;
|
||||
private string $regionalCredVerificationUrl;
|
||||
private ?string $regionUrl;
|
||||
private ?string $securityCredentialsUrl;
|
||||
private ?string $imdsv2SessionTokenUrl;
|
||||
|
||||
/**
|
||||
* @param string $audience The audience for the credential.
|
||||
* @param string $regionalCredVerificationUrl The regional AWS GetCallerIdentity action URL used to determine the
|
||||
* AWS account ID and its roles. This is not called by this library, but
|
||||
* is sent in the subject token to be called by the STS token server.
|
||||
* @param string|null $regionUrl This URL should be used to determine the current AWS region needed for the signed
|
||||
* request construction.
|
||||
* @param string|null $securityCredentialsUrl The AWS metadata server URL used to retrieve the access key, secret
|
||||
* key and security token needed to sign the GetCallerIdentity request.
|
||||
* @param string|null $imdsv2SessionTokenUrl Presence of this URL enforces the auth libraries to fetch a Session
|
||||
* Token from AWS. This field is required for EC2 instances using IMDSv2.
|
||||
*/
|
||||
public function __construct(
|
||||
string $audience,
|
||||
string $regionalCredVerificationUrl,
|
||||
?string $regionUrl = null,
|
||||
?string $securityCredentialsUrl = null,
|
||||
?string $imdsv2SessionTokenUrl = null
|
||||
) {
|
||||
$this->audience = $audience;
|
||||
$this->regionalCredVerificationUrl = $regionalCredVerificationUrl;
|
||||
$this->regionUrl = $regionUrl;
|
||||
$this->securityCredentialsUrl = $securityCredentialsUrl;
|
||||
$this->imdsv2SessionTokenUrl = $imdsv2SessionTokenUrl;
|
||||
}
|
||||
|
||||
public function fetchSubjectToken(?callable $httpHandler = null): string
|
||||
{
|
||||
if (is_null($httpHandler)) {
|
||||
$httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient());
|
||||
}
|
||||
|
||||
$headers = [];
|
||||
if ($this->imdsv2SessionTokenUrl) {
|
||||
$headers = [
|
||||
'X-aws-ec2-metadata-token' => self::getImdsV2SessionToken($this->imdsv2SessionTokenUrl, $httpHandler)
|
||||
];
|
||||
}
|
||||
|
||||
if (!$signingVars = self::getSigningVarsFromEnv()) {
|
||||
if (!$this->securityCredentialsUrl) {
|
||||
throw new \LogicException('Unable to get credentials from ENV, and no security credentials URL provided');
|
||||
}
|
||||
$signingVars = self::getSigningVarsFromUrl(
|
||||
$httpHandler,
|
||||
$this->securityCredentialsUrl,
|
||||
self::getRoleName($httpHandler, $this->securityCredentialsUrl, $headers),
|
||||
$headers
|
||||
);
|
||||
}
|
||||
|
||||
if (!$region = self::getRegionFromEnv()) {
|
||||
if (!$this->regionUrl) {
|
||||
throw new \LogicException('Unable to get region from ENV, and no region URL provided');
|
||||
}
|
||||
$region = self::getRegionFromUrl($httpHandler, $this->regionUrl, $headers);
|
||||
}
|
||||
$url = str_replace('{region}', $region, $this->regionalCredVerificationUrl);
|
||||
$host = parse_url($url)['host'] ?? '';
|
||||
|
||||
// From here we use the signing vars to create the signed request to receive a token
|
||||
[$accessKeyId, $secretAccessKey, $securityToken] = $signingVars;
|
||||
$headers = self::getSignedRequestHeaders($region, $host, $accessKeyId, $secretAccessKey, $securityToken);
|
||||
|
||||
// Inject x-goog-cloud-target-resource into header
|
||||
$headers['x-goog-cloud-target-resource'] = $this->audience;
|
||||
|
||||
// Format headers as they're expected in the subject token
|
||||
$formattedHeaders = array_map(
|
||||
fn ($k, $v) => ['key' => $k, 'value' => $v],
|
||||
array_keys($headers),
|
||||
$headers,
|
||||
);
|
||||
|
||||
$request = [
|
||||
'headers' => $formattedHeaders,
|
||||
'method' => 'POST',
|
||||
'url' => $url,
|
||||
];
|
||||
|
||||
return urlencode(json_encode($request) ?: '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function getImdsV2SessionToken(string $imdsV2Url, callable $httpHandler): string
|
||||
{
|
||||
$headers = [
|
||||
'X-aws-ec2-metadata-token-ttl-seconds' => '21600'
|
||||
];
|
||||
$request = new Request(
|
||||
'PUT',
|
||||
$imdsV2Url,
|
||||
$headers
|
||||
);
|
||||
|
||||
$response = $httpHandler($request);
|
||||
return (string) $response->getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function getSignedRequestHeaders(
|
||||
string $region,
|
||||
string $host,
|
||||
string $accessKeyId,
|
||||
string $secretAccessKey,
|
||||
?string $securityToken
|
||||
): array {
|
||||
$service = 'sts';
|
||||
|
||||
# Create a date for headers and the credential string in ISO-8601 format
|
||||
$amzdate = gmdate('Ymd\THis\Z');
|
||||
$datestamp = gmdate('Ymd'); # Date w/o time, used in credential scope
|
||||
|
||||
# Create the canonical headers and signed headers. Header names
|
||||
# must be trimmed and lowercase, and sorted in code point order from
|
||||
# low to high. Note that there is a trailing \n.
|
||||
$canonicalHeaders = sprintf("host:%s\nx-amz-date:%s\n", $host, $amzdate);
|
||||
if ($securityToken) {
|
||||
$canonicalHeaders .= sprintf("x-amz-security-token:%s\n", $securityToken);
|
||||
}
|
||||
|
||||
# Step 5: Create the list of signed headers. This lists the headers
|
||||
# in the canonicalHeaders list, delimited with ";" and in alpha order.
|
||||
# Note: The request can include any headers; $canonicalHeaders and
|
||||
# $signedHeaders lists those that you want to be included in the
|
||||
# hash of the request. "Host" and "x-amz-date" are always required.
|
||||
$signedHeaders = 'host;x-amz-date';
|
||||
if ($securityToken) {
|
||||
$signedHeaders .= ';x-amz-security-token';
|
||||
}
|
||||
|
||||
# Step 6: Create payload hash (hash of the request body content). For GET
|
||||
# requests, the payload is an empty string ("").
|
||||
$payloadHash = hash('sha256', '');
|
||||
|
||||
# Step 7: Combine elements to create canonical request
|
||||
$canonicalRequest = implode("\n", [
|
||||
'POST', // method
|
||||
'/', // canonical URL
|
||||
self::CRED_VERIFICATION_QUERY, // query string
|
||||
$canonicalHeaders,
|
||||
$signedHeaders,
|
||||
$payloadHash
|
||||
]);
|
||||
|
||||
# ************* TASK 2: CREATE THE STRING TO SIGN*************
|
||||
# Match the algorithm to the hashing algorithm you use, either SHA-1 or
|
||||
# SHA-256 (recommended)
|
||||
$algorithm = 'AWS4-HMAC-SHA256';
|
||||
$scope = implode('/', [$datestamp, $region, $service, 'aws4_request']);
|
||||
$stringToSign = implode("\n", [$algorithm, $amzdate, $scope, hash('sha256', $canonicalRequest)]);
|
||||
|
||||
# ************* TASK 3: CALCULATE THE SIGNATURE *************
|
||||
# Create the signing key using the function defined above.
|
||||
// (done above)
|
||||
$signingKey = self::getSignatureKey($secretAccessKey, $datestamp, $region, $service);
|
||||
|
||||
# Sign the string_to_sign using the signing_key
|
||||
$signature = bin2hex(self::hmacSign($signingKey, $stringToSign));
|
||||
|
||||
# ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST *************
|
||||
# The signing information can be either in a query string value or in
|
||||
# a header named Authorization. This code shows how to use a header.
|
||||
# Create authorization header and add to request headers
|
||||
$authorizationHeader = sprintf(
|
||||
'%s Credential=%s/%s, SignedHeaders=%s, Signature=%s',
|
||||
$algorithm,
|
||||
$accessKeyId,
|
||||
$scope,
|
||||
$signedHeaders,
|
||||
$signature
|
||||
);
|
||||
|
||||
# The request can include any headers, but MUST include "host", "x-amz-date",
|
||||
# and (for this scenario) "Authorization". "host" and "x-amz-date" must
|
||||
# be included in the canonical_headers and signed_headers, as noted
|
||||
# earlier. Order here is not significant.
|
||||
$headers = [
|
||||
'host' => $host,
|
||||
'x-amz-date' => $amzdate,
|
||||
'Authorization' => $authorizationHeader,
|
||||
];
|
||||
if ($securityToken) {
|
||||
$headers['x-amz-security-token'] = $securityToken;
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function getRegionFromEnv(): ?string
|
||||
{
|
||||
$region = getenv('AWS_REGION');
|
||||
if (empty($region)) {
|
||||
$region = getenv('AWS_DEFAULT_REGION');
|
||||
}
|
||||
return $region ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @param callable $httpHandler
|
||||
* @param string $regionUrl
|
||||
* @param array<string, string|string[]> $headers Request headers to send in with the request.
|
||||
*/
|
||||
public static function getRegionFromUrl(callable $httpHandler, string $regionUrl, array $headers): string
|
||||
{
|
||||
// get the region/zone from the region URL
|
||||
$regionRequest = new Request('GET', $regionUrl, $headers);
|
||||
$regionResponse = $httpHandler($regionRequest);
|
||||
|
||||
// Remove last character. For example, if us-east-2b is returned,
|
||||
// the region would be us-east-2.
|
||||
return substr((string) $regionResponse->getBody(), 0, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @param callable $httpHandler
|
||||
* @param string $securityCredentialsUrl
|
||||
* @param array<string, string|string[]> $headers Request headers to send in with the request.
|
||||
*/
|
||||
public static function getRoleName(callable $httpHandler, string $securityCredentialsUrl, array $headers): string
|
||||
{
|
||||
// Get the AWS role name
|
||||
$roleRequest = new Request('GET', $securityCredentialsUrl, $headers);
|
||||
$roleResponse = $httpHandler($roleRequest);
|
||||
$roleName = (string) $roleResponse->getBody();
|
||||
|
||||
return $roleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @param callable $httpHandler
|
||||
* @param string $securityCredentialsUrl
|
||||
* @param array<string, string|string[]> $headers Request headers to send in with the request.
|
||||
* @return array{string, string, ?string}
|
||||
*/
|
||||
public static function getSigningVarsFromUrl(
|
||||
callable $httpHandler,
|
||||
string $securityCredentialsUrl,
|
||||
string $roleName,
|
||||
array $headers
|
||||
): array {
|
||||
// Get the AWS credentials
|
||||
$credsRequest = new Request(
|
||||
'GET',
|
||||
$securityCredentialsUrl . '/' . $roleName,
|
||||
$headers
|
||||
);
|
||||
$credsResponse = $httpHandler($credsRequest);
|
||||
$awsCreds = json_decode((string) $credsResponse->getBody(), true);
|
||||
return [
|
||||
$awsCreds['AccessKeyId'], // accessKeyId
|
||||
$awsCreds['SecretAccessKey'], // secretAccessKey
|
||||
$awsCreds['Token'], // token
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return array{string, string, ?string}
|
||||
*/
|
||||
public static function getSigningVarsFromEnv(): ?array
|
||||
{
|
||||
$accessKeyId = getenv('AWS_ACCESS_KEY_ID');
|
||||
$secretAccessKey = getenv('AWS_SECRET_ACCESS_KEY');
|
||||
if ($accessKeyId && $secretAccessKey) {
|
||||
return [
|
||||
$accessKeyId,
|
||||
$secretAccessKey,
|
||||
getenv('AWS_SESSION_TOKEN') ?: null, // session token (can be null)
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unique key for caching
|
||||
* For AwsNativeSource the values are:
|
||||
* Imdsv2SessionTokenUrl.SecurityCredentialsUrl.RegionUrl.RegionalCredVerificationUrl
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheKey(): string
|
||||
{
|
||||
return ($this->imdsv2SessionTokenUrl ?? '') .
|
||||
'.' . ($this->securityCredentialsUrl ?? '') .
|
||||
'.' . $this->regionUrl .
|
||||
'.' . $this->regionalCredVerificationUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return HMAC hash in binary string
|
||||
*/
|
||||
private static function hmacSign(string $key, string $msg): string
|
||||
{
|
||||
return hash_hmac('sha256', self::utf8Encode($msg), $key, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @TODO add a fallback when mbstring is not available
|
||||
*/
|
||||
private static function utf8Encode(string $string): string
|
||||
{
|
||||
return (string) mb_convert_encoding($string, 'UTF-8', 'ISO-8859-1');
|
||||
}
|
||||
|
||||
private static function getSignatureKey(
|
||||
string $key,
|
||||
string $dateStamp,
|
||||
string $regionName,
|
||||
string $serviceName
|
||||
): string {
|
||||
$kDate = self::hmacSign(self::utf8Encode('AWS4' . $key), $dateStamp);
|
||||
$kRegion = self::hmacSign($kDate, $regionName);
|
||||
$kService = self::hmacSign($kRegion, $serviceName);
|
||||
$kSigning = self::hmacSign($kService, 'aws4_request');
|
||||
|
||||
return $kSigning;
|
||||
}
|
||||
}
|
||||
272
vendor/google/auth/src/CredentialSource/ExecutableSource.php
vendored
Normal file
@ -0,0 +1,272 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2024 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\CredentialSource;
|
||||
|
||||
use Google\Auth\ExecutableHandler\ExecutableHandler;
|
||||
use Google\Auth\ExecutableHandler\ExecutableResponseError;
|
||||
use Google\Auth\ExternalAccountCredentialSourceInterface;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* ExecutableSource enables the exchange of workload identity pool external credentials for
|
||||
* Google access tokens by retrieving 3rd party tokens through a user supplied executable. These
|
||||
* scripts/executables are completely independent of the Google Cloud Auth libraries. These
|
||||
* credentials plug into ADC and will call the specified executable to retrieve the 3rd party token
|
||||
* to be exchanged for a Google access token.
|
||||
*
|
||||
* To use these credentials, the GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES environment variable
|
||||
* must be set to '1'. This is for security reasons.
|
||||
*
|
||||
* Both OIDC and SAML are supported. The executable must adhere to a specific response format
|
||||
* defined below.
|
||||
*
|
||||
* The executable must print out the 3rd party token to STDOUT in JSON format. When an
|
||||
* output_file is specified in the credential configuration, the executable must also handle writing the
|
||||
* JSON response to this file.
|
||||
*
|
||||
* <pre>
|
||||
* OIDC response sample:
|
||||
* {
|
||||
* "version": 1,
|
||||
* "success": true,
|
||||
* "token_type": "urn:ietf:params:oauth:token-type:id_token",
|
||||
* "id_token": "HEADER.PAYLOAD.SIGNATURE",
|
||||
* "expiration_time": 1620433341
|
||||
* }
|
||||
*
|
||||
* SAML2 response sample:
|
||||
* {
|
||||
* "version": 1,
|
||||
* "success": true,
|
||||
* "token_type": "urn:ietf:params:oauth:token-type:saml2",
|
||||
* "saml_response": "...",
|
||||
* "expiration_time": 1620433341
|
||||
* }
|
||||
*
|
||||
* Error response sample:
|
||||
* {
|
||||
* "version": 1,
|
||||
* "success": false,
|
||||
* "code": "401",
|
||||
* "message": "Error message."
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* The "expiration_time" field in the JSON response is only required for successful
|
||||
* responses when an output file was specified in the credential configuration
|
||||
*
|
||||
* The auth libraries will populate certain environment variables that will be accessible by the
|
||||
* executable, such as: GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE, GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE,
|
||||
* GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE, GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL, and
|
||||
* GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE.
|
||||
*/
|
||||
class ExecutableSource implements ExternalAccountCredentialSourceInterface
|
||||
{
|
||||
private const GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES = 'GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES';
|
||||
private const SAML_SUBJECT_TOKEN_TYPE = 'urn:ietf:params:oauth:token-type:saml2';
|
||||
private const OIDC_SUBJECT_TOKEN_TYPE1 = 'urn:ietf:params:oauth:token-type:id_token';
|
||||
private const OIDC_SUBJECT_TOKEN_TYPE2 = 'urn:ietf:params:oauth:token-type:jwt';
|
||||
|
||||
private string $command;
|
||||
private ExecutableHandler $executableHandler;
|
||||
private ?string $outputFile;
|
||||
|
||||
/**
|
||||
* @param string $command The string command to run to get the subject token.
|
||||
* @param string|null $outputFile
|
||||
*/
|
||||
public function __construct(
|
||||
string $command,
|
||||
?string $outputFile,
|
||||
?ExecutableHandler $executableHandler = null,
|
||||
) {
|
||||
$this->command = $command;
|
||||
$this->outputFile = $outputFile;
|
||||
$this->executableHandler = $executableHandler ?: new ExecutableHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unique key for caching
|
||||
* The format for the cache key is:
|
||||
* Command.OutputFile
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getCacheKey(): ?string
|
||||
{
|
||||
return $this->command . '.' . $this->outputFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|null $httpHandler unused.
|
||||
* @return string
|
||||
* @throws RuntimeException if the executable is not allowed to run.
|
||||
* @throws ExecutableResponseError if the executable response is invalid.
|
||||
*/
|
||||
public function fetchSubjectToken(?callable $httpHandler = null): string
|
||||
{
|
||||
// Check if the executable is allowed to run.
|
||||
if (getenv(self::GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES) !== '1') {
|
||||
throw new RuntimeException(
|
||||
'Pluggable Auth executables need to be explicitly allowed to run by '
|
||||
. 'setting the GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES environment '
|
||||
. 'Variable to 1.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!$executableResponse = $this->getCachedExecutableResponse()) {
|
||||
// Run the executable.
|
||||
$exitCode = ($this->executableHandler)($this->command);
|
||||
$output = $this->executableHandler->getOutput();
|
||||
|
||||
// If the exit code is not 0, throw an exception with the output as the error details
|
||||
if ($exitCode !== 0) {
|
||||
throw new ExecutableResponseError(
|
||||
'The executable failed to run'
|
||||
. ($output ? ' with the following error: ' . $output : '.'),
|
||||
(string) $exitCode
|
||||
);
|
||||
}
|
||||
|
||||
$executableResponse = $this->parseExecutableResponse($output);
|
||||
|
||||
// Validate expiration.
|
||||
if (isset($executableResponse['expiration_time']) && time() >= $executableResponse['expiration_time']) {
|
||||
throw new ExecutableResponseError('Executable response is expired.');
|
||||
}
|
||||
}
|
||||
|
||||
// Throw error when the request was unsuccessful
|
||||
if ($executableResponse['success'] === false) {
|
||||
throw new ExecutableResponseError($executableResponse['message'], (string) $executableResponse['code']);
|
||||
}
|
||||
|
||||
// Return subject token field based on the token type
|
||||
return $executableResponse['token_type'] === self::SAML_SUBJECT_TOKEN_TYPE
|
||||
? $executableResponse['saml_response']
|
||||
: $executableResponse['id_token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
private function getCachedExecutableResponse(): ?array
|
||||
{
|
||||
if (
|
||||
$this->outputFile
|
||||
&& file_exists($this->outputFile)
|
||||
&& !empty(trim($outputFileContents = (string) file_get_contents($this->outputFile)))
|
||||
) {
|
||||
try {
|
||||
$executableResponse = $this->parseExecutableResponse($outputFileContents);
|
||||
} catch (ExecutableResponseError $e) {
|
||||
throw new ExecutableResponseError(
|
||||
'Error in output file: ' . $e->getMessage(),
|
||||
'INVALID_OUTPUT_FILE'
|
||||
);
|
||||
}
|
||||
|
||||
if ($executableResponse['success'] === false) {
|
||||
// If the cached token was unsuccessful, run the executable to get a new one.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($executableResponse['expiration_time']) && time() >= $executableResponse['expiration_time']) {
|
||||
// If the cached token is expired, run the executable to get a new one.
|
||||
return null;
|
||||
}
|
||||
|
||||
return $executableResponse;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function parseExecutableResponse(string $response): array
|
||||
{
|
||||
$executableResponse = json_decode($response, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new ExecutableResponseError(
|
||||
'The executable returned an invalid response: ' . $response,
|
||||
'INVALID_RESPONSE'
|
||||
);
|
||||
}
|
||||
if (!array_key_exists('version', $executableResponse)) {
|
||||
throw new ExecutableResponseError('Executable response must contain a "version" field.');
|
||||
}
|
||||
if (!array_key_exists('success', $executableResponse)) {
|
||||
throw new ExecutableResponseError('Executable response must contain a "success" field.');
|
||||
}
|
||||
|
||||
// Validate required fields for a successful response.
|
||||
if ($executableResponse['success']) {
|
||||
// Validate token type field.
|
||||
$tokenTypes = [self::SAML_SUBJECT_TOKEN_TYPE, self::OIDC_SUBJECT_TOKEN_TYPE1, self::OIDC_SUBJECT_TOKEN_TYPE2];
|
||||
if (!isset($executableResponse['token_type'])) {
|
||||
throw new ExecutableResponseError(
|
||||
'Executable response must contain a "token_type" field when successful'
|
||||
);
|
||||
}
|
||||
if (!in_array($executableResponse['token_type'], $tokenTypes)) {
|
||||
throw new ExecutableResponseError(sprintf(
|
||||
'Executable response "token_type" field must be one of %s.',
|
||||
implode(', ', $tokenTypes)
|
||||
));
|
||||
}
|
||||
|
||||
// Validate subject token for SAML and OIDC.
|
||||
if ($executableResponse['token_type'] === self::SAML_SUBJECT_TOKEN_TYPE) {
|
||||
if (empty($executableResponse['saml_response'])) {
|
||||
throw new ExecutableResponseError(sprintf(
|
||||
'Executable response must contain a "saml_response" field when token_type=%s.',
|
||||
self::SAML_SUBJECT_TOKEN_TYPE
|
||||
));
|
||||
}
|
||||
} elseif (empty($executableResponse['id_token'])) {
|
||||
throw new ExecutableResponseError(sprintf(
|
||||
'Executable response must contain a "id_token" field when '
|
||||
. 'token_type=%s.',
|
||||
$executableResponse['token_type']
|
||||
));
|
||||
}
|
||||
|
||||
// Validate expiration exists when an output file is specified.
|
||||
if ($this->outputFile) {
|
||||
if (!isset($executableResponse['expiration_time'])) {
|
||||
throw new ExecutableResponseError(
|
||||
'The executable response must contain a "expiration_time" field for successful responses ' .
|
||||
'when an output_file has been specified in the configuration.'
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Both code and message must be provided for unsuccessful responses.
|
||||
if (!array_key_exists('code', $executableResponse)) {
|
||||
throw new ExecutableResponseError('Executable response must contain a "code" field when unsuccessful.');
|
||||
}
|
||||
if (empty($executableResponse['message'])) {
|
||||
throw new ExecutableResponseError('Executable response must contain a "message" field when unsuccessful.');
|
||||
}
|
||||
}
|
||||
|
||||
return $executableResponse;
|
||||
}
|
||||
}
|
||||
87
vendor/google/auth/src/CredentialSource/FileSource.php
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2023 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\CredentialSource;
|
||||
|
||||
use Google\Auth\ExternalAccountCredentialSourceInterface;
|
||||
use InvalidArgumentException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* Retrieve a token from a file.
|
||||
*/
|
||||
class FileSource implements ExternalAccountCredentialSourceInterface
|
||||
{
|
||||
private string $file;
|
||||
private ?string $format;
|
||||
private ?string $subjectTokenFieldName;
|
||||
|
||||
/**
|
||||
* @param string $file The file to read the subject token from.
|
||||
* @param string|null $format The format of the token in the file. Can be null or "json".
|
||||
* @param string|null $subjectTokenFieldName The name of the field containing the token in the file. This is required
|
||||
* when format is "json".
|
||||
*/
|
||||
public function __construct(
|
||||
string $file,
|
||||
?string $format = null,
|
||||
?string $subjectTokenFieldName = null
|
||||
) {
|
||||
$this->file = $file;
|
||||
|
||||
if ($format === 'json' && is_null($subjectTokenFieldName)) {
|
||||
throw new InvalidArgumentException(
|
||||
'subject_token_field_name must be set when format is JSON'
|
||||
);
|
||||
}
|
||||
|
||||
$this->format = $format;
|
||||
$this->subjectTokenFieldName = $subjectTokenFieldName;
|
||||
}
|
||||
|
||||
public function fetchSubjectToken(?callable $httpHandler = null): string
|
||||
{
|
||||
$contents = file_get_contents($this->file);
|
||||
if ($this->format === 'json') {
|
||||
if (!$json = json_decode((string) $contents, true)) {
|
||||
throw new UnexpectedValueException(
|
||||
'Unable to decode JSON file'
|
||||
);
|
||||
}
|
||||
if (!isset($json[$this->subjectTokenFieldName])) {
|
||||
throw new UnexpectedValueException(
|
||||
'subject_token_field_name not found in JSON file'
|
||||
);
|
||||
}
|
||||
$contents = $json[$this->subjectTokenFieldName];
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unique key for caching.
|
||||
* The format for the cache key one of the following:
|
||||
* Filename
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheKey(): ?string
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
}
|
||||
109
vendor/google/auth/src/CredentialSource/UrlSource.php
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2023 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\CredentialSource;
|
||||
|
||||
use Google\Auth\ExternalAccountCredentialSourceInterface;
|
||||
use Google\Auth\HttpHandler\HttpClientCache;
|
||||
use Google\Auth\HttpHandler\HttpHandlerFactory;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use InvalidArgumentException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* Retrieve a token from a URL.
|
||||
*/
|
||||
class UrlSource implements ExternalAccountCredentialSourceInterface
|
||||
{
|
||||
private string $url;
|
||||
private ?string $format;
|
||||
private ?string $subjectTokenFieldName;
|
||||
|
||||
/**
|
||||
* @var array<string, string|string[]>
|
||||
*/
|
||||
private ?array $headers;
|
||||
|
||||
/**
|
||||
* @param string $url The URL to fetch the subject token from.
|
||||
* @param string|null $format The format of the token in the response. Can be null or "json".
|
||||
* @param string|null $subjectTokenFieldName The name of the field containing the token in the response. This is required
|
||||
* when format is "json".
|
||||
* @param array<string, string|string[]>|null $headers Request headers to send in with the request to the URL.
|
||||
*/
|
||||
public function __construct(
|
||||
string $url,
|
||||
?string $format = null,
|
||||
?string $subjectTokenFieldName = null,
|
||||
?array $headers = null
|
||||
) {
|
||||
$this->url = $url;
|
||||
|
||||
if ($format === 'json' && is_null($subjectTokenFieldName)) {
|
||||
throw new InvalidArgumentException(
|
||||
'subject_token_field_name must be set when format is JSON'
|
||||
);
|
||||
}
|
||||
|
||||
$this->format = $format;
|
||||
$this->subjectTokenFieldName = $subjectTokenFieldName;
|
||||
$this->headers = $headers;
|
||||
}
|
||||
|
||||
public function fetchSubjectToken(?callable $httpHandler = null): string
|
||||
{
|
||||
if (is_null($httpHandler)) {
|
||||
$httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient());
|
||||
}
|
||||
|
||||
$request = new Request(
|
||||
'GET',
|
||||
$this->url,
|
||||
$this->headers ?: []
|
||||
);
|
||||
|
||||
$response = $httpHandler($request);
|
||||
$body = (string) $response->getBody();
|
||||
if ($this->format === 'json') {
|
||||
if (!$json = json_decode((string) $body, true)) {
|
||||
throw new UnexpectedValueException(
|
||||
'Unable to decode JSON response'
|
||||
);
|
||||
}
|
||||
if (!isset($json[$this->subjectTokenFieldName])) {
|
||||
throw new UnexpectedValueException(
|
||||
'subject_token_field_name not found in JSON file'
|
||||
);
|
||||
}
|
||||
$body = $json[$this->subjectTokenFieldName];
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache key for the credentials.
|
||||
* The format for the cache key is:
|
||||
* URL
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getCacheKey(): ?string
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
}
|
||||
238
vendor/google/auth/src/Credentials/AppIdentityCredentials.php
vendored
Normal file
@ -0,0 +1,238 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\Credentials;
|
||||
|
||||
/*
|
||||
* The AppIdentityService class is automatically defined on App Engine,
|
||||
* so including this dependency is not necessary, and will result in a
|
||||
* PHP fatal error in the App Engine environment.
|
||||
*/
|
||||
use google\appengine\api\app_identity\AppIdentityService;
|
||||
use Google\Auth\CredentialsLoader;
|
||||
use Google\Auth\ProjectIdProviderInterface;
|
||||
use Google\Auth\SignBlobInterface;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* AppIdentityCredentials supports authorization on Google App Engine.
|
||||
*
|
||||
* It can be used to authorize requests using the AuthTokenMiddleware or
|
||||
* AuthTokenSubscriber, but will only succeed if being run on App Engine:
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* use Google\Auth\Credentials\AppIdentityCredentials;
|
||||
* use Google\Auth\Middleware\AuthTokenMiddleware;
|
||||
* use GuzzleHttp\Client;
|
||||
* use GuzzleHttp\HandlerStack;
|
||||
*
|
||||
* $gae = new AppIdentityCredentials('https://www.googleapis.com/auth/books');
|
||||
* $middleware = new AuthTokenMiddleware($gae);
|
||||
* $stack = HandlerStack::create();
|
||||
* $stack->push($middleware);
|
||||
*
|
||||
* $client = new Client([
|
||||
* 'handler' => $stack,
|
||||
* 'base_uri' => 'https://www.googleapis.com/books/v1',
|
||||
* 'auth' => 'google_auth'
|
||||
* ]);
|
||||
*
|
||||
* $res = $client->get('volumes?q=Henry+David+Thoreau&country=US');
|
||||
* ```
|
||||
*/
|
||||
class AppIdentityCredentials extends CredentialsLoader implements
|
||||
SignBlobInterface,
|
||||
ProjectIdProviderInterface
|
||||
{
|
||||
/**
|
||||
* Result of fetchAuthToken.
|
||||
*
|
||||
* @var array<mixed>
|
||||
*/
|
||||
protected $lastReceivedToken;
|
||||
|
||||
/**
|
||||
* Array of OAuth2 scopes to be requested.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $scope;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $clientName;
|
||||
|
||||
/**
|
||||
* @param string|string[] $scope One or more scopes.
|
||||
*/
|
||||
public function __construct($scope = [])
|
||||
{
|
||||
$this->scope = is_array($scope) ? $scope : explode(' ', (string) $scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this an App Engine instance, by accessing the
|
||||
* SERVER_SOFTWARE environment variable (prod) or the APPENGINE_RUNTIME
|
||||
* environment variable (dev).
|
||||
*
|
||||
* @return bool true if this an App Engine Instance, false otherwise
|
||||
*/
|
||||
public static function onAppEngine()
|
||||
{
|
||||
$appEngineProduction = isset($_SERVER['SERVER_SOFTWARE']) &&
|
||||
0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Google App Engine');
|
||||
if ($appEngineProduction) {
|
||||
return true;
|
||||
}
|
||||
$appEngineDevAppServer = isset($_SERVER['APPENGINE_RUNTIME']) &&
|
||||
$_SERVER['APPENGINE_RUNTIME'] == 'php';
|
||||
if ($appEngineDevAppServer) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements FetchAuthTokenInterface#fetchAuthToken.
|
||||
*
|
||||
* Fetches the auth tokens using the AppIdentityService if available.
|
||||
* As the AppIdentityService uses protobufs to fetch the access token,
|
||||
* the GuzzleHttp\ClientInterface instance passed in will not be used.
|
||||
*
|
||||
* @param callable|null $httpHandler callback which delivers psr7 request
|
||||
* @return array<mixed> {
|
||||
* A set of auth related metadata, containing the following
|
||||
*
|
||||
* @type string $access_token
|
||||
* @type string $expiration_time
|
||||
* }
|
||||
*/
|
||||
public function fetchAuthToken(?callable $httpHandler = null)
|
||||
{
|
||||
try {
|
||||
$this->checkAppEngineContext();
|
||||
} catch (\Exception $e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$token = AppIdentityService::getAccessToken($this->scope);
|
||||
$this->lastReceivedToken = $token;
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a string using AppIdentityService.
|
||||
*
|
||||
* @param string $stringToSign The string to sign.
|
||||
* @param bool $forceOpenSsl [optional] Does not apply to this credentials
|
||||
* type.
|
||||
* @return string The signature, base64-encoded.
|
||||
* @throws \Exception If AppEngine SDK or mock is not available.
|
||||
*/
|
||||
public function signBlob($stringToSign, $forceOpenSsl = false)
|
||||
{
|
||||
$this->checkAppEngineContext();
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
return base64_encode(AppIdentityService::signForApp($stringToSign)['signature']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the project ID from AppIdentityService.
|
||||
*
|
||||
* Returns null if AppIdentityService is unavailable.
|
||||
*
|
||||
* @param callable|null $httpHandler Not used by this type.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProjectId(?callable $httpHandler = null)
|
||||
{
|
||||
try {
|
||||
$this->checkAppEngineContext();
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
return AppIdentityService::getApplicationId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client name from AppIdentityService.
|
||||
*
|
||||
* Subsequent calls to this method will return a cached value.
|
||||
*
|
||||
* @param callable|null $httpHandler Not used in this implementation.
|
||||
* @return string
|
||||
* @throws \Exception If AppEngine SDK or mock is not available.
|
||||
*/
|
||||
public function getClientName(?callable $httpHandler = null)
|
||||
{
|
||||
$this->checkAppEngineContext();
|
||||
|
||||
if (!$this->clientName) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->clientName = AppIdentityService::getServiceAccountName();
|
||||
}
|
||||
|
||||
return $this->clientName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{access_token:string,expires_at:int}|null
|
||||
*/
|
||||
public function getLastReceivedToken()
|
||||
{
|
||||
if ($this->lastReceivedToken) {
|
||||
return [
|
||||
'access_token' => $this->lastReceivedToken['access_token'],
|
||||
'expires_at' => $this->lastReceivedToken['expiration_time'],
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caching is handled by the underlying AppIdentityService, return empty string
|
||||
* to prevent caching.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheKey()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function checkAppEngineContext()
|
||||
{
|
||||
if (!self::onAppEngine() || !class_exists('google\appengine\api\app_identity\AppIdentityService')) {
|
||||
throw new \Exception(
|
||||
'This class must be run in App Engine, or you must include the AppIdentityService '
|
||||
. 'mock class defined in tests/mocks/AppIdentityService.php'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
394
vendor/google/auth/src/Credentials/ExternalAccountCredentials.php
vendored
Normal file
@ -0,0 +1,394 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2023 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\Credentials;
|
||||
|
||||
use Google\Auth\CredentialSource\AwsNativeSource;
|
||||
use Google\Auth\CredentialSource\ExecutableSource;
|
||||
use Google\Auth\CredentialSource\FileSource;
|
||||
use Google\Auth\CredentialSource\UrlSource;
|
||||
use Google\Auth\ExecutableHandler\ExecutableHandler;
|
||||
use Google\Auth\ExternalAccountCredentialSourceInterface;
|
||||
use Google\Auth\FetchAuthTokenInterface;
|
||||
use Google\Auth\GetQuotaProjectInterface;
|
||||
use Google\Auth\GetUniverseDomainInterface;
|
||||
use Google\Auth\HttpHandler\HttpClientCache;
|
||||
use Google\Auth\HttpHandler\HttpHandlerFactory;
|
||||
use Google\Auth\OAuth2;
|
||||
use Google\Auth\ProjectIdProviderInterface;
|
||||
use Google\Auth\UpdateMetadataInterface;
|
||||
use Google\Auth\UpdateMetadataTrait;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* **IMPORTANT**:
|
||||
* This class does not validate the credential configuration. A security
|
||||
* risk occurs when a credential configuration configured with malicious urls
|
||||
* is used.
|
||||
* When the credential configuration is accepted from an
|
||||
* untrusted source, you should validate it before creating this class.
|
||||
* @see https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
|
||||
*/
|
||||
class ExternalAccountCredentials implements
|
||||
FetchAuthTokenInterface,
|
||||
UpdateMetadataInterface,
|
||||
GetQuotaProjectInterface,
|
||||
GetUniverseDomainInterface,
|
||||
ProjectIdProviderInterface
|
||||
{
|
||||
use UpdateMetadataTrait;
|
||||
|
||||
private const EXTERNAL_ACCOUNT_TYPE = 'external_account';
|
||||
private const CLOUD_RESOURCE_MANAGER_URL = 'https://cloudresourcemanager.UNIVERSE_DOMAIN/v1/projects/%s';
|
||||
|
||||
private OAuth2 $auth;
|
||||
private ?string $quotaProject;
|
||||
private ?string $serviceAccountImpersonationUrl;
|
||||
private ?string $workforcePoolUserProject;
|
||||
private ?string $projectId;
|
||||
/** @var array<mixed> */
|
||||
private ?array $lastImpersonatedAccessToken;
|
||||
private string $universeDomain;
|
||||
|
||||
/**
|
||||
* @param string|string[] $scope The scope of the access request, expressed either as an array
|
||||
* or as a space-delimited string.
|
||||
* @param array<mixed> $jsonKey JSON credentials as an associative array.
|
||||
*/
|
||||
public function __construct(
|
||||
$scope,
|
||||
array $jsonKey
|
||||
) {
|
||||
if (!array_key_exists('type', $jsonKey)) {
|
||||
throw new InvalidArgumentException('json key is missing the type field');
|
||||
}
|
||||
if ($jsonKey['type'] !== self::EXTERNAL_ACCOUNT_TYPE) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'expected "%s" type but received "%s"',
|
||||
self::EXTERNAL_ACCOUNT_TYPE,
|
||||
$jsonKey['type']
|
||||
));
|
||||
}
|
||||
|
||||
if (!array_key_exists('token_url', $jsonKey)) {
|
||||
throw new InvalidArgumentException(
|
||||
'json key is missing the token_url field'
|
||||
);
|
||||
}
|
||||
|
||||
if (!array_key_exists('audience', $jsonKey)) {
|
||||
throw new InvalidArgumentException(
|
||||
'json key is missing the audience field'
|
||||
);
|
||||
}
|
||||
|
||||
if (!array_key_exists('subject_token_type', $jsonKey)) {
|
||||
throw new InvalidArgumentException(
|
||||
'json key is missing the subject_token_type field'
|
||||
);
|
||||
}
|
||||
|
||||
if (!array_key_exists('credential_source', $jsonKey)) {
|
||||
throw new InvalidArgumentException(
|
||||
'json key is missing the credential_source field'
|
||||
);
|
||||
}
|
||||
|
||||
$this->serviceAccountImpersonationUrl = $jsonKey['service_account_impersonation_url'] ?? null;
|
||||
|
||||
$this->quotaProject = $jsonKey['quota_project_id'] ?? null;
|
||||
$this->workforcePoolUserProject = $jsonKey['workforce_pool_user_project'] ?? null;
|
||||
$this->universeDomain = $jsonKey['universe_domain'] ?? GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN;
|
||||
|
||||
$this->auth = new OAuth2([
|
||||
'tokenCredentialUri' => $jsonKey['token_url'],
|
||||
'audience' => $jsonKey['audience'],
|
||||
'scope' => $scope,
|
||||
'subjectTokenType' => $jsonKey['subject_token_type'],
|
||||
'subjectTokenFetcher' => self::buildCredentialSource($jsonKey),
|
||||
'additionalOptions' => $this->workforcePoolUserProject
|
||||
? ['userProject' => $this->workforcePoolUserProject]
|
||||
: [],
|
||||
]);
|
||||
|
||||
if (!$this->isWorkforcePool() && $this->workforcePoolUserProject) {
|
||||
throw new InvalidArgumentException(
|
||||
'workforce_pool_user_project should not be set for non-workforce pool credentials.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $jsonKey
|
||||
*/
|
||||
private static function buildCredentialSource(array $jsonKey): ExternalAccountCredentialSourceInterface
|
||||
{
|
||||
$credentialSource = $jsonKey['credential_source'];
|
||||
if (isset($credentialSource['file'])) {
|
||||
return new FileSource(
|
||||
$credentialSource['file'],
|
||||
$credentialSource['format']['type'] ?? null,
|
||||
$credentialSource['format']['subject_token_field_name'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
isset($credentialSource['environment_id'])
|
||||
&& 1 === preg_match('/^aws(\d+)$/', $credentialSource['environment_id'], $matches)
|
||||
) {
|
||||
if ($matches[1] !== '1') {
|
||||
throw new InvalidArgumentException(
|
||||
"aws version \"$matches[1]\" is not supported in the current build."
|
||||
);
|
||||
}
|
||||
if (!array_key_exists('regional_cred_verification_url', $credentialSource)) {
|
||||
throw new InvalidArgumentException(
|
||||
'The regional_cred_verification_url field is required for aws1 credential source.'
|
||||
);
|
||||
}
|
||||
|
||||
return new AwsNativeSource(
|
||||
$jsonKey['audience'],
|
||||
$credentialSource['regional_cred_verification_url'], // $regionalCredVerificationUrl
|
||||
$credentialSource['region_url'] ?? null, // $regionUrl
|
||||
$credentialSource['url'] ?? null, // $securityCredentialsUrl
|
||||
$credentialSource['imdsv2_session_token_url'] ?? null, // $imdsV2TokenUrl
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($credentialSource['url'])) {
|
||||
return new UrlSource(
|
||||
$credentialSource['url'],
|
||||
$credentialSource['format']['type'] ?? null,
|
||||
$credentialSource['format']['subject_token_field_name'] ?? null,
|
||||
$credentialSource['headers'] ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($credentialSource['executable'])) {
|
||||
if (!array_key_exists('command', $credentialSource['executable'])) {
|
||||
throw new InvalidArgumentException(
|
||||
'executable source requires a command to be set in the JSON file.'
|
||||
);
|
||||
}
|
||||
|
||||
// Build command environment variables
|
||||
$env = [
|
||||
'GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE' => $jsonKey['audience'],
|
||||
'GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE' => $jsonKey['subject_token_type'],
|
||||
// Always set to 0 because interactive mode is not supported.
|
||||
'GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE' => '0',
|
||||
];
|
||||
|
||||
if ($outputFile = $credentialSource['executable']['output_file'] ?? null) {
|
||||
$env['GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE'] = $outputFile;
|
||||
}
|
||||
|
||||
if ($serviceAccountImpersonationUrl = $jsonKey['service_account_impersonation_url'] ?? null) {
|
||||
// Parse email from URL. The formal looks as follows:
|
||||
// https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/name@project-id.iam.gserviceaccount.com:generateAccessToken
|
||||
$regex = '/serviceAccounts\/(?<email>[^:]+):generateAccessToken$/';
|
||||
if (preg_match($regex, $serviceAccountImpersonationUrl, $matches)) {
|
||||
$env['GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL'] = $matches['email'];
|
||||
}
|
||||
}
|
||||
|
||||
$timeoutMs = $credentialSource['executable']['timeout_millis'] ?? null;
|
||||
|
||||
return new ExecutableSource(
|
||||
$credentialSource['executable']['command'],
|
||||
$outputFile,
|
||||
$timeoutMs ? new ExecutableHandler($env, $timeoutMs) : new ExecutableHandler($env)
|
||||
);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unable to determine credential source from json key.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $stsToken
|
||||
* @param callable|null $httpHandler
|
||||
*
|
||||
* @return array<mixed> {
|
||||
* A set of auth related metadata, containing the following
|
||||
*
|
||||
* @type string $access_token
|
||||
* @type int $expires_at
|
||||
* }
|
||||
*/
|
||||
private function getImpersonatedAccessToken(string $stsToken, ?callable $httpHandler = null): array
|
||||
{
|
||||
if (!isset($this->serviceAccountImpersonationUrl)) {
|
||||
throw new InvalidArgumentException(
|
||||
'service_account_impersonation_url must be set in JSON credentials.'
|
||||
);
|
||||
}
|
||||
$request = new Request(
|
||||
'POST',
|
||||
$this->serviceAccountImpersonationUrl,
|
||||
[
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => 'Bearer ' . $stsToken,
|
||||
],
|
||||
(string) json_encode([
|
||||
'lifetime' => sprintf('%ss', OAuth2::DEFAULT_EXPIRY_SECONDS),
|
||||
'scope' => explode(' ', $this->auth->getScope()),
|
||||
]),
|
||||
);
|
||||
if (is_null($httpHandler)) {
|
||||
$httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient());
|
||||
}
|
||||
$response = $httpHandler($request);
|
||||
$body = json_decode((string) $response->getBody(), true);
|
||||
return [
|
||||
'access_token' => $body['accessToken'],
|
||||
'expires_at' => strtotime($body['expireTime']),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|null $httpHandler
|
||||
* @param array<mixed> $headers [optional] Metrics headers to be inserted
|
||||
* into the token endpoint request present.
|
||||
*
|
||||
* @return array<mixed> {
|
||||
* A set of auth related metadata, containing the following
|
||||
*
|
||||
* @type string $access_token
|
||||
* @type int $expires_at (impersonated service accounts only)
|
||||
* @type int $expires_in (identity pool only)
|
||||
* @type string $issued_token_type (identity pool only)
|
||||
* @type string $token_type (identity pool only)
|
||||
* }
|
||||
*/
|
||||
public function fetchAuthToken(?callable $httpHandler = null, array $headers = [])
|
||||
{
|
||||
$stsToken = $this->auth->fetchAuthToken($httpHandler, $headers);
|
||||
|
||||
if (isset($this->serviceAccountImpersonationUrl)) {
|
||||
return $this->lastImpersonatedAccessToken = $this->getImpersonatedAccessToken(
|
||||
$stsToken['access_token'],
|
||||
$httpHandler
|
||||
);
|
||||
}
|
||||
|
||||
return $stsToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache token key for the credentials.
|
||||
* The cache token key format depends on the type of source
|
||||
* The format for the cache key one of the following:
|
||||
* FetcherCacheKey.Scope.[ServiceAccount].[TokenType].[WorkforcePoolUserProject]
|
||||
* FetcherCacheKey.Audience.[ServiceAccount].[TokenType].[WorkforcePoolUserProject]
|
||||
*
|
||||
* @return ?string;
|
||||
*/
|
||||
public function getCacheKey(): ?string
|
||||
{
|
||||
$scopeOrAudience = $this->auth->getAudience();
|
||||
if (!$scopeOrAudience) {
|
||||
$scopeOrAudience = $this->auth->getScope();
|
||||
}
|
||||
|
||||
return $this->auth->getSubjectTokenFetcher()->getCacheKey() .
|
||||
'.' . $scopeOrAudience .
|
||||
'.' . ($this->serviceAccountImpersonationUrl ?? '') .
|
||||
'.' . ($this->auth->getSubjectTokenType() ?? '') .
|
||||
'.' . ($this->workforcePoolUserProject ?? '');
|
||||
}
|
||||
|
||||
public function getLastReceivedToken()
|
||||
{
|
||||
return $this->lastImpersonatedAccessToken ?? $this->auth->getLastReceivedToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the quota project used for this API request
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getQuotaProject()
|
||||
{
|
||||
return $this->quotaProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the universe domain used for this API request
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUniverseDomain(): string
|
||||
{
|
||||
return $this->universeDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the project ID.
|
||||
*
|
||||
* @param callable|null $httpHandler Callback which delivers psr7 request
|
||||
* @param string|null $accessToken The access token to use to sign the blob. If
|
||||
* provided, saves a call to the metadata server for a new access
|
||||
* token. **Defaults to** `null`.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProjectId(?callable $httpHandler = null, ?string $accessToken = null)
|
||||
{
|
||||
if (isset($this->projectId)) {
|
||||
return $this->projectId;
|
||||
}
|
||||
|
||||
$projectNumber = $this->getProjectNumber() ?: $this->workforcePoolUserProject;
|
||||
if (!$projectNumber) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_null($httpHandler)) {
|
||||
$httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient());
|
||||
}
|
||||
|
||||
$url = str_replace(
|
||||
'UNIVERSE_DOMAIN',
|
||||
$this->getUniverseDomain(),
|
||||
sprintf(self::CLOUD_RESOURCE_MANAGER_URL, $projectNumber)
|
||||
);
|
||||
|
||||
if (is_null($accessToken)) {
|
||||
$accessToken = $this->fetchAuthToken($httpHandler)['access_token'];
|
||||
}
|
||||
|
||||
$request = new Request('GET', $url, ['authorization' => 'Bearer ' . $accessToken]);
|
||||
$response = $httpHandler($request);
|
||||
|
||||
$body = json_decode((string) $response->getBody(), true);
|
||||
return $this->projectId = $body['projectId'];
|
||||
}
|
||||
|
||||
private function getProjectNumber(): ?string
|
||||
{
|
||||
$parts = explode('/', $this->auth->getAudience());
|
||||
$i = array_search('projects', $parts);
|
||||
return $parts[$i + 1] ?? null;
|
||||
}
|
||||
|
||||
private function isWorkforcePool(): bool
|
||||
{
|
||||
$regex = '#//iam\.googleapis\.com/locations/[^/]+/workforcePools/#';
|
||||
return preg_match($regex, $this->auth->getAudience()) === 1;
|
||||
}
|
||||
}
|
||||
685
vendor/google/auth/src/Credentials/GCECredentials.php
vendored
Normal file
@ -0,0 +1,685 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\Credentials;
|
||||
|
||||
use COM;
|
||||
use com_exception;
|
||||
use Google\Auth\CredentialsLoader;
|
||||
use Google\Auth\GetQuotaProjectInterface;
|
||||
use Google\Auth\HttpHandler\HttpClientCache;
|
||||
use Google\Auth\HttpHandler\HttpHandlerFactory;
|
||||
use Google\Auth\Iam;
|
||||
use Google\Auth\IamSignerTrait;
|
||||
use Google\Auth\ProjectIdProviderInterface;
|
||||
use Google\Auth\SignBlobInterface;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Exception\ServerException;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* GCECredentials supports authorization on Google Compute Engine.
|
||||
*
|
||||
* It can be used to authorize requests using the AuthTokenMiddleware, but will
|
||||
* only succeed if being run on GCE:
|
||||
*
|
||||
* use Google\Auth\Credentials\GCECredentials;
|
||||
* use Google\Auth\Middleware\AuthTokenMiddleware;
|
||||
* use GuzzleHttp\Client;
|
||||
* use GuzzleHttp\HandlerStack;
|
||||
*
|
||||
* $gce = new GCECredentials();
|
||||
* $middleware = new AuthTokenMiddleware($gce);
|
||||
* $stack = HandlerStack::create();
|
||||
* $stack->push($middleware);
|
||||
*
|
||||
* $client = new Client([
|
||||
* 'handler' => $stack,
|
||||
* 'base_uri' => 'https://www.googleapis.com/taskqueue/v1beta2/projects/',
|
||||
* 'auth' => 'google_auth'
|
||||
* ]);
|
||||
*
|
||||
* $res = $client->get('myproject/taskqueues/myqueue');
|
||||
*/
|
||||
class GCECredentials extends CredentialsLoader implements
|
||||
SignBlobInterface,
|
||||
ProjectIdProviderInterface,
|
||||
GetQuotaProjectInterface
|
||||
{
|
||||
use IamSignerTrait;
|
||||
|
||||
// phpcs:disable
|
||||
const cacheKey = 'GOOGLE_AUTH_PHP_GCE';
|
||||
// phpcs:enable
|
||||
|
||||
/**
|
||||
* The metadata IP address on appengine instances.
|
||||
*
|
||||
* The IP is used instead of the domain 'metadata' to avoid slow responses
|
||||
* when not on Compute Engine.
|
||||
*/
|
||||
const METADATA_IP = '169.254.169.254';
|
||||
|
||||
/**
|
||||
* The metadata path of the default token.
|
||||
*/
|
||||
const TOKEN_URI_PATH = 'v1/instance/service-accounts/default/token';
|
||||
|
||||
/**
|
||||
* The metadata path of the default id token.
|
||||
*/
|
||||
const ID_TOKEN_URI_PATH = 'v1/instance/service-accounts/default/identity';
|
||||
|
||||
/**
|
||||
* The metadata path of the client ID.
|
||||
*/
|
||||
const CLIENT_ID_URI_PATH = 'v1/instance/service-accounts/default/email';
|
||||
|
||||
/**
|
||||
* The metadata path of the project ID.
|
||||
*/
|
||||
const PROJECT_ID_URI_PATH = 'v1/project/project-id';
|
||||
|
||||
/**
|
||||
* The metadata path of the project ID.
|
||||
*/
|
||||
const UNIVERSE_DOMAIN_URI_PATH = 'v1/universe/universe-domain';
|
||||
|
||||
/**
|
||||
* The header whose presence indicates GCE presence.
|
||||
*/
|
||||
const FLAVOR_HEADER = 'Metadata-Flavor';
|
||||
|
||||
/**
|
||||
* The Linux file which contains the product name.
|
||||
*/
|
||||
private const GKE_PRODUCT_NAME_FILE = '/sys/class/dmi/id/product_name';
|
||||
|
||||
/**
|
||||
* The Windows Registry key path to the product name
|
||||
*/
|
||||
private const WINDOWS_REGISTRY_KEY_PATH = 'HKEY_LOCAL_MACHINE\\SYSTEM\\HardwareConfig\\Current\\';
|
||||
|
||||
/**
|
||||
* The Windows registry key name for the product name
|
||||
*/
|
||||
private const WINDOWS_REGISTRY_KEY_NAME = 'SystemProductName';
|
||||
|
||||
/**
|
||||
* The Name of the product expected from the windows registry
|
||||
*/
|
||||
private const PRODUCT_NAME = 'Google';
|
||||
|
||||
private const CRED_TYPE = 'mds';
|
||||
|
||||
/**
|
||||
* Note: the explicit `timeout` and `tries` below is a workaround. The underlying
|
||||
* issue is that resolving an unknown host on some networks will take
|
||||
* 20-30 seconds; making this timeout short fixes the issue, but
|
||||
* could lead to false negatives in the event that we are on GCE, but
|
||||
* the metadata resolution was particularly slow. The latter case is
|
||||
* "unlikely" since the expected 4-nines time is about 0.5 seconds.
|
||||
* This allows us to limit the total ping maximum timeout to 1.5 seconds
|
||||
* for developer desktop scenarios.
|
||||
*/
|
||||
const MAX_COMPUTE_PING_TRIES = 3;
|
||||
const COMPUTE_PING_CONNECTION_TIMEOUT_S = 0.5;
|
||||
|
||||
/**
|
||||
* Flag used to ensure that the onGCE test is only done once;.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $hasCheckedOnGce = false;
|
||||
|
||||
/**
|
||||
* Flag that stores the value of the onGCE check.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $isOnGce = false;
|
||||
|
||||
/**
|
||||
* Result of fetchAuthToken.
|
||||
*
|
||||
* @var array<mixed>
|
||||
*/
|
||||
protected $lastReceivedToken;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $clientName;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $projectId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $tokenUri;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $targetAudience;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $quotaProject;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $serviceAccountIdentity;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private ?string $universeDomain;
|
||||
|
||||
/**
|
||||
* @param Iam|null $iam [optional] An IAM instance.
|
||||
* @param string|string[] $scope [optional] the scope of the access request,
|
||||
* expressed either as an array or as a space-delimited string.
|
||||
* @param string $targetAudience [optional] The audience for the ID token.
|
||||
* @param string $quotaProject [optional] Specifies a project to bill for access
|
||||
* charges associated with the request.
|
||||
* @param string $serviceAccountIdentity [optional] Specify a service
|
||||
* account identity name to use instead of "default".
|
||||
* @param string|null $universeDomain [optional] Specify a universe domain to use
|
||||
* instead of fetching one from the metadata server.
|
||||
*/
|
||||
public function __construct(
|
||||
?Iam $iam = null,
|
||||
$scope = null,
|
||||
$targetAudience = null,
|
||||
$quotaProject = null,
|
||||
$serviceAccountIdentity = null,
|
||||
?string $universeDomain = null
|
||||
) {
|
||||
$this->iam = $iam;
|
||||
|
||||
if ($scope && $targetAudience) {
|
||||
throw new InvalidArgumentException(
|
||||
'Scope and targetAudience cannot both be supplied'
|
||||
);
|
||||
}
|
||||
|
||||
$tokenUri = self::getTokenUri($serviceAccountIdentity);
|
||||
if ($scope) {
|
||||
if (is_string($scope)) {
|
||||
$scope = explode(' ', $scope);
|
||||
}
|
||||
|
||||
$scope = implode(',', $scope);
|
||||
|
||||
$tokenUri = $tokenUri . '?scopes=' . $scope;
|
||||
} elseif ($targetAudience) {
|
||||
$tokenUri = self::getIdTokenUri($serviceAccountIdentity);
|
||||
$tokenUri = $tokenUri . '?audience=' . $targetAudience;
|
||||
$this->targetAudience = $targetAudience;
|
||||
}
|
||||
|
||||
$this->tokenUri = $tokenUri;
|
||||
$this->quotaProject = $quotaProject;
|
||||
$this->serviceAccountIdentity = $serviceAccountIdentity;
|
||||
$this->universeDomain = $universeDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* The full uri for accessing the default token.
|
||||
*
|
||||
* @param string $serviceAccountIdentity [optional] Specify a service
|
||||
* account identity name to use instead of "default".
|
||||
* @return string
|
||||
*/
|
||||
public static function getTokenUri($serviceAccountIdentity = null)
|
||||
{
|
||||
$base = 'http://' . self::METADATA_IP . '/computeMetadata/';
|
||||
$base .= self::TOKEN_URI_PATH;
|
||||
|
||||
if ($serviceAccountIdentity) {
|
||||
return str_replace(
|
||||
'/default/',
|
||||
'/' . $serviceAccountIdentity . '/',
|
||||
$base
|
||||
);
|
||||
}
|
||||
return $base;
|
||||
}
|
||||
|
||||
/**
|
||||
* The full uri for accessing the default service account.
|
||||
*
|
||||
* @param string $serviceAccountIdentity [optional] Specify a service
|
||||
* account identity name to use instead of "default".
|
||||
* @return string
|
||||
*/
|
||||
public static function getClientNameUri($serviceAccountIdentity = null)
|
||||
{
|
||||
$base = 'http://' . self::METADATA_IP . '/computeMetadata/';
|
||||
$base .= self::CLIENT_ID_URI_PATH;
|
||||
|
||||
if ($serviceAccountIdentity) {
|
||||
return str_replace(
|
||||
'/default/',
|
||||
'/' . $serviceAccountIdentity . '/',
|
||||
$base
|
||||
);
|
||||
}
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
/**
|
||||
* The full uri for accesesing the default identity token.
|
||||
*
|
||||
* @param string $serviceAccountIdentity [optional] Specify a service
|
||||
* account identity name to use instead of "default".
|
||||
* @return string
|
||||
*/
|
||||
private static function getIdTokenUri($serviceAccountIdentity = null)
|
||||
{
|
||||
$base = 'http://' . self::METADATA_IP . '/computeMetadata/';
|
||||
$base .= self::ID_TOKEN_URI_PATH;
|
||||
|
||||
if ($serviceAccountIdentity) {
|
||||
return str_replace(
|
||||
'/default/',
|
||||
'/' . $serviceAccountIdentity . '/',
|
||||
$base
|
||||
);
|
||||
}
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
/**
|
||||
* The full uri for accessing the default project ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getProjectIdUri()
|
||||
{
|
||||
$base = 'http://' . self::METADATA_IP . '/computeMetadata/';
|
||||
|
||||
return $base . self::PROJECT_ID_URI_PATH;
|
||||
}
|
||||
|
||||
/**
|
||||
* The full uri for accessing the default universe domain.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getUniverseDomainUri()
|
||||
{
|
||||
$base = 'http://' . self::METADATA_IP . '/computeMetadata/';
|
||||
|
||||
return $base . self::UNIVERSE_DOMAIN_URI_PATH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this an App Engine Flexible instance, by accessing the
|
||||
* GAE_INSTANCE environment variable.
|
||||
*
|
||||
* @return bool true if this an App Engine Flexible Instance, false otherwise
|
||||
*/
|
||||
public static function onAppEngineFlexible()
|
||||
{
|
||||
return substr((string) getenv('GAE_INSTANCE'), 0, 4) === 'aef-';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this a GCE instance, by accessing the expected metadata
|
||||
* host.
|
||||
* If $httpHandler is not specified a the default HttpHandler is used.
|
||||
*
|
||||
* @param callable|null $httpHandler callback which delivers psr7 request
|
||||
* @return bool True if this a GCEInstance, false otherwise
|
||||
*/
|
||||
public static function onGce(?callable $httpHandler = null)
|
||||
{
|
||||
$httpHandler = $httpHandler
|
||||
?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
|
||||
|
||||
$checkUri = 'http://' . self::METADATA_IP;
|
||||
for ($i = 1; $i <= self::MAX_COMPUTE_PING_TRIES; $i++) {
|
||||
try {
|
||||
// Comment from: oauth2client/client.py
|
||||
//
|
||||
// Note: the explicit `timeout` below is a workaround. The underlying
|
||||
// issue is that resolving an unknown host on some networks will take
|
||||
// 20-30 seconds; making this timeout short fixes the issue, but
|
||||
// could lead to false negatives in the event that we are on GCE, but
|
||||
// the metadata resolution was particularly slow. The latter case is
|
||||
// "unlikely".
|
||||
$resp = $httpHandler(
|
||||
new Request(
|
||||
'GET',
|
||||
$checkUri,
|
||||
[
|
||||
self::FLAVOR_HEADER => 'Google',
|
||||
self::$metricMetadataKey => self::getMetricsHeader('', 'mds')
|
||||
]
|
||||
),
|
||||
['timeout' => self::COMPUTE_PING_CONNECTION_TIMEOUT_S]
|
||||
);
|
||||
|
||||
return $resp->getHeaderLine(self::FLAVOR_HEADER) == 'Google';
|
||||
} catch (ClientException $e) {
|
||||
} catch (ServerException $e) {
|
||||
} catch (RequestException $e) {
|
||||
} catch (ConnectException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (PHP_OS === 'Windows' || PHP_OS === 'WINNT') {
|
||||
return self::detectResidencyWindows(
|
||||
self::WINDOWS_REGISTRY_KEY_PATH . self::WINDOWS_REGISTRY_KEY_NAME
|
||||
);
|
||||
}
|
||||
|
||||
// Detect GCE residency on Linux
|
||||
return self::detectResidencyLinux(self::GKE_PRODUCT_NAME_FILE);
|
||||
}
|
||||
|
||||
private static function detectResidencyLinux(string $productNameFile): bool
|
||||
{
|
||||
if (file_exists($productNameFile)) {
|
||||
$productName = trim((string) file_get_contents($productNameFile));
|
||||
return 0 === strpos($productName, self::PRODUCT_NAME);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function detectResidencyWindows(string $registryProductKey): bool
|
||||
{
|
||||
if (!class_exists(COM::class)) {
|
||||
// the COM extension must be installed and enabled to detect Windows residency
|
||||
// see https://www.php.net/manual/en/book.com.php
|
||||
return false;
|
||||
}
|
||||
|
||||
$shell = new COM('WScript.Shell');
|
||||
$productName = null;
|
||||
|
||||
try {
|
||||
$productName = $shell->regRead($registryProductKey);
|
||||
} catch (com_exception) {
|
||||
// This means that we tried to read a key that doesn't exist on the registry
|
||||
// which might mean that it is a windows instance that is not on GCE
|
||||
return false;
|
||||
}
|
||||
|
||||
return 0 === strpos($productName, self::PRODUCT_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements FetchAuthTokenInterface#fetchAuthToken.
|
||||
*
|
||||
* Fetches the auth tokens from the GCE metadata host if it is available.
|
||||
* If $httpHandler is not specified a the default HttpHandler is used.
|
||||
*
|
||||
* @param callable|null $httpHandler callback which delivers psr7 request
|
||||
* @param array<mixed> $headers [optional] Headers to be inserted
|
||||
* into the token endpoint request present.
|
||||
*
|
||||
* @return array<mixed> {
|
||||
* A set of auth related metadata, based on the token type.
|
||||
*
|
||||
* @type string $access_token for access tokens
|
||||
* @type int $expires_in for access tokens
|
||||
* @type string $token_type for access tokens
|
||||
* @type string $id_token for ID tokens
|
||||
* }
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function fetchAuthToken(?callable $httpHandler = null, array $headers = [])
|
||||
{
|
||||
$httpHandler = $httpHandler
|
||||
?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
|
||||
|
||||
if (!$this->hasCheckedOnGce) {
|
||||
$this->isOnGce = self::onGce($httpHandler);
|
||||
$this->hasCheckedOnGce = true;
|
||||
}
|
||||
if (!$this->isOnGce) {
|
||||
return []; // return an empty array with no access token
|
||||
}
|
||||
|
||||
$response = $this->getFromMetadata(
|
||||
$httpHandler,
|
||||
$this->tokenUri,
|
||||
$this->applyTokenEndpointMetrics($headers, $this->targetAudience ? 'it' : 'at')
|
||||
);
|
||||
|
||||
if ($this->targetAudience) {
|
||||
return $this->lastReceivedToken = ['id_token' => $response];
|
||||
}
|
||||
|
||||
if (null === $json = json_decode($response, true)) {
|
||||
throw new \Exception('Invalid JSON response');
|
||||
}
|
||||
|
||||
$json['expires_at'] = time() + $json['expires_in'];
|
||||
|
||||
// store this so we can retrieve it later
|
||||
$this->lastReceivedToken = $json;
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Cache Key for the credential token.
|
||||
* The format for the cache key is:
|
||||
* TokenURI
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheKey()
|
||||
{
|
||||
return $this->tokenUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>|null
|
||||
*/
|
||||
public function getLastReceivedToken()
|
||||
{
|
||||
if ($this->lastReceivedToken) {
|
||||
if (array_key_exists('id_token', $this->lastReceivedToken)) {
|
||||
return $this->lastReceivedToken;
|
||||
}
|
||||
|
||||
return [
|
||||
'access_token' => $this->lastReceivedToken['access_token'],
|
||||
'expires_at' => $this->lastReceivedToken['expires_at']
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client name from GCE metadata.
|
||||
*
|
||||
* Subsequent calls will return a cached value.
|
||||
*
|
||||
* @param callable|null $httpHandler callback which delivers psr7 request
|
||||
* @return string
|
||||
*/
|
||||
public function getClientName(?callable $httpHandler = null)
|
||||
{
|
||||
if ($this->clientName) {
|
||||
return $this->clientName;
|
||||
}
|
||||
|
||||
$httpHandler = $httpHandler
|
||||
?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
|
||||
|
||||
if (!$this->hasCheckedOnGce) {
|
||||
$this->isOnGce = self::onGce($httpHandler);
|
||||
$this->hasCheckedOnGce = true;
|
||||
}
|
||||
|
||||
if (!$this->isOnGce) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->clientName = $this->getFromMetadata(
|
||||
$httpHandler,
|
||||
self::getClientNameUri($this->serviceAccountIdentity)
|
||||
);
|
||||
|
||||
return $this->clientName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the default Project ID from compute engine.
|
||||
*
|
||||
* Returns null if called outside GCE.
|
||||
*
|
||||
* @param callable|null $httpHandler Callback which delivers psr7 request
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProjectId(?callable $httpHandler = null)
|
||||
{
|
||||
if ($this->projectId) {
|
||||
return $this->projectId;
|
||||
}
|
||||
|
||||
$httpHandler = $httpHandler
|
||||
?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
|
||||
|
||||
if (!$this->hasCheckedOnGce) {
|
||||
$this->isOnGce = self::onGce($httpHandler);
|
||||
$this->hasCheckedOnGce = true;
|
||||
}
|
||||
|
||||
if (!$this->isOnGce) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->projectId = $this->getFromMetadata($httpHandler, self::getProjectIdUri());
|
||||
return $this->projectId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the default universe domain from the metadata server.
|
||||
*
|
||||
* @param callable|null $httpHandler Callback which delivers psr7 request
|
||||
* @return string
|
||||
*/
|
||||
public function getUniverseDomain(?callable $httpHandler = null): string
|
||||
{
|
||||
if (null !== $this->universeDomain) {
|
||||
return $this->universeDomain;
|
||||
}
|
||||
|
||||
$httpHandler = $httpHandler
|
||||
?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
|
||||
|
||||
if (!$this->hasCheckedOnGce) {
|
||||
$this->isOnGce = self::onGce($httpHandler);
|
||||
$this->hasCheckedOnGce = true;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->universeDomain = $this->getFromMetadata(
|
||||
$httpHandler,
|
||||
self::getUniverseDomainUri()
|
||||
);
|
||||
} catch (ClientException $e) {
|
||||
// If the metadata server exists, but returns a 404 for the universe domain, the auth
|
||||
// libraries should safely assume this is an older metadata server running in GCU, and
|
||||
// should return the default universe domain.
|
||||
if (!$e->hasResponse() || 404 != $e->getResponse()->getStatusCode()) {
|
||||
throw $e;
|
||||
}
|
||||
$this->universeDomain = self::DEFAULT_UNIVERSE_DOMAIN;
|
||||
}
|
||||
|
||||
// We expect in some cases the metadata server will return an empty string for the universe
|
||||
// domain. In this case, the auth library MUST return the default universe domain.
|
||||
if ('' === $this->universeDomain) {
|
||||
$this->universeDomain = self::DEFAULT_UNIVERSE_DOMAIN;
|
||||
}
|
||||
|
||||
return $this->universeDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the value of a GCE metadata server URI.
|
||||
*
|
||||
* @param callable $httpHandler An HTTP Handler to deliver PSR7 requests.
|
||||
* @param string $uri The metadata URI.
|
||||
* @param array<mixed> $headers [optional] If present, add these headers to the token
|
||||
* endpoint request.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getFromMetadata(callable $httpHandler, $uri, array $headers = [])
|
||||
{
|
||||
$resp = $httpHandler(
|
||||
new Request(
|
||||
'GET',
|
||||
$uri,
|
||||
[self::FLAVOR_HEADER => 'Google'] + $headers
|
||||
)
|
||||
);
|
||||
|
||||
return (string) $resp->getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the quota project used for this API request
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getQuotaProject()
|
||||
{
|
||||
return $this->quotaProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not we've already checked the GCE environment.
|
||||
*
|
||||
* @param bool $isOnGce
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setIsOnGce($isOnGce)
|
||||
{
|
||||
// Implicitly set hasCheckedGce to true
|
||||
$this->hasCheckedOnGce = true;
|
||||
|
||||
// Set isOnGce
|
||||
$this->isOnGce = $isOnGce;
|
||||
}
|
||||
|
||||
protected function getCredType(): string
|
||||
{
|
||||
return self::CRED_TYPE;
|
||||
}
|
||||
}
|
||||
91
vendor/google/auth/src/Credentials/IAMCredentials.php
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\Credentials;
|
||||
|
||||
/**
|
||||
* Authenticates requests using IAM credentials.
|
||||
*/
|
||||
class IAMCredentials
|
||||
{
|
||||
const SELECTOR_KEY = 'x-goog-iam-authority-selector';
|
||||
const TOKEN_KEY = 'x-goog-iam-authorization-token';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $selector;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $token;
|
||||
|
||||
/**
|
||||
* @param string $selector the IAM selector
|
||||
* @param string $token the IAM token
|
||||
*/
|
||||
public function __construct($selector, $token)
|
||||
{
|
||||
if (!is_string($selector)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'selector must be a string'
|
||||
);
|
||||
}
|
||||
if (!is_string($token)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'token must be a string'
|
||||
);
|
||||
}
|
||||
|
||||
$this->selector = $selector;
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* export a callback function which updates runtime metadata.
|
||||
*
|
||||
* @return callable updateMetadata function
|
||||
*/
|
||||
public function getUpdateMetadataFunc()
|
||||
{
|
||||
return [$this, 'updateMetadata'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates metadata with the appropriate header metadata.
|
||||
*
|
||||
* @param array<mixed> $metadata metadata hashmap
|
||||
* @param string $unusedAuthUri optional auth uri
|
||||
* @param callable|null $httpHandler callback which delivers psr7 request
|
||||
* Note: this param is unused here, only included here for
|
||||
* consistency with other credentials class
|
||||
*
|
||||
* @return array<mixed> updated metadata hashmap
|
||||
*/
|
||||
public function updateMetadata(
|
||||
$metadata,
|
||||
$unusedAuthUri = null,
|
||||
?callable $httpHandler = null
|
||||
) {
|
||||
$metadata_copy = $metadata;
|
||||
$metadata_copy[self::SELECTOR_KEY] = $this->selector;
|
||||
$metadata_copy[self::TOKEN_KEY] = $this->token;
|
||||
|
||||
return $metadata_copy;
|
||||
}
|
||||
}
|
||||
295
vendor/google/auth/src/Credentials/ImpersonatedServiceAccountCredentials.php
vendored
Normal file
@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2022 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\Credentials;
|
||||
|
||||
use Google\Auth\CacheTrait;
|
||||
use Google\Auth\CredentialsLoader;
|
||||
use Google\Auth\FetchAuthTokenInterface;
|
||||
use Google\Auth\GetUniverseDomainInterface;
|
||||
use Google\Auth\HttpHandler\HttpClientCache;
|
||||
use Google\Auth\HttpHandler\HttpHandlerFactory;
|
||||
use Google\Auth\IamSignerTrait;
|
||||
use Google\Auth\SignBlobInterface;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* **IMPORTANT**:
|
||||
* This class does not validate the credential configuration. A security
|
||||
* risk occurs when a credential configuration configured with malicious urls
|
||||
* is used.
|
||||
* When the credential configuration is accepted from an
|
||||
* untrusted source, you should validate it before creating this class.
|
||||
* @see https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
|
||||
*/
|
||||
class ImpersonatedServiceAccountCredentials extends CredentialsLoader implements
|
||||
SignBlobInterface,
|
||||
GetUniverseDomainInterface
|
||||
{
|
||||
use CacheTrait;
|
||||
use IamSignerTrait;
|
||||
|
||||
private const CRED_TYPE = 'imp';
|
||||
private const IAM_SCOPE = 'https://www.googleapis.com/auth/iam';
|
||||
private const ID_TOKEN_IMPERSONATION_URL =
|
||||
'https://iamcredentials.UNIVERSE_DOMAIN/v1/projects/-/serviceAccounts/%s:generateIdToken';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $impersonatedServiceAccountName;
|
||||
|
||||
protected FetchAuthTokenInterface $sourceCredentials;
|
||||
|
||||
private string $serviceAccountImpersonationUrl;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $delegates;
|
||||
|
||||
/**
|
||||
* @var string|string[]
|
||||
*/
|
||||
private string|array $targetScope;
|
||||
|
||||
private int $lifetime;
|
||||
|
||||
/**
|
||||
* Instantiate an instance of ImpersonatedServiceAccountCredentials from a credentials file that
|
||||
* has be created with the --impersonate-service-account flag.
|
||||
*
|
||||
* @param string|string[]|null $scope The scope of the access request, expressed either as an
|
||||
* array or as a space-delimited string.
|
||||
* @param string|array<mixed> $jsonKey JSON credential file path or JSON array credentials {
|
||||
* JSON credentials as an associative array.
|
||||
*
|
||||
* @type string $service_account_impersonation_url The URL to the service account
|
||||
* @type string|FetchAuthTokenInterface $source_credentials The source credentials to impersonate
|
||||
* @type int $lifetime The lifetime of the impersonated credentials
|
||||
* @type string[] $delegates The delegates to impersonate
|
||||
* }
|
||||
* @param string|null $targetAudience The audience to request an ID token.
|
||||
*/
|
||||
public function __construct(
|
||||
string|array|null $scope,
|
||||
string|array $jsonKey,
|
||||
private ?string $targetAudience = null
|
||||
) {
|
||||
if (is_string($jsonKey)) {
|
||||
if (!file_exists($jsonKey)) {
|
||||
throw new InvalidArgumentException('file does not exist');
|
||||
}
|
||||
$json = file_get_contents($jsonKey);
|
||||
if (!$jsonKey = json_decode((string) $json, true)) {
|
||||
throw new LogicException('invalid json for auth config');
|
||||
}
|
||||
}
|
||||
if (!array_key_exists('service_account_impersonation_url', $jsonKey)) {
|
||||
throw new LogicException(
|
||||
'json key is missing the service_account_impersonation_url field'
|
||||
);
|
||||
}
|
||||
if (!array_key_exists('source_credentials', $jsonKey)) {
|
||||
throw new LogicException('json key is missing the source_credentials field');
|
||||
}
|
||||
if ($scope && $targetAudience) {
|
||||
throw new InvalidArgumentException(
|
||||
'Scope and targetAudience cannot both be supplied'
|
||||
);
|
||||
}
|
||||
if (is_array($jsonKey['source_credentials'])) {
|
||||
if (!array_key_exists('type', $jsonKey['source_credentials'])) {
|
||||
throw new InvalidArgumentException('json key source credentials are missing the type field');
|
||||
}
|
||||
if (
|
||||
$targetAudience !== null
|
||||
&& $jsonKey['source_credentials']['type'] === 'service_account'
|
||||
) {
|
||||
// Service account tokens MUST request a scope, and as this token is only used to impersonate
|
||||
// an ID token, the narrowest scope we can request is `iam`.
|
||||
$scope = self::IAM_SCOPE;
|
||||
}
|
||||
$jsonKey['source_credentials'] = match ($jsonKey['source_credentials']['type'] ?? null) {
|
||||
// Do not pass $defaultScope to ServiceAccountCredentials
|
||||
'service_account' => new ServiceAccountCredentials($scope, $jsonKey['source_credentials']),
|
||||
'authorized_user' => new UserRefreshCredentials($scope, $jsonKey['source_credentials']),
|
||||
'external_account' => new ExternalAccountCredentials($scope, $jsonKey['source_credentials']),
|
||||
default => throw new \InvalidArgumentException('invalid value in the type field'),
|
||||
};
|
||||
}
|
||||
|
||||
$this->targetScope = $scope ?? [];
|
||||
$this->lifetime = $jsonKey['lifetime'] ?? 3600;
|
||||
$this->delegates = $jsonKey['delegates'] ?? [];
|
||||
|
||||
$this->serviceAccountImpersonationUrl = $jsonKey['service_account_impersonation_url'];
|
||||
$this->impersonatedServiceAccountName = $this->getImpersonatedServiceAccountNameFromUrl(
|
||||
$this->serviceAccountImpersonationUrl
|
||||
);
|
||||
|
||||
$this->sourceCredentials = $jsonKey['source_credentials'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for extracting the Server Account Name from the URL saved in the account
|
||||
* credentials file.
|
||||
*
|
||||
* @param $serviceAccountImpersonationUrl string URL from "service_account_impersonation_url"
|
||||
* @return string Service account email or ID.
|
||||
*/
|
||||
private function getImpersonatedServiceAccountNameFromUrl(
|
||||
string $serviceAccountImpersonationUrl
|
||||
): string {
|
||||
$fields = explode('/', $serviceAccountImpersonationUrl);
|
||||
$lastField = end($fields);
|
||||
$splitter = explode(':', $lastField);
|
||||
return $splitter[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client name from the keyfile
|
||||
*
|
||||
* In this implementation, it will return the issuers email from the oauth token.
|
||||
*
|
||||
* @param callable|null $unusedHttpHandler not used by this credentials type.
|
||||
* @return string Token issuer email
|
||||
*/
|
||||
public function getClientName(?callable $unusedHttpHandler = null)
|
||||
{
|
||||
return $this->impersonatedServiceAccountName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|null $httpHandler
|
||||
*
|
||||
* @return array<mixed> {
|
||||
* A set of auth related metadata, containing the following
|
||||
*
|
||||
* @type string $access_token
|
||||
* @type int $expires_in
|
||||
* @type string $scope
|
||||
* @type string $token_type
|
||||
* @type string $id_token
|
||||
* }
|
||||
*/
|
||||
public function fetchAuthToken(?callable $httpHandler = null)
|
||||
{
|
||||
$httpHandler = $httpHandler ?? HttpHandlerFactory::build(HttpClientCache::getHttpClient());
|
||||
|
||||
// The FetchAuthTokenInterface technically does not have a "headers" argument, but all of
|
||||
// the implementations do. Additionally, passing in more parameters than the function has
|
||||
// defined is allowed in PHP. So we'll just ignore the phpstan error here.
|
||||
// @phpstan-ignore-next-line
|
||||
$authToken = $this->sourceCredentials->fetchAuthToken(
|
||||
$httpHandler,
|
||||
$this->applyTokenEndpointMetrics([], 'at')
|
||||
);
|
||||
|
||||
$headers = $this->applyTokenEndpointMetrics([
|
||||
'Content-Type' => 'application/json',
|
||||
'Cache-Control' => 'no-store',
|
||||
'Authorization' => sprintf('Bearer %s', $authToken['access_token'] ?? $authToken['id_token']),
|
||||
], $this->isIdTokenRequest() ? 'it' : 'at');
|
||||
|
||||
$body = match ($this->isIdTokenRequest()) {
|
||||
true => [
|
||||
'audience' => $this->targetAudience,
|
||||
'includeEmail' => true,
|
||||
],
|
||||
false => [
|
||||
'scope' => $this->targetScope,
|
||||
'delegates' => $this->delegates,
|
||||
'lifetime' => sprintf('%ss', $this->lifetime),
|
||||
]
|
||||
};
|
||||
|
||||
$url = $this->serviceAccountImpersonationUrl;
|
||||
if ($this->isIdTokenRequest()) {
|
||||
$regex = '/serviceAccounts\/(?<email>[^:]+):generateAccessToken$/';
|
||||
if (!preg_match($regex, $url, $matches)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Invalid service account impersonation URL - unable to parse service account email'
|
||||
);
|
||||
}
|
||||
$url = str_replace(
|
||||
'UNIVERSE_DOMAIN',
|
||||
$this->getUniverseDomain(),
|
||||
sprintf(self::ID_TOKEN_IMPERSONATION_URL, $matches['email'])
|
||||
);
|
||||
}
|
||||
|
||||
$request = new Request(
|
||||
'POST',
|
||||
$url,
|
||||
$headers,
|
||||
(string) json_encode($body)
|
||||
);
|
||||
|
||||
$response = $httpHandler($request);
|
||||
$body = json_decode((string) $response->getBody(), true);
|
||||
|
||||
return match ($this->isIdTokenRequest()) {
|
||||
true => ['id_token' => $body['token']],
|
||||
false => [
|
||||
'access_token' => $body['accessToken'],
|
||||
'expires_at' => strtotime($body['expireTime']),
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Cache Key for the credentials
|
||||
* The cache key is the same as the UserRefreshCredentials class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheKey()
|
||||
{
|
||||
return $this->getFullCacheKey(
|
||||
$this->serviceAccountImpersonationUrl . $this->sourceCredentials->getCacheKey()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function getLastReceivedToken()
|
||||
{
|
||||
return $this->sourceCredentials->getLastReceivedToken();
|
||||
}
|
||||
|
||||
protected function getCredType(): string
|
||||
{
|
||||
return self::CRED_TYPE;
|
||||
}
|
||||
|
||||
private function isIdTokenRequest(): bool
|
||||
{
|
||||
return !is_null($this->targetAudience);
|
||||
}
|
||||
|
||||
public function getUniverseDomain(): string
|
||||
{
|
||||
return $this->sourceCredentials instanceof GetUniverseDomainInterface
|
||||
? $this->sourceCredentials->getUniverseDomain()
|
||||
: self::DEFAULT_UNIVERSE_DOMAIN;
|
||||
}
|
||||
}
|
||||
68
vendor/google/auth/src/Credentials/InsecureCredentials.php
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2018 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\Credentials;
|
||||
|
||||
use Google\Auth\FetchAuthTokenInterface;
|
||||
|
||||
/**
|
||||
* Provides a set of credentials that will always return an empty access token.
|
||||
* This is useful for APIs which do not require authentication, for local
|
||||
* service emulators, and for testing.
|
||||
*/
|
||||
class InsecureCredentials implements FetchAuthTokenInterface
|
||||
{
|
||||
/**
|
||||
* @var array{access_token:string}
|
||||
*/
|
||||
private $token = [
|
||||
'access_token' => ''
|
||||
];
|
||||
|
||||
/**
|
||||
* Fetches the auth token. In this case it returns an empty string.
|
||||
*
|
||||
* @param callable|null $httpHandler
|
||||
* @return array{access_token:string} A set of auth related metadata
|
||||
*/
|
||||
public function fetchAuthToken(?callable $httpHandler = null)
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache key. In this case it returns a null value, disabling
|
||||
* caching.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCacheKey()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the last received token. In this case, it returns the same empty string
|
||||
* auth token.
|
||||
*
|
||||
* @return array{access_token:string}
|
||||
*/
|
||||
public function getLastReceivedToken()
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
}
|
||||
457
vendor/google/auth/src/Credentials/ServiceAccountCredentials.php
vendored
Normal file
@ -0,0 +1,457 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\Credentials;
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use Google\Auth\CredentialsLoader;
|
||||
use Google\Auth\GetQuotaProjectInterface;
|
||||
use Google\Auth\Iam;
|
||||
use Google\Auth\OAuth2;
|
||||
use Google\Auth\ProjectIdProviderInterface;
|
||||
use Google\Auth\ServiceAccountSignerTrait;
|
||||
use Google\Auth\SignBlobInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* ServiceAccountCredentials supports authorization using a Google service
|
||||
* account.
|
||||
*
|
||||
* (cf https://developers.google.com/accounts/docs/OAuth2ServiceAccount)
|
||||
*
|
||||
* It's initialized using the json key file that's downloadable from developer
|
||||
* console, which should contain a private_key and client_email fields that it
|
||||
* uses.
|
||||
*
|
||||
* Use it with AuthTokenMiddleware to authorize http requests:
|
||||
*
|
||||
* use Google\Auth\Credentials\ServiceAccountCredentials;
|
||||
* use Google\Auth\Middleware\AuthTokenMiddleware;
|
||||
* use GuzzleHttp\Client;
|
||||
* use GuzzleHttp\HandlerStack;
|
||||
*
|
||||
* $sa = new ServiceAccountCredentials(
|
||||
* 'https://www.googleapis.com/auth/taskqueue',
|
||||
* '/path/to/your/json/key_file.json'
|
||||
* );
|
||||
* $middleware = new AuthTokenMiddleware($sa);
|
||||
* $stack = HandlerStack::create();
|
||||
* $stack->push($middleware);
|
||||
*
|
||||
* $client = new Client([
|
||||
* 'handler' => $stack,
|
||||
* 'base_uri' => 'https://www.googleapis.com/taskqueue/v1beta2/projects/',
|
||||
* 'auth' => 'google_auth' // authorize all requests
|
||||
* ]);
|
||||
*
|
||||
* $res = $client->get('myproject/taskqueues/myqueue');
|
||||
*/
|
||||
class ServiceAccountCredentials extends CredentialsLoader implements
|
||||
GetQuotaProjectInterface,
|
||||
SignBlobInterface,
|
||||
ProjectIdProviderInterface
|
||||
{
|
||||
use ServiceAccountSignerTrait;
|
||||
|
||||
/**
|
||||
* Used in observability metric headers
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private const CRED_TYPE = 'sa';
|
||||
private const IAM_SCOPE = 'https://www.googleapis.com/auth/iam';
|
||||
|
||||
/**
|
||||
* The OAuth2 instance used to conduct authorization.
|
||||
*
|
||||
* @var OAuth2
|
||||
*/
|
||||
protected $auth;
|
||||
|
||||
/**
|
||||
* The quota project associated with the JSON credentials
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $quotaProject;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $projectId;
|
||||
|
||||
/**
|
||||
* @var array<mixed>|null
|
||||
*/
|
||||
private $lastReceivedJwtAccessToken;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $useJwtAccessWithScope = false;
|
||||
|
||||
/**
|
||||
* @var ServiceAccountJwtAccessCredentials|null
|
||||
*/
|
||||
private $jwtAccessCredentials;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $universeDomain;
|
||||
|
||||
/**
|
||||
* Whether this is an ID token request or an access token request. Used when
|
||||
* building the metric header.
|
||||
*/
|
||||
private bool $isIdTokenRequest = false;
|
||||
|
||||
/**
|
||||
* Create a new ServiceAccountCredentials.
|
||||
*
|
||||
* @param string|string[]|null $scope the scope of the access request, expressed
|
||||
* either as an Array or as a space-delimited String.
|
||||
* @param string|array<mixed> $jsonKey JSON credential file path or JSON credentials
|
||||
* as an associative array
|
||||
* @param string $sub an email address account to impersonate, in situations when
|
||||
* the service account has been delegated domain wide access.
|
||||
* @param string $targetAudience The audience for the ID token.
|
||||
*/
|
||||
public function __construct(
|
||||
$scope,
|
||||
$jsonKey,
|
||||
$sub = null,
|
||||
$targetAudience = null
|
||||
) {
|
||||
if (is_string($jsonKey)) {
|
||||
if (!file_exists($jsonKey)) {
|
||||
throw new \InvalidArgumentException('file does not exist');
|
||||
}
|
||||
$jsonKeyStream = file_get_contents($jsonKey);
|
||||
if (!$jsonKey = json_decode((string) $jsonKeyStream, true)) {
|
||||
throw new \LogicException('invalid json for auth config');
|
||||
}
|
||||
}
|
||||
if (!array_key_exists('client_email', $jsonKey)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'json key is missing the client_email field'
|
||||
);
|
||||
}
|
||||
if (!array_key_exists('private_key', $jsonKey)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'json key is missing the private_key field'
|
||||
);
|
||||
}
|
||||
if (array_key_exists('quota_project_id', $jsonKey)) {
|
||||
$this->quotaProject = (string) $jsonKey['quota_project_id'];
|
||||
}
|
||||
if ($scope && $targetAudience) {
|
||||
throw new InvalidArgumentException(
|
||||
'Scope and targetAudience cannot both be supplied'
|
||||
);
|
||||
}
|
||||
$additionalClaims = [];
|
||||
if ($targetAudience) {
|
||||
$additionalClaims = ['target_audience' => $targetAudience];
|
||||
$this->isIdTokenRequest = true;
|
||||
}
|
||||
$this->auth = new OAuth2([
|
||||
'audience' => self::TOKEN_CREDENTIAL_URI,
|
||||
'issuer' => $jsonKey['client_email'],
|
||||
'scope' => $scope,
|
||||
'signingAlgorithm' => 'RS256',
|
||||
'signingKey' => $jsonKey['private_key'],
|
||||
'signingKeyId' => $jsonKey['private_key_id'] ?? null,
|
||||
'sub' => $sub,
|
||||
'tokenCredentialUri' => self::TOKEN_CREDENTIAL_URI,
|
||||
'additionalClaims' => $additionalClaims,
|
||||
]);
|
||||
|
||||
$this->projectId = $jsonKey['project_id'] ?? null;
|
||||
$this->universeDomain = $jsonKey['universe_domain'] ?? self::DEFAULT_UNIVERSE_DOMAIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* When called, the ServiceAccountCredentials will use an instance of
|
||||
* ServiceAccountJwtAccessCredentials to fetch (self-sign) an access token
|
||||
* even when only scopes are supplied. Otherwise,
|
||||
* ServiceAccountJwtAccessCredentials is only called when no scopes and an
|
||||
* authUrl (audience) is suppled.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function useJwtAccessWithScope()
|
||||
{
|
||||
$this->useJwtAccessWithScope = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|null $httpHandler
|
||||
* @param array<mixed> $headers [optional] Headers to be inserted
|
||||
* into the token endpoint request present.
|
||||
*
|
||||
* @return array<mixed> {
|
||||
* A set of auth related metadata, containing the following
|
||||
*
|
||||
* @type string $access_token
|
||||
* @type int $expires_in
|
||||
* @type string $token_type
|
||||
* }
|
||||
*/
|
||||
public function fetchAuthToken(?callable $httpHandler = null, array $headers = [])
|
||||
{
|
||||
if ($this->useSelfSignedJwt()) {
|
||||
$jwtCreds = $this->createJwtAccessCredentials();
|
||||
|
||||
$accessToken = $jwtCreds->fetchAuthToken($httpHandler);
|
||||
|
||||
if ($lastReceivedToken = $jwtCreds->getLastReceivedToken()) {
|
||||
// Keep self-signed JWTs in memory as the last received token
|
||||
$this->lastReceivedJwtAccessToken = $lastReceivedToken;
|
||||
}
|
||||
|
||||
return $accessToken;
|
||||
}
|
||||
|
||||
if ($this->isIdTokenRequest && $this->getUniverseDomain() !== self::DEFAULT_UNIVERSE_DOMAIN) {
|
||||
$now = time();
|
||||
$jwt = Jwt::encode(
|
||||
[
|
||||
'iss' => $this->auth->getIssuer(),
|
||||
'sub' => $this->auth->getIssuer(),
|
||||
'scope' => self::IAM_SCOPE,
|
||||
'exp' => ($now + $this->auth->getExpiry()),
|
||||
'iat' => ($now - OAuth2::DEFAULT_SKEW_SECONDS),
|
||||
],
|
||||
$this->auth->getSigningKey(),
|
||||
$this->auth->getSigningAlgorithm(),
|
||||
$this->auth->getSigningKeyId()
|
||||
);
|
||||
// We create a new instance of Iam each time because the `$httpHandler` might change.
|
||||
$idToken = (new Iam($httpHandler, $this->getUniverseDomain()))->generateIdToken(
|
||||
$this->auth->getIssuer(),
|
||||
$this->auth->getAdditionalClaims()['target_audience'],
|
||||
$jwt,
|
||||
$this->applyTokenEndpointMetrics($headers, 'it')
|
||||
);
|
||||
return ['id_token' => $idToken];
|
||||
}
|
||||
return $this->auth->fetchAuthToken(
|
||||
$httpHandler,
|
||||
$this->applyTokenEndpointMetrics($headers, $this->isIdTokenRequest ? 'it' : 'at')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Cache Key for the credentials.
|
||||
* For the cache key format is one of the following:
|
||||
* ClientEmail.Scope[.Sub]
|
||||
* ClientEmail.Audience[.Sub]
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheKey()
|
||||
{
|
||||
$scopeOrAudience = $this->auth->getScope();
|
||||
if (!$scopeOrAudience) {
|
||||
$scopeOrAudience = $this->auth->getAudience();
|
||||
}
|
||||
|
||||
$key = $this->auth->getIssuer() . '.' . $scopeOrAudience;
|
||||
if ($sub = $this->auth->getSub()) {
|
||||
$key .= '.' . $sub;
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function getLastReceivedToken()
|
||||
{
|
||||
// If self-signed JWTs are being used, fetch the last received token
|
||||
// from memory. Else, fetch it from OAuth2
|
||||
return $this->useSelfSignedJwt()
|
||||
? $this->lastReceivedJwtAccessToken
|
||||
: $this->auth->getLastReceivedToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the project ID from the service account keyfile.
|
||||
*
|
||||
* Returns null if the project ID does not exist in the keyfile.
|
||||
*
|
||||
* @param callable|null $httpHandler Not used by this credentials type.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProjectId(?callable $httpHandler = null)
|
||||
{
|
||||
return $this->projectId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates metadata with the authorization token.
|
||||
*
|
||||
* @param array<mixed> $metadata metadata hashmap
|
||||
* @param string $authUri optional auth uri
|
||||
* @param callable|null $httpHandler callback which delivers psr7 request
|
||||
* @return array<mixed> updated metadata hashmap
|
||||
*/
|
||||
public function updateMetadata(
|
||||
$metadata,
|
||||
$authUri = null,
|
||||
?callable $httpHandler = null
|
||||
) {
|
||||
// scope exists. use oauth implementation
|
||||
if (!$this->useSelfSignedJwt()) {
|
||||
return parent::updateMetadata($metadata, $authUri, $httpHandler);
|
||||
}
|
||||
|
||||
$jwtCreds = $this->createJwtAccessCredentials();
|
||||
if ($this->auth->getScope()) {
|
||||
// Prefer user-provided "scope" to "audience"
|
||||
$updatedMetadata = $jwtCreds->updateMetadata($metadata, null, $httpHandler);
|
||||
} else {
|
||||
$updatedMetadata = $jwtCreds->updateMetadata($metadata, $authUri, $httpHandler);
|
||||
}
|
||||
|
||||
if ($lastReceivedToken = $jwtCreds->getLastReceivedToken()) {
|
||||
// Keep self-signed JWTs in memory as the last received token
|
||||
$this->lastReceivedJwtAccessToken = $lastReceivedToken;
|
||||
}
|
||||
|
||||
return $updatedMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ServiceAccountJwtAccessCredentials
|
||||
*/
|
||||
private function createJwtAccessCredentials()
|
||||
{
|
||||
if (!$this->jwtAccessCredentials) {
|
||||
// Create credentials for self-signing a JWT (JwtAccess)
|
||||
$credJson = [
|
||||
'private_key' => $this->auth->getSigningKey(),
|
||||
'client_email' => $this->auth->getIssuer(),
|
||||
];
|
||||
$this->jwtAccessCredentials = new ServiceAccountJwtAccessCredentials(
|
||||
$credJson,
|
||||
$this->auth->getScope()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->jwtAccessCredentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sub an email address account to impersonate, in situations when
|
||||
* the service account has been delegated domain wide access.
|
||||
* @return void
|
||||
*/
|
||||
public function setSub($sub)
|
||||
{
|
||||
$this->auth->setSub($sub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client name from the keyfile.
|
||||
*
|
||||
* In this case, it returns the keyfile's client_email key.
|
||||
*
|
||||
* @param callable|null $httpHandler Not used by this credentials type.
|
||||
* @return string
|
||||
*/
|
||||
public function getClientName(?callable $httpHandler = null)
|
||||
{
|
||||
return $this->auth->getIssuer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the private key from the keyfile.
|
||||
*
|
||||
* In this case, it returns the keyfile's private_key key, needed for JWT signing.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPrivateKey()
|
||||
{
|
||||
return $this->auth->getSigningKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the quota project used for this API request
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getQuotaProject()
|
||||
{
|
||||
return $this->quotaProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the universe domain configured in the JSON credential.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUniverseDomain(): string
|
||||
{
|
||||
return $this->universeDomain;
|
||||
}
|
||||
|
||||
protected function getCredType(): string
|
||||
{
|
||||
return self::CRED_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function useSelfSignedJwt()
|
||||
{
|
||||
// When a sub is supplied, the user is using domain-wide delegation, which not available
|
||||
// with self-signed JWTs
|
||||
if (null !== $this->auth->getSub()) {
|
||||
// If we are outside the GDU, we can't use domain-wide delegation
|
||||
if ($this->getUniverseDomain() !== self::DEFAULT_UNIVERSE_DOMAIN) {
|
||||
throw new \LogicException(sprintf(
|
||||
'Service Account subject is configured for the credential. Domain-wide ' .
|
||||
'delegation is not supported in universes other than %s.',
|
||||
self::DEFAULT_UNIVERSE_DOMAIN
|
||||
));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not use self-signed JWT for ID tokens
|
||||
if ($this->isIdTokenRequest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// When true, ServiceAccountCredentials will always use JwtAccess for access tokens
|
||||
if ($this->useJwtAccessWithScope) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the universe domain is outside the GDU, use JwtAccess for access tokens
|
||||
if ($this->getUniverseDomain() !== self::DEFAULT_UNIVERSE_DOMAIN) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return is_null($this->auth->getScope());
|
||||
}
|
||||
}
|
||||
246
vendor/google/auth/src/Credentials/ServiceAccountJwtAccessCredentials.php
vendored
Normal file
@ -0,0 +1,246 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\Credentials;
|
||||
|
||||
use Google\Auth\CredentialsLoader;
|
||||
use Google\Auth\GetQuotaProjectInterface;
|
||||
use Google\Auth\OAuth2;
|
||||
use Google\Auth\ProjectIdProviderInterface;
|
||||
use Google\Auth\ServiceAccountSignerTrait;
|
||||
use Google\Auth\SignBlobInterface;
|
||||
|
||||
/**
|
||||
* Authenticates requests using Google's Service Account credentials via
|
||||
* JWT Access.
|
||||
*
|
||||
* This class allows authorizing requests for service accounts directly
|
||||
* from credentials from a json key file downloaded from the developer
|
||||
* console (via 'Generate new Json Key'). It is not part of any OAuth2
|
||||
* flow, rather it creates a JWT and sends that as a credential.
|
||||
*/
|
||||
class ServiceAccountJwtAccessCredentials extends CredentialsLoader implements
|
||||
GetQuotaProjectInterface,
|
||||
SignBlobInterface,
|
||||
ProjectIdProviderInterface
|
||||
{
|
||||
use ServiceAccountSignerTrait;
|
||||
|
||||
/**
|
||||
* Used in observability metric headers
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private const CRED_TYPE = 'jwt';
|
||||
|
||||
/**
|
||||
* The OAuth2 instance used to conduct authorization.
|
||||
*
|
||||
* @var OAuth2
|
||||
*/
|
||||
protected $auth;
|
||||
|
||||
/**
|
||||
* The quota project associated with the JSON credentials
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $quotaProject;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $projectId;
|
||||
|
||||
/**
|
||||
* Create a new ServiceAccountJwtAccessCredentials.
|
||||
*
|
||||
* @param string|array<mixed> $jsonKey JSON credential file path or JSON credentials
|
||||
* as an associative array
|
||||
* @param string|string[] $scope the scope of the access request, expressed
|
||||
* either as an Array or as a space-delimited String.
|
||||
*/
|
||||
public function __construct($jsonKey, $scope = null)
|
||||
{
|
||||
if (is_string($jsonKey)) {
|
||||
if (!file_exists($jsonKey)) {
|
||||
throw new \InvalidArgumentException('file does not exist');
|
||||
}
|
||||
$jsonKeyStream = file_get_contents($jsonKey);
|
||||
if (!$jsonKey = json_decode((string) $jsonKeyStream, true)) {
|
||||
throw new \LogicException('invalid json for auth config');
|
||||
}
|
||||
}
|
||||
if (!array_key_exists('client_email', $jsonKey)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'json key is missing the client_email field'
|
||||
);
|
||||
}
|
||||
if (!array_key_exists('private_key', $jsonKey)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'json key is missing the private_key field'
|
||||
);
|
||||
}
|
||||
if (array_key_exists('quota_project_id', $jsonKey)) {
|
||||
$this->quotaProject = (string) $jsonKey['quota_project_id'];
|
||||
}
|
||||
$this->auth = new OAuth2([
|
||||
'issuer' => $jsonKey['client_email'],
|
||||
'sub' => $jsonKey['client_email'],
|
||||
'signingAlgorithm' => 'RS256',
|
||||
'signingKey' => $jsonKey['private_key'],
|
||||
'scope' => $scope,
|
||||
]);
|
||||
|
||||
$this->projectId = $jsonKey['project_id'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates metadata with the authorization token.
|
||||
*
|
||||
* @param array<mixed> $metadata metadata hashmap
|
||||
* @param string $authUri optional auth uri
|
||||
* @param callable|null $httpHandler callback which delivers psr7 request
|
||||
* @return array<mixed> updated metadata hashmap
|
||||
*/
|
||||
public function updateMetadata(
|
||||
$metadata,
|
||||
$authUri = null,
|
||||
?callable $httpHandler = null
|
||||
) {
|
||||
$scope = $this->auth->getScope();
|
||||
if (empty($authUri) && empty($scope)) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
$this->auth->setAudience($authUri);
|
||||
|
||||
return parent::updateMetadata($metadata, $authUri, $httpHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements FetchAuthTokenInterface#fetchAuthToken.
|
||||
*
|
||||
* @param callable|null $httpHandler
|
||||
*
|
||||
* @return null|array{access_token:string} A set of auth related metadata
|
||||
*/
|
||||
public function fetchAuthToken(?callable $httpHandler = null)
|
||||
{
|
||||
$audience = $this->auth->getAudience();
|
||||
$scope = $this->auth->getScope();
|
||||
if (empty($audience) && empty($scope)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!empty($audience) && !empty($scope)) {
|
||||
throw new \UnexpectedValueException(
|
||||
'Cannot sign both audience and scope in JwtAccess'
|
||||
);
|
||||
}
|
||||
|
||||
$access_token = $this->auth->toJwt();
|
||||
|
||||
// Set the self-signed access token in OAuth2 for getLastReceivedToken
|
||||
$this->auth->setAccessToken($access_token);
|
||||
|
||||
return [
|
||||
'access_token' => $access_token,
|
||||
'expires_in' => $this->auth->getExpiry(),
|
||||
'token_type' => 'Bearer'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cache key for the credentials.
|
||||
* The format for the Cache Key one of the following:
|
||||
* ClientEmail.Scope
|
||||
* ClientEmail.Audience
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheKey()
|
||||
{
|
||||
$scopeOrAudience = $this->auth->getScope();
|
||||
if (!$scopeOrAudience) {
|
||||
$scopeOrAudience = $this->auth->getAudience();
|
||||
}
|
||||
|
||||
return $this->auth->getIssuer() . '.' . $scopeOrAudience;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function getLastReceivedToken()
|
||||
{
|
||||
return $this->auth->getLastReceivedToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the project ID from the service account keyfile.
|
||||
*
|
||||
* Returns null if the project ID does not exist in the keyfile.
|
||||
*
|
||||
* @param callable|null $httpHandler Not used by this credentials type.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProjectId(?callable $httpHandler = null)
|
||||
{
|
||||
return $this->projectId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client name from the keyfile.
|
||||
*
|
||||
* In this case, it returns the keyfile's client_email key.
|
||||
*
|
||||
* @param callable|null $httpHandler Not used by this credentials type.
|
||||
* @return string
|
||||
*/
|
||||
public function getClientName(?callable $httpHandler = null)
|
||||
{
|
||||
return $this->auth->getIssuer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the private key from the keyfile.
|
||||
*
|
||||
* In this case, it returns the keyfile's private_key key, needed for JWT signing.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPrivateKey()
|
||||
{
|
||||
return $this->auth->getSigningKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the quota project used for this API request
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getQuotaProject()
|
||||
{
|
||||
return $this->quotaProject;
|
||||
}
|
||||
|
||||
protected function getCredType(): string
|
||||
{
|
||||
return self::CRED_TYPE;
|
||||
}
|
||||
}
|
||||
202
vendor/google/auth/src/Credentials/UserRefreshCredentials.php
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth\Credentials;
|
||||
|
||||
use Google\Auth\CredentialsLoader;
|
||||
use Google\Auth\GetQuotaProjectInterface;
|
||||
use Google\Auth\OAuth2;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* Authenticates requests using User Refresh credentials.
|
||||
*
|
||||
* This class allows authorizing requests from user refresh tokens.
|
||||
*
|
||||
* This the end of the result of a 3LO flow. E.g, the end result of
|
||||
* 'gcloud auth login' saves a file with these contents in well known
|
||||
* location
|
||||
*
|
||||
* @see [Application Default Credentials](http://goo.gl/mkAHpZ)
|
||||
*/
|
||||
class UserRefreshCredentials extends CredentialsLoader implements GetQuotaProjectInterface
|
||||
{
|
||||
/**
|
||||
* Used in observability metric headers
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private const CRED_TYPE = 'u';
|
||||
|
||||
/**
|
||||
* The OAuth2 instance used to conduct authorization.
|
||||
*
|
||||
* @var OAuth2
|
||||
*/
|
||||
protected $auth;
|
||||
|
||||
/**
|
||||
* The quota project associated with the JSON credentials
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $quotaProject;
|
||||
|
||||
/**
|
||||
* Whether this is an ID token request or an access token request. Used when
|
||||
* building the metric header.
|
||||
*/
|
||||
private bool $isIdTokenRequest = false;
|
||||
|
||||
/**
|
||||
* Create a new UserRefreshCredentials.
|
||||
*
|
||||
* @param string|string[]|null $scope the scope of the access request, expressed
|
||||
* either as an Array or as a space-delimited String.
|
||||
* @param string|array<mixed> $jsonKey JSON credential file path or JSON credentials
|
||||
* as an associative array
|
||||
* @param string|null $targetAudience The audience for the ID token.
|
||||
*/
|
||||
public function __construct(
|
||||
$scope,
|
||||
$jsonKey,
|
||||
?string $targetAudience = null
|
||||
) {
|
||||
if (is_string($jsonKey)) {
|
||||
if (!file_exists($jsonKey)) {
|
||||
throw new InvalidArgumentException('file does not exist or is unreadable');
|
||||
}
|
||||
$json = file_get_contents($jsonKey);
|
||||
if (!$jsonKey = json_decode((string) $json, true)) {
|
||||
throw new LogicException('invalid json for auth config');
|
||||
}
|
||||
}
|
||||
if (!array_key_exists('client_id', $jsonKey)) {
|
||||
throw new InvalidArgumentException(
|
||||
'json key is missing the client_id field'
|
||||
);
|
||||
}
|
||||
if (!array_key_exists('client_secret', $jsonKey)) {
|
||||
throw new InvalidArgumentException(
|
||||
'json key is missing the client_secret field'
|
||||
);
|
||||
}
|
||||
if (!array_key_exists('refresh_token', $jsonKey)) {
|
||||
throw new InvalidArgumentException(
|
||||
'json key is missing the refresh_token field'
|
||||
);
|
||||
}
|
||||
if ($scope && $targetAudience) {
|
||||
throw new InvalidArgumentException(
|
||||
'Scope and targetAudience cannot both be supplied'
|
||||
);
|
||||
}
|
||||
$additionalClaims = [];
|
||||
if ($targetAudience) {
|
||||
$additionalClaims = ['target_audience' => $targetAudience];
|
||||
$this->isIdTokenRequest = true;
|
||||
}
|
||||
$this->auth = new OAuth2([
|
||||
'clientId' => $jsonKey['client_id'],
|
||||
'clientSecret' => $jsonKey['client_secret'],
|
||||
'refresh_token' => $jsonKey['refresh_token'],
|
||||
'scope' => $scope,
|
||||
'tokenCredentialUri' => self::TOKEN_CREDENTIAL_URI,
|
||||
'additionalClaims' => $additionalClaims,
|
||||
]);
|
||||
if (array_key_exists('quota_project_id', $jsonKey)) {
|
||||
$this->quotaProject = (string) $jsonKey['quota_project_id'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|null $httpHandler
|
||||
* @param array<mixed> $headers [optional] Metrics headers to be inserted
|
||||
* into the token endpoint request present.
|
||||
* This could be passed from ImersonatedServiceAccountCredentials as it uses
|
||||
* UserRefreshCredentials as source credentials.
|
||||
*
|
||||
* @return array<mixed> {
|
||||
* A set of auth related metadata, containing the following
|
||||
*
|
||||
* @type string $access_token
|
||||
* @type int $expires_in
|
||||
* @type string $scope
|
||||
* @type string $token_type
|
||||
* @type string $id_token
|
||||
* }
|
||||
*/
|
||||
public function fetchAuthToken(?callable $httpHandler = null, array $headers = [])
|
||||
{
|
||||
return $this->auth->fetchAuthToken(
|
||||
$httpHandler,
|
||||
$this->applyTokenEndpointMetrics($headers, $this->isIdTokenRequest ? 'it' : 'at')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Cache Key for the credentials.
|
||||
* The format for the Cache key is one of the following:
|
||||
* ClientId.Scope
|
||||
* ClientId.Audience
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheKey()
|
||||
{
|
||||
$scopeOrAudience = $this->auth->getScope();
|
||||
if (!$scopeOrAudience) {
|
||||
$scopeOrAudience = $this->auth->getAudience();
|
||||
}
|
||||
|
||||
return $this->auth->getClientId() . '.' . $scopeOrAudience;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function getLastReceivedToken()
|
||||
{
|
||||
return $this->auth->getLastReceivedToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the quota project used for this API request
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getQuotaProject()
|
||||
{
|
||||
return $this->quotaProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the granted scopes (if they exist) for the last fetched token.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getGrantedScope()
|
||||
{
|
||||
return $this->auth->getGrantedScope();
|
||||
}
|
||||
|
||||
protected function getCredType(): string
|
||||
{
|
||||
return self::CRED_TYPE;
|
||||
}
|
||||
}
|
||||
319
vendor/google/auth/src/CredentialsLoader.php
vendored
Normal file
@ -0,0 +1,319 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Auth;
|
||||
|
||||
use Google\Auth\Credentials\ExternalAccountCredentials;
|
||||
use Google\Auth\Credentials\ImpersonatedServiceAccountCredentials;
|
||||
use Google\Auth\Credentials\InsecureCredentials;
|
||||
use Google\Auth\Credentials\ServiceAccountCredentials;
|
||||
use Google\Auth\Credentials\UserRefreshCredentials;
|
||||
use RuntimeException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* CredentialsLoader contains the behaviour used to locate and find default
|
||||
* credentials files on the file system.
|
||||
*/
|
||||
abstract class CredentialsLoader implements
|
||||
GetUniverseDomainInterface,
|
||||
FetchAuthTokenInterface,
|
||||
UpdateMetadataInterface
|
||||
{
|
||||
use UpdateMetadataTrait;
|
||||
|
||||
const TOKEN_CREDENTIAL_URI = 'https://oauth2.googleapis.com/token';
|
||||
const ENV_VAR = 'GOOGLE_APPLICATION_CREDENTIALS';
|
||||
const QUOTA_PROJECT_ENV_VAR = 'GOOGLE_CLOUD_QUOTA_PROJECT';
|
||||
const WELL_KNOWN_PATH = 'gcloud/application_default_credentials.json';
|
||||
const NON_WINDOWS_WELL_KNOWN_PATH_BASE = '.config';
|
||||
const MTLS_WELL_KNOWN_PATH = '.secureConnect/context_aware_metadata.json';
|
||||
const MTLS_CERT_ENV_VAR = 'GOOGLE_API_USE_CLIENT_CERTIFICATE';
|
||||
|
||||
/**
|
||||
* @param string $cause
|
||||
* @return string
|
||||
*/
|
||||
private static function unableToReadEnv($cause)
|
||||
{
|
||||
$msg = 'Unable to read the credential file specified by ';
|
||||
$msg .= ' GOOGLE_APPLICATION_CREDENTIALS: ';
|
||||
$msg .= $cause;
|
||||
|
||||
return $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private static function isOnWindows()
|
||||
{
|
||||
return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a JSON key from the path specified in the environment.
|
||||
*
|
||||
* Load a JSON key from the path specified in the environment
|
||||
* variable GOOGLE_APPLICATION_CREDENTIALS. Return null if
|
||||
* GOOGLE_APPLICATION_CREDENTIALS is not specified.
|
||||
*
|
||||
* @return array<mixed>|null JSON key | null
|
||||
*/
|
||||
public static function fromEnv()
|
||||
{
|
||||
$path = self::getEnv(self::ENV_VAR);
|
||||
if (empty($path)) {
|
||||
return null;
|
||||
}
|
||||
if (!file_exists($path)) {
|
||||
$cause = 'file ' . $path . ' does not exist';
|
||||
throw new \DomainException(self::unableToReadEnv($cause));
|
||||
}
|
||||
$jsonKey = file_get_contents($path);
|
||||
|
||||
return json_decode((string) $jsonKey, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a JSON key from a well known path.
|
||||
*
|
||||
* The well known path is OS dependent:
|
||||
*
|
||||
* * windows: %APPDATA%/gcloud/application_default_credentials.json
|
||||
* * others: $HOME/.config/gcloud/application_default_credentials.json
|
||||
*
|
||||
* If the file does not exist, this returns null.
|
||||
*
|
||||
* @return array<mixed>|null JSON key | null
|
||||
*/
|
||||
public static function fromWellKnownFile()
|
||||
{
|
||||
$rootEnv = self::isOnWindows() ? 'APPDATA' : 'HOME';
|
||||
$path = [self::getEnv($rootEnv)];
|
||||
if (!self::isOnWindows()) {
|
||||
$path[] = self::NON_WINDOWS_WELL_KNOWN_PATH_BASE;
|
||||
}
|
||||
$path[] = self::WELL_KNOWN_PATH;
|
||||
$path = implode(DIRECTORY_SEPARATOR, $path);
|
||||
if (!file_exists($path)) {
|
||||
return null;
|
||||
}
|
||||
$jsonKey = file_get_contents($path);
|
||||
return json_decode((string) $jsonKey, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Credentials instance.
|
||||
*
|
||||
* @deprecated This method is being deprecated because of a potential security risk.
|
||||
*
|
||||
* This method does not validate the credential configuration. The security
|
||||
* risk occurs when a credential configuration is accepted from a source
|
||||
* that is not under your control and used without validation on your side.
|
||||
*
|
||||
* If you know that you will be loading credential configurations of a
|
||||
* specific type, it is recommended to use a credential-type-specific
|
||||
* method.
|
||||
* This will ensure that an unexpected credential type with potential for
|
||||
* malicious intent is not loaded unintentionally. You might still have to do
|
||||
* validation for certain credential types. Please follow the recommendation
|
||||
* for that method. For example, if you want to load only service accounts,
|
||||
* you can create the {@see ServiceAccountCredentials} explicitly:
|
||||
*
|
||||
* ```
|
||||
* use Google\Auth\Credentials\ServiceAccountCredentials;
|
||||
* $creds = new ServiceAccountCredentials($scopes, $json);
|
||||
* ```
|
||||
*
|
||||
* If you are loading your credential configuration from an untrusted source and have
|
||||
* not mitigated the risks (e.g. by validating the configuration yourself), make
|
||||
* these changes as soon as possible to prevent security risks to your environment.
|
||||
*
|
||||
* Regardless of the method used, it is always your responsibility to validate
|
||||
* configurations received from external sources.
|
||||
*
|
||||
* @see https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
|
||||
*
|
||||
* @param string|string[] $scope
|
||||
* @param array<mixed> $jsonKey
|
||||
* @param string|string[] $defaultScope
|
||||
* @return ServiceAccountCredentials|UserRefreshCredentials|ImpersonatedServiceAccountCredentials|ExternalAccountCredentials
|
||||
*/
|
||||
public static function makeCredentials(
|
||||
$scope,
|
||||
array $jsonKey,
|
||||
$defaultScope = null
|
||||
) {
|
||||
if (!array_key_exists('type', $jsonKey)) {
|
||||
throw new \InvalidArgumentException('json key is missing the type field');
|
||||
}
|
||||
|
||||
if ($jsonKey['type'] == 'service_account') {
|
||||
// Do not pass $defaultScope to ServiceAccountCredentials
|
||||
return new ServiceAccountCredentials($scope, $jsonKey);
|
||||
}
|
||||
|
||||
if ($jsonKey['type'] == 'authorized_user') {
|
||||
$anyScope = $scope ?: $defaultScope;
|
||||
return new UserRefreshCredentials($anyScope, $jsonKey);
|
||||
}
|
||||
|
||||
if ($jsonKey['type'] == 'impersonated_service_account') {
|
||||
$anyScope = $scope ?: $defaultScope;
|
||||
return new ImpersonatedServiceAccountCredentials($anyScope, $jsonKey);
|
||||
}
|
||||
|
||||
if ($jsonKey['type'] == 'external_account') {
|
||||
$anyScope = $scope ?: $defaultScope;
|
||||
return new ExternalAccountCredentials($anyScope, $jsonKey);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('invalid value in the type field');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an authorized HTTP Client from an instance of FetchAuthTokenInterface.
|
||||
*
|
||||
* @param FetchAuthTokenInterface $fetcher is used to fetch the auth token
|
||||
* @param array<mixed> $httpClientOptions (optional) Array of request options to apply.
|
||||
* @param callable|null $httpHandler (optional) http client to fetch the token.
|
||||
* @param callable|null $tokenCallback (optional) function to be called when a new token is fetched.
|
||||
* @return \GuzzleHttp\Client
|
||||
*/
|
||||
public static function makeHttpClient(
|
||||
FetchAuthTokenInterface $fetcher,
|
||||
array $httpClientOptions = [],
|
||||
?callable $httpHandler = null,
|
||||
?callable $tokenCallback = null
|
||||
) {
|
||||
$middleware = new Middleware\AuthTokenMiddleware(
|
||||
$fetcher,
|
||||
$httpHandler,
|
||||
$tokenCallback
|
||||
);
|
||||
$stack = \GuzzleHttp\HandlerStack::create();
|
||||
$stack->push($middleware);
|
||||
|
||||
return new \GuzzleHttp\Client([
|
||||
'handler' => $stack,
|
||||
'auth' => 'google_auth',
|
||||
] + $httpClientOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of InsecureCredentials.
|
||||
*
|
||||
* @return InsecureCredentials
|
||||
*/
|
||||
public static function makeInsecureCredentials()
|
||||
{
|
||||
return new InsecureCredentials();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a quota project from the environment variable
|
||||
* GOOGLE_CLOUD_QUOTA_PROJECT. Return null if
|
||||
* GOOGLE_CLOUD_QUOTA_PROJECT is not specified.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function quotaProjectFromEnv()
|
||||
{
|
||||
return self::getEnv(self::QUOTA_PROJECT_ENV_VAR) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a callable which returns the default device certification.
|
||||
*
|
||||
* @throws UnexpectedValueException
|
||||
* @return callable|null
|
||||
*/
|
||||
public static function getDefaultClientCertSource()
|
||||
{
|
||||
if (!$clientCertSourceJson = self::loadDefaultClientCertSourceFile()) {
|
||||
return null;
|
||||
}
|
||||
$clientCertSourceCmd = $clientCertSourceJson['cert_provider_command'];
|
||||
|
||||
return function () use ($clientCertSourceCmd) {
|
||||
$cmd = array_map('escapeshellarg', $clientCertSourceCmd);
|
||||
exec(implode(' ', $cmd), $output, $returnVar);
|
||||
|
||||
if (0 === $returnVar) {
|
||||
return implode(PHP_EOL, $output);
|
||||
}
|
||||
throw new RuntimeException(
|
||||
'"cert_provider_command" failed with a nonzero exit code'
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not the default device certificate should be loaded.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function shouldLoadClientCertSource()
|
||||
{
|
||||
return filter_var(self::getEnv(self::MTLS_CERT_ENV_VAR), FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{cert_provider_command:string[]}|null
|
||||
*/
|
||||
private static function loadDefaultClientCertSourceFile()
|
||||
{
|
||||
$rootEnv = self::isOnWindows() ? 'APPDATA' : 'HOME';
|
||||
$path = sprintf('%s/%s', self::getEnv($rootEnv), self::MTLS_WELL_KNOWN_PATH);
|
||||
if (!file_exists($path)) {
|
||||
return null;
|
||||
}
|
||||
$jsonKey = file_get_contents($path);
|
||||
$clientCertSourceJson = json_decode((string) $jsonKey, true);
|
||||
if (!$clientCertSourceJson) {
|
||||
throw new UnexpectedValueException('Invalid client cert source JSON');
|
||||
}
|
||||
if (!isset($clientCertSourceJson['cert_provider_command'])) {
|
||||
throw new UnexpectedValueException(
|
||||
'cert source requires "cert_provider_command"'
|
||||
);
|
||||
}
|
||||
if (!is_array($clientCertSourceJson['cert_provider_command'])) {
|
||||
throw new UnexpectedValueException(
|
||||
'cert source expects "cert_provider_command" to be an array'
|
||||
);
|
||||
}
|
||||
return $clientCertSourceJson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the universe domain from the credential. Defaults to "googleapis.com"
|
||||
* for all credential types which do not support universe domain.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUniverseDomain(): string
|
||||
{
|
||||
return self::DEFAULT_UNIVERSE_DOMAIN;
|
||||
}
|
||||
|
||||
private static function getEnv(string $env): mixed
|
||||
{
|
||||
return getenv($env) ?: $_ENV[$env] ?? null;
|
||||
}
|
||||
}
|
||||