DSP Project first push, date: 29/01/2026
This commit is contained in:
110
oauth/authorize.php
Normal file
110
oauth/authorize.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
// oauth/authorize.php
|
||||
session_start();
|
||||
|
||||
require_once __DIR__ . '/../config.php';
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
require_once __DIR__ . '/../classes/OAuth.php';
|
||||
|
||||
$requestParams = [
|
||||
'response_type' => $_GET['response_type'] ?? '',
|
||||
'client_id' => $_GET['client_id'] ?? '',
|
||||
'redirect_uri' => $_GET['redirect_uri'] ?? '',
|
||||
'scope' => $_GET['scope'] ?? '',
|
||||
'state' => $_GET['state'] ?? '',
|
||||
];
|
||||
|
||||
$responseType = $requestParams['response_type'];
|
||||
$clientId = trim($requestParams['client_id']);
|
||||
$redirectUri = trim($requestParams['redirect_uri']);
|
||||
$scope = trim($requestParams['scope']);
|
||||
$state = $requestParams['state'];
|
||||
|
||||
function oauth_bad_request(string $message): void {
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['error' => 'invalid_request', 'error_description' => $message], JSON_UNESCAPED_SLASHES);
|
||||
exit();
|
||||
}
|
||||
|
||||
function oauth_redirect_with_error(string $redirectUri, string $error, ?string $description = null, ?string $state = null): void {
|
||||
$fragment = '';
|
||||
if (($hashPos = strpos($redirectUri, '#')) !== false) {
|
||||
$fragment = substr($redirectUri, $hashPos);
|
||||
$redirectUri = substr($redirectUri, 0, $hashPos);
|
||||
}
|
||||
$separator = (strpos($redirectUri, '?') === false) ? '?' : '&';
|
||||
$payload = ['error' => $error];
|
||||
if ($description) {
|
||||
$payload['error_description'] = $description;
|
||||
}
|
||||
if ($state !== null && $state !== '') {
|
||||
$payload['state'] = $state;
|
||||
}
|
||||
$location = $redirectUri . $separator . http_build_query($payload) . $fragment;
|
||||
header('Location: ' . $location);
|
||||
exit();
|
||||
}
|
||||
|
||||
function oauth_redirect_with_params(string $redirectUri, array $params): void {
|
||||
$fragment = '';
|
||||
if (($hashPos = strpos($redirectUri, '#')) !== false) {
|
||||
$fragment = substr($redirectUri, $hashPos);
|
||||
$redirectUri = substr($redirectUri, 0, $hashPos);
|
||||
}
|
||||
$separator = (strpos($redirectUri, '?') === false) ? '?' : '&';
|
||||
$location = $redirectUri . $separator . http_build_query($params) . $fragment;
|
||||
header('Location: ' . $location);
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($responseType !== 'code') {
|
||||
oauth_bad_request('Unsupported response_type. Only "code" is supported.');
|
||||
}
|
||||
|
||||
if ($clientId === '' || $redirectUri === '') {
|
||||
oauth_bad_request('Missing client_id or redirect_uri.');
|
||||
}
|
||||
|
||||
if (!is_logged_in()) {
|
||||
$_SESSION['oauth_pending_request'] = [
|
||||
'params' => $requestParams,
|
||||
'created_at' => time(),
|
||||
];
|
||||
set_message("Please login to continue with the requested integration.", "warning");
|
||||
header('Location: ../index.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['person_id']) || (int) $_SESSION['person_id'] <= 0) {
|
||||
oauth_bad_request('Your session is missing required profile information.');
|
||||
}
|
||||
|
||||
$oauthService = new OAuthService($pdo);
|
||||
$client = $oauthService->getClient($clientId);
|
||||
|
||||
if (!$client) {
|
||||
oauth_bad_request('Unknown or revoked client.');
|
||||
}
|
||||
|
||||
if (!$oauthService->isRedirectUriAllowed($client, $redirectUri)) {
|
||||
oauth_bad_request('The provided redirect_uri is not registered for this client.');
|
||||
}
|
||||
|
||||
if (!$oauthService->isScopeAllowed($client, $scope)) {
|
||||
oauth_redirect_with_error($redirectUri, 'invalid_scope', 'Requested scope is not permitted.', $state);
|
||||
}
|
||||
|
||||
$codeData = $oauthService->issueAuthorizationCode(
|
||||
$clientId,
|
||||
(int) $_SESSION['person_id'],
|
||||
$redirectUri,
|
||||
$scope !== '' ? $scope : null
|
||||
);
|
||||
|
||||
$payload = ['code' => $codeData['code']];
|
||||
if ($state !== '') {
|
||||
$payload['state'] = $state;
|
||||
}
|
||||
|
||||
oauth_redirect_with_params($redirectUri, $payload);
|
||||
125
oauth/token.php
Normal file
125
oauth/token.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
// oauth/token.php
|
||||
|
||||
require_once __DIR__ . '/../config.php';
|
||||
require_once __DIR__ . '/../classes/OAuth.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Cache-Control: no-store');
|
||||
header('Pragma: no-cache');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'invalid_request', 'error_description' => 'POST required.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
function respond_with_error(string $error, string $description, int $status = 400): void {
|
||||
http_response_code($status);
|
||||
echo json_encode(['error' => $error, 'error_description' => $description], JSON_UNESCAPED_SLASHES);
|
||||
exit();
|
||||
}
|
||||
|
||||
function extract_client_credentials(): array {
|
||||
$clientId = null;
|
||||
$clientSecret = null;
|
||||
|
||||
if (!empty($_SERVER['HTTP_AUTHORIZATION']) && stripos($_SERVER['HTTP_AUTHORIZATION'], 'basic ') === 0) {
|
||||
$encoded = substr($_SERVER['HTTP_AUTHORIZATION'], 6);
|
||||
$decoded = base64_decode($encoded, true);
|
||||
if ($decoded !== false && strpos($decoded, ':') !== false) {
|
||||
[$clientId, $clientSecret] = explode(':', $decoded, 2);
|
||||
}
|
||||
}
|
||||
|
||||
if ($clientId === null && isset($_POST['client_id'])) {
|
||||
$clientId = $_POST['client_id'];
|
||||
$clientSecret = $_POST['client_secret'] ?? '';
|
||||
}
|
||||
|
||||
return [trim((string) $clientId), (string) $clientSecret];
|
||||
}
|
||||
|
||||
[$clientId, $clientSecret] = extract_client_credentials();
|
||||
$grantType = $_POST['grant_type'] ?? '';
|
||||
|
||||
if ($clientId === '' || $grantType === '') {
|
||||
respond_with_error('invalid_request', 'client_id and grant_type are required.');
|
||||
}
|
||||
|
||||
$oauthService = new OAuthService($pdo);
|
||||
$client = $oauthService->getClient($clientId);
|
||||
|
||||
if (!$client) {
|
||||
respond_with_error('unauthorized_client', 'Unknown or revoked client.', 401);
|
||||
}
|
||||
|
||||
$requiresSecret = (int) $client['is_confidential'] === 1;
|
||||
if ($requiresSecret && $clientSecret === '') {
|
||||
respond_with_error('invalid_client', 'Client credentials required.', 401);
|
||||
}
|
||||
|
||||
if ($requiresSecret && !$oauthService->verifyClientSecret($client, $clientSecret)) {
|
||||
respond_with_error('invalid_client', 'Client authentication failed.', 401);
|
||||
}
|
||||
|
||||
switch ($grantType) {
|
||||
case 'authorization_code':
|
||||
$code = $_POST['code'] ?? '';
|
||||
$redirectUri = $_POST['redirect_uri'] ?? '';
|
||||
|
||||
if ($code === '') {
|
||||
respond_with_error('invalid_request', 'Authorization code is required.');
|
||||
}
|
||||
|
||||
$authRecord = $oauthService->consumeAuthorizationCode($code, $clientId);
|
||||
if (!$authRecord) {
|
||||
respond_with_error('invalid_grant', 'Authorization code is invalid or expired.');
|
||||
}
|
||||
|
||||
if ($redirectUri !== '' && !hash_equals($authRecord['redirect_uri'], $redirectUri)) {
|
||||
respond_with_error('invalid_grant', 'redirect_uri mismatch.');
|
||||
}
|
||||
|
||||
$tokens = $oauthService->issueTokens(
|
||||
$clientId,
|
||||
(int) $authRecord['person_id'],
|
||||
$authRecord['scope'] ?? null,
|
||||
true
|
||||
);
|
||||
break;
|
||||
|
||||
case 'refresh_token':
|
||||
$refreshToken = $_POST['refresh_token'] ?? '';
|
||||
if ($refreshToken === '') {
|
||||
respond_with_error('invalid_request', 'refresh_token is required.');
|
||||
}
|
||||
|
||||
$tokens = $oauthService->exchangeRefreshToken($clientId, $refreshToken);
|
||||
if (!$tokens) {
|
||||
respond_with_error('invalid_grant', 'Refresh token is invalid or expired.');
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
respond_with_error('unsupported_grant_type', 'The grant_type is not supported.');
|
||||
}
|
||||
|
||||
$response = [
|
||||
'access_token' => $tokens['access_token'],
|
||||
'token_type' => $tokens['token_type'],
|
||||
'expires_in' => max(0, $tokens['access_expires_at'] - time()),
|
||||
];
|
||||
|
||||
if (!empty($tokens['scope'])) {
|
||||
$response['scope'] = $tokens['scope'];
|
||||
}
|
||||
|
||||
if (!empty($tokens['refresh_token'])) {
|
||||
$response['refresh_token'] = $tokens['refresh_token'];
|
||||
if (!empty($tokens['refresh_expires_at'])) {
|
||||
$response['refresh_expires_in'] = max(0, $tokens['refresh_expires_at'] - time());
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($response, JSON_UNESCAPED_SLASHES);
|
||||
86
oauth/userinfo.php
Normal file
86
oauth/userinfo.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
// oauth/userinfo.php
|
||||
|
||||
require_once __DIR__ . '/../config.php';
|
||||
require_once __DIR__ . '/../classes/OAuth.php';
|
||||
require_once __DIR__ . '/../includes/jupyter_helpers.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Cache-Control: no-store');
|
||||
header('Pragma: no-cache');
|
||||
|
||||
function unauthorized(string $message = 'Unauthorized'): void {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'invalid_token', 'error_description' => $message], JSON_UNESCAPED_SLASHES);
|
||||
exit();
|
||||
}
|
||||
|
||||
$authorization = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
|
||||
if ($authorization === '' && function_exists('apache_request_headers')) {
|
||||
$headers = apache_request_headers();
|
||||
if (isset($headers['Authorization'])) {
|
||||
$authorization = $headers['Authorization'];
|
||||
}
|
||||
}
|
||||
|
||||
if (stripos($authorization, 'bearer ') !== 0) {
|
||||
unauthorized('Bearer token required.');
|
||||
}
|
||||
|
||||
$token = trim(substr($authorization, 7));
|
||||
if ($token === '') {
|
||||
unauthorized('Bearer token required.');
|
||||
}
|
||||
|
||||
$oauthService = new OAuthService($pdo);
|
||||
$tokenRecord = $oauthService->getAccessToken($token);
|
||||
|
||||
if (!$tokenRecord) {
|
||||
unauthorized('Access token is invalid or expired.');
|
||||
}
|
||||
|
||||
$oauthService->recordTokenUsage($tokenRecord['token_hash']);
|
||||
|
||||
$personId = (int) $tokenRecord['person_id'];
|
||||
|
||||
$sql = "SELECT p.pkisp_id AS person_id,
|
||||
p.isp_firstname_en,
|
||||
p.isp_lastname_en,
|
||||
p.isp_email,
|
||||
u.isu_name,
|
||||
u.isu_status
|
||||
FROM ist_tbl_people p
|
||||
JOIN ist_tbl_users u ON u.fkisp_id_of = p.pkisp_id
|
||||
WHERE p.pkisp_id = :person_id
|
||||
LIMIT 1";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([':person_id' => $personId]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$user) {
|
||||
unauthorized('Associated user account not found.');
|
||||
}
|
||||
|
||||
$hubUsername = dsp_resolve_jupyterhub_username(
|
||||
$personId,
|
||||
$user['isu_name'] ?? null,
|
||||
$user['isp_email'] ?? null
|
||||
);
|
||||
|
||||
$response = [
|
||||
'sub' => (string) $personId,
|
||||
'person_id' => $personId,
|
||||
'hub_username' => $hubUsername,
|
||||
'username' => $user['isu_name'] ?? null,
|
||||
'email' => $user['isp_email'] ?? null,
|
||||
'first_name' => $user['isp_firstname_en'] ?? null,
|
||||
'last_name' => $user['isp_lastname_en'] ?? null,
|
||||
'role' => $user['isu_status'] ?? null,
|
||||
];
|
||||
|
||||
if (!empty($tokenRecord['scope'])) {
|
||||
$response['scope'] = $tokenRecord['scope'];
|
||||
}
|
||||
|
||||
echo json_encode($response, JSON_UNESCAPED_SLASHES);
|
||||
Reference in New Issue
Block a user