OTP, Groups, Grants,
This commit is contained in:
parent
04f2123bc6
commit
972eeae7d3
136
src/Tor/Api.php
136
src/Tor/Api.php
@ -2,16 +2,24 @@
|
|||||||
|
|
||||||
namespace Tor;
|
namespace Tor;
|
||||||
|
|
||||||
|
use Tor\Controller\ServiceApi;
|
||||||
use Tor\Controller\GrantApi;
|
use Tor\Controller\GrantApi;
|
||||||
use Tor\Controller\TicketApi;
|
use Tor\Controller\TicketApi;
|
||||||
|
|
||||||
|
use Tor\Controller\ApiAuth;
|
||||||
|
|
||||||
use Gac\Routing\Request;
|
use Gac\Routing\Request;
|
||||||
|
use Gac\Routing\Routes;
|
||||||
|
|
||||||
// Implements the dispatcher for Tor's API
|
// Implements the dispatcher for Tor's API
|
||||||
class Api {
|
class Api {
|
||||||
public function init() {
|
public function init() {
|
||||||
global $routes;
|
global $routes;
|
||||||
|
|
||||||
|
$middlewareE = [ [ApiAuth::class, 'authExternal'] ];
|
||||||
|
$middlewareI = [ [ApiAuth::class, 'authInternal'] ];
|
||||||
|
$middlewareS = [ [ApiAuth::class, 'authSystem'] ];
|
||||||
|
|
||||||
// Respond with supported legacy versions and current version of API
|
// Respond with supported legacy versions and current version of API
|
||||||
$routes->add('/api', function (Request $request) {
|
$routes->add('/api', function (Request $request) {
|
||||||
$request->status(200, 'OK')
|
$request->status(200, 'OK')
|
||||||
@ -20,93 +28,109 @@ class Api {
|
|||||||
|
|
||||||
$routes->add('/api/v1', function (Request $request) {
|
$routes->add('/api/v1', function (Request $request) {
|
||||||
$request->status(200, 'OK')
|
$request->status(200, 'OK')
|
||||||
->send([ 'result' => 'unauthorized' ]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Internal: List all users
|
|
||||||
$routes->add('/api/v1/user/list', function (Request $request) {
|
|
||||||
$request->status(200, 'OK')
|
|
||||||
->send([ 'result' => 'ok' ]);
|
->send([ 'result' => 'ok' ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Internal: Assign a given new public key id to username
|
// No Action specified
|
||||||
|
$routes->middleware($middlewareS)->add('/api/v1/user', [ UserApi::class, 'response' ]);
|
||||||
|
|
||||||
|
// System: Assign a given new public key id and cert to username
|
||||||
// Creates user if it doesn't exist
|
// Creates user if it doesn't exist
|
||||||
$routes->add('/api/v1/user/assign', function (Request $request) {
|
$routes->middleware($middlewareS)->add('/api/v1/user/provision/{userName}', [ UserApi::class, 'provision' ]);
|
||||||
$request->status(200, 'OK')
|
|
||||||
->send([ 'result' => 'ok' ]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Internal: Delete User and all Sessions + ExtraData
|
// System: Delete User and all Sessions + ExtraData
|
||||||
$routes->add('/api/v1/user/delete/{username}', function (Request $request) {
|
$routes->middleware($middlewareS)->add('/api/v1/user/delete/{userName}', [ UserApi::class, 'delete' ]);
|
||||||
$request->status(200, 'OK')
|
|
||||||
->send([ 'result' => 'ok' ]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Internal: Create a new permission group
|
// System: Fetch info about user
|
||||||
$routes->add('/api/v1/group/create/{name}', function (Request $request) {
|
$routes->middleware($middlewareS)->add('/api/v1/user/info/{userName}', [ UserApi::class, 'info' ]);
|
||||||
$request->status(200, 'OK')
|
|
||||||
->send([ 'result' => 'ok' ]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Internal: Delete a permission group
|
// System: List all users
|
||||||
$routes->add('/api/v1/group/delete/{name}', function (Request $request) {
|
$routes->middleware($middlewareS)->add('/api/v1/user/list', [ UserApi::class, 'list' ]);
|
||||||
$request->status(200, 'OK')
|
|
||||||
->send([ 'result' => 'ok' ]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Internal: Assign User to Group
|
// No Action specified
|
||||||
$routes->add('/api/v1/group/assign/{name}', function (Request $request) {
|
$routes->middleware($middlewareS)->add('/api/v1/group', [ GroupApi::class, 'response' ]);
|
||||||
$request->status(200, 'OK')
|
|
||||||
->send([ 'result' => 'ok' ]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Internal: Revoke User's Group Membership
|
// System: Change properties of group, like default provisioning for ex.
|
||||||
$routes->add('/api/v1/group/revoke', function (Request $request) {
|
// If not exists: Create a new permission group
|
||||||
$request->status(200, 'OK')
|
$routes->middleware($middlewareS)->add('/api/v1/group/provision/{groupName}', [ GroupApi::class, 'provision' ]);
|
||||||
->send([ 'result' => 'ok' ]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Internal: Delete User and all Sessions + ExtraData
|
// System: Delete a permission group
|
||||||
$routes->add('/api/v1/group/delete', function (Request $request) {
|
$routes->middleware($middlewareS)->add('/api/v1/group/delete/{groupName}', [ GroupApi::class, 'delete' ]);
|
||||||
$request->status(200, 'OK')
|
|
||||||
->send([ 'result' => 'ok' ]);
|
// System: Assign User to Group
|
||||||
});
|
$routes->middleware($middlewareS)->add('/api/v1/group/assign/{groupName}', [ GroupApi::class, 'assign' ]);
|
||||||
|
|
||||||
|
// System: Revoke User's Group Membership
|
||||||
|
$routes->middleware($middlewareS)->add('/api/v1/group/revoke/{groupName}', [ GroupApi::class, 'revoke' ]);
|
||||||
|
|
||||||
|
// Fetch info about group
|
||||||
|
$routes->middleware($middlewareS)->add('/api/v1/group/info/{groupName}', [ GroupApi::class, 'info' ]);
|
||||||
|
|
||||||
|
// List all groups
|
||||||
|
$routes->middleware($middlewareS)->add('/api/v1/group/list', [ GroupApi::class, 'list' ]);
|
||||||
|
|
||||||
|
// List all groups by service
|
||||||
|
$routes->middleware($middlewareS)->add('/api/v1/group/list/{serviceName}', [ GroupApi::class, 'listByService' ]);
|
||||||
|
|
||||||
// No action specified
|
// No action specified
|
||||||
$routes->add('/api/v1/grant', [ GrantApi::class, 'response' ]);
|
$routes->middleware($middlewareE)->add('/api/v1/service', [ ServiceApi::class, 'response' ]);
|
||||||
|
|
||||||
|
// System: Provision a new token or api level for a given service
|
||||||
|
// Creates Service if it does not exist
|
||||||
|
$routes->middleware($middlewareS)->add('/api/v1/service/provision/{serviceName}', [ ServiceApi::class, 'provision' ]);
|
||||||
|
|
||||||
|
// System: Delete a service and all grants, tickets depending on it
|
||||||
|
$routes->middleware($middlewareS)->add('/api/v1/service/delete/{serviceName}', [ ServiceApi::class, 'delete' ]);
|
||||||
|
|
||||||
|
// System: List info about a service
|
||||||
|
$routes->middleware($middlewareS)->add('/api/v1/service/info/{serviceName}', [ ServiceApi::class, 'info' ]);
|
||||||
|
|
||||||
|
// System: List all services
|
||||||
|
$routes->middleware($middlewareS)->add('/api/v1/service/list', [ ServiceApi::class, 'list' ]);
|
||||||
|
|
||||||
|
// ---------------------------------------------------- //
|
||||||
|
|
||||||
|
// No action specified
|
||||||
|
$routes->middleware($middlewareE)->add('/api/v1/grant', [ GrantApi::class, 'response' ]);
|
||||||
|
|
||||||
// Create Ticket Granting Ticket
|
// Create Ticket Granting Ticket
|
||||||
$routes->add('/api/v1/grant/create', [ GrantApi::class, 'create' ]);
|
$routes->middleware($middlewareE)->add('/api/v1/grant/create', [ GrantApi::class, 'create' ]);
|
||||||
|
|
||||||
// Invalidate Ticket Granting Ticket
|
// Invalidate Ticket Granting Ticket
|
||||||
$routes->add('/api/v1/grant/destroy/{grantID}', [ GrantApi::class, 'destroy' ]);
|
$routes->middleware($middlewareE)->add('/api/v1/grant/destroy/{grantID}', [ GrantApi::class, 'destroy' ]);
|
||||||
|
|
||||||
// Poll Authorization Status of Grant
|
// Poll Authorization Status of Grant
|
||||||
$routes->add('/api/v1/grant/status/{grantID}', [ GrantApi::class, 'status' ]);
|
$routes->middleware($middlewareE)->add('/api/v1/grant/status/{grantID}', [ GrantApi::class, 'status' ]);
|
||||||
|
|
||||||
// Internal: List all active Grants
|
// Internal: List all active Grants for current service
|
||||||
$routes->add('/api/v1/grant/list', [ GrantApi::class, 'list' ]);
|
$routes->middleware($middlewareI)->add('/api/v1/grant/list', [ GrantApi::class, 'list' ]);
|
||||||
|
|
||||||
// Internal: Approve of Grant with specified subset of extradata
|
// System: Approve of Grant with specified subset of extradata
|
||||||
$routes->add('/api/v1/grant/approve/{grantID}', [ GrantApi::class, 'approve' ]);
|
$routes->middleware($middlewareS)->add('/api/v1/grant/approve/{grantID}', [ GrantApi::class, 'approve' ]);
|
||||||
|
|
||||||
// Internal: Reject Grant
|
// Internal: Reject Grant
|
||||||
$routes->add('/api/v1/grant/reject/{grantID}', [ GrantApi::class, 'reject' ]);
|
$routes->middleware($middlewareI)->add('/api/v1/grant/reject/{grantID}', [ GrantApi::class, 'reject' ]);
|
||||||
|
|
||||||
// Fetch ID of Session Ticket by using authorized Grant ID
|
// TODO: Decision
|
||||||
|
|
||||||
|
// No action specified
|
||||||
|
$routes->middleware($middlewareE)->add('/api/v1/ticket', [ TicketApi::class, 'response' ]);
|
||||||
|
|
||||||
|
// Fetch ID of Session Ticket by using authorized Grant ID => NEW: Decision OTP
|
||||||
// Useful when polling manually, not neccessary when using callback
|
// Useful when polling manually, not neccessary when using callback
|
||||||
$routes->add('/api/v1/ticket/fetch/{grantID}', [ TicketApi::class, 'fetch' ]);
|
$routes->middleware($middlewareE)->add('/api/v1/ticket/fetch/{grantID}', [ TicketApi::class, 'fetch' ]);
|
||||||
|
|
||||||
// Destroy active Session
|
// Destroy active Session
|
||||||
$routes->add('/api/v1/ticket/destroy/{ticketID}', [ TicketApi::class, 'destroy' ]);
|
$routes->middleware($middlewareE)->add('/api/v1/ticket/destroy/{ticketID}', [ TicketApi::class, 'destroy' ]);
|
||||||
|
|
||||||
// Poll authorization status and start / end date for session ticket
|
// Poll authorization status and start / end date for session ticket
|
||||||
$routes->add('/api/v1/ticket/status/{ticketID}', [ TicketApi::class, 'status' ]);
|
$routes->middleware($middlewareE)->add('/api/v1/ticket/status/{ticketID}', [ TicketApi::class, 'status' ]);
|
||||||
|
|
||||||
// Session KeepAlive
|
// Session KeepAlive
|
||||||
$routes->add('/api/v1/ticket/heartbeat/{ticketID}', [ TicketApi::class, 'heartbeat' ]);
|
$routes->middleware($middlewareE)->add('/api/v1/ticket/heartbeat/{ticketID}', [ TicketApi::class, 'heartbeat' ]);
|
||||||
|
|
||||||
// Internal: List all active Session Tickets
|
// Internal: List all active Session Tickets
|
||||||
$routes->add('/api/v1/ticket/list', [ TicketApi::class, 'list' ]);
|
$routes->middleware($middlewareI)->add('/api/v1/ticket/list', [ TicketApi::class, 'list' ]);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,18 @@ class Utils {
|
|||||||
return ( $position === false ) ? $path : substr($path, 0, $position);
|
return ( $position === false ) ? $path : substr($path, 0, $position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function FetchPostData(): array | bool
|
||||||
|
{
|
||||||
|
// POST
|
||||||
|
$post_data = file_get_contents('php://input');
|
||||||
|
|
||||||
|
// Check if post data exists and auth json node is present
|
||||||
|
if ($post_data != false)
|
||||||
|
return json_decode($post_data, true)['data'] ?? false;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if the browser provided a valid SSL client certificate
|
* Determines if the browser provided a valid SSL client certificate
|
||||||
*
|
*
|
||||||
@ -37,5 +49,44 @@ class Utils {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function OTPFactory(int $length = 6, bool $alpha = false): string
|
||||||
|
{
|
||||||
|
// Take a generator string which consist of
|
||||||
|
// all numeric digits
|
||||||
|
// or all alpha numeric chars
|
||||||
|
if (!$alpha)
|
||||||
|
$generator = "1357902468";
|
||||||
|
else
|
||||||
|
$generator = "qwerASDFGHJKLtzuioQWERTZUIOPpasdfgh102938jklyxcYXCVBNMvbnm4756";
|
||||||
|
|
||||||
|
// Iterate $length times and pick a single character
|
||||||
|
// from generator and append it to $result
|
||||||
|
|
||||||
|
// Login for generating a random character from generator
|
||||||
|
// ---generate a random number
|
||||||
|
// ---take modulus of same with length of generator (say i)
|
||||||
|
// ---append the character at place (i) from generator to result
|
||||||
|
|
||||||
|
$result = "";
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $length; $i++) {
|
||||||
|
$result .= substr($generator, (rand()%(strlen($generator))), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return result
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function TokenFactory(int $blocks = 4): string
|
||||||
|
{
|
||||||
|
$token = "";
|
||||||
|
|
||||||
|
// Generate a token consisting out of $blocks many blocks
|
||||||
|
for ($i = 1; $i <= $blocks; $i++)
|
||||||
|
$token = self::OTPFactory(length: 4, alpha: true) . '-' . $token;
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
109
src/Tor/Controller/ApiAuth.php
Normal file
109
src/Tor/Controller/ApiAuth.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tor\Controller;
|
||||||
|
|
||||||
|
use Tor\Common\Utils;
|
||||||
|
use Tor\Data\AuthLevel;
|
||||||
|
use Tor\Data\Service;
|
||||||
|
|
||||||
|
use Gac\Routing\Request;
|
||||||
|
|
||||||
|
class ApiAuth {
|
||||||
|
|
||||||
|
// Returns decoded array or false if no authorization header/parameter found
|
||||||
|
private static function FetchAuth()
|
||||||
|
{
|
||||||
|
// TODO: Use Authorization Token Header or similar concepts
|
||||||
|
|
||||||
|
// POST
|
||||||
|
$post_data = file_get_contents('php://input');
|
||||||
|
|
||||||
|
// Check if post data exists and auth json node is present
|
||||||
|
if ($post_data != false)
|
||||||
|
$post_data = json_decode($post_data, true)["auth"] ?? false;
|
||||||
|
|
||||||
|
// GET
|
||||||
|
if ($post_data == false && isset($_GET["service"]) && isset($_GET["token"]))
|
||||||
|
$post_data = array("service" => $_GET["service"], "token" => $_GET["token"]);
|
||||||
|
|
||||||
|
if ($post_data != false)
|
||||||
|
{
|
||||||
|
$srv_value = trim($post_data['service'] ?? "");
|
||||||
|
$token_value = trim($post_data['token'] ?? "");
|
||||||
|
|
||||||
|
if (!empty($srv_value) && !empty($token_value))
|
||||||
|
return array("service" => $srv_value, "token" => $token_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function ProcessAuth()
|
||||||
|
{
|
||||||
|
global $data, $api_service;
|
||||||
|
|
||||||
|
// We already have acquired authorization
|
||||||
|
// INFO: This might not be instance safe
|
||||||
|
if (isset($api_service))
|
||||||
|
return;
|
||||||
|
|
||||||
|
$authData = self::fetchAuth();
|
||||||
|
|
||||||
|
// Check if service exists
|
||||||
|
if ($authData != false && $data->exists(Service::class, $authData['service']))
|
||||||
|
{
|
||||||
|
$service = $data->load(Service::class, $authData['service']);
|
||||||
|
if ($service->token != null && !empty(trim($service->token)))
|
||||||
|
{
|
||||||
|
// Check if authorization token matches
|
||||||
|
if ($service->token == $authData['token'])
|
||||||
|
$api_service = $service;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authExternal(\Gac\Routing\Request $request)
|
||||||
|
{
|
||||||
|
$this->auth($request, AuthLevel::External);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authInternal(\Gac\Routing\Request $request)
|
||||||
|
{
|
||||||
|
$this->auth($request, AuthLevel::Internal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authSystem(\Gac\Routing\Request $request)
|
||||||
|
{
|
||||||
|
$this->auth($request, AuthLevel::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Middleware for api authentication checking
|
||||||
|
private function auth(\Gac\Routing\Request $request, AuthLevel $minLevel = AuthLevel::External)
|
||||||
|
{
|
||||||
|
if (!self::MatchesLevel($minLevel)) {
|
||||||
|
$request->status(401, 'Unauthorized')->send(["error" => ["message" => "unauthorized"]]);
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns current service if authorization successful
|
||||||
|
public static function GetService(): Service | null
|
||||||
|
{
|
||||||
|
global $api_service;
|
||||||
|
|
||||||
|
self::ProcessAuth();
|
||||||
|
|
||||||
|
return $api_service ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function MatchesLevel(AuthLevel $level): bool
|
||||||
|
{
|
||||||
|
if (self::GetService() != null
|
||||||
|
&& self::GetService()->level->value >= $level->value)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
@ -7,6 +7,8 @@ use Tor\Data\Ticket;
|
|||||||
use Tor\Data\User;
|
use Tor\Data\User;
|
||||||
use Tor\Data\Service;
|
use Tor\Data\Service;
|
||||||
|
|
||||||
|
use Tor\Controller\ApiAuth;
|
||||||
|
|
||||||
use Gac\Routing\Request;
|
use Gac\Routing\Request;
|
||||||
|
|
||||||
class GrantApi extends BaseApi {
|
class GrantApi extends BaseApi {
|
||||||
@ -21,20 +23,21 @@ class GrantApi extends BaseApi {
|
|||||||
while ($data->exists(Grant::class , $uniqid))
|
while ($data->exists(Grant::class , $uniqid))
|
||||||
$uniqid = uniqid();
|
$uniqid = uniqid();
|
||||||
|
|
||||||
// TODO: Services
|
|
||||||
$service = new Service();
|
|
||||||
$service->id = uniqid();
|
|
||||||
|
|
||||||
$grant = new Grant();
|
$grant = new Grant();
|
||||||
$grant->id = $uniqid;
|
$grant->id = $uniqid;
|
||||||
$grant->service = $service;
|
$grant->service = ApiAuth::GetService();
|
||||||
$grant->create = time();
|
$grant->create = time();
|
||||||
|
|
||||||
$data->save($grant);
|
$data->save($grant);
|
||||||
$data->save($service);
|
|
||||||
|
|
||||||
$request->status(201, 'Created')
|
$response = [
|
||||||
->send([ 'grant' => $grant->id, 'service' => $grant->service->flatIdentifier(), 'result' => 'created' ]);
|
'grant' => $grant->id,
|
||||||
|
'service' => $grant->service->flatIdentifier(),
|
||||||
|
'create' => $grant->create,
|
||||||
|
'result' => 'created'
|
||||||
|
];
|
||||||
|
|
||||||
|
$request->status(201, 'Created')->send($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy a grant and all dependencies
|
// Destroy a grant and all dependencies
|
||||||
@ -59,7 +62,7 @@ class GrantApi extends BaseApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal: Approve Grant (Only works if not authorized yet)
|
// System: Approve Grant (Only works if not authorized yet)
|
||||||
function approve(\Gac\Routing\Request $request, string $grantID) {
|
function approve(\Gac\Routing\Request $request, string $grantID) {
|
||||||
global $data;
|
global $data;
|
||||||
|
|
||||||
@ -147,13 +150,18 @@ class GrantApi extends BaseApi {
|
|||||||
function list(\Gac\Routing\Request $request) {
|
function list(\Gac\Routing\Request $request) {
|
||||||
global $data;
|
global $data;
|
||||||
|
|
||||||
// TODO:
|
// Get name of authenticated service
|
||||||
$serviceName = '638bcc3500810';
|
$serviceName = ApiAuth::GetService()->flatIdentifier();
|
||||||
|
|
||||||
$grants = $data->fromIndex(Service::class, $serviceName, findClass: Grant::class);
|
$grants = $data->fromIndex(Service::class, $serviceName, findClass: Grant::class);
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'grants' => $grants,
|
||||||
|
'result' => 'ok'
|
||||||
|
];
|
||||||
|
|
||||||
$request->status(200, 'OK')
|
$request->status(200, 'OK')
|
||||||
->send([ 'grants' => $grants, 'result' => 'ok' ]);
|
->send($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default response if no action is defined
|
// Default response if no action is defined
|
||||||
|
137
src/Tor/Controller/ServiceApi.php
Normal file
137
src/Tor/Controller/ServiceApi.php
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tor\Controller;
|
||||||
|
|
||||||
|
use Tor\Data\Grant;
|
||||||
|
use Tor\Data\Ticket;
|
||||||
|
use Tor\Data\Service;
|
||||||
|
use Tor\Data\AuthLevel;
|
||||||
|
|
||||||
|
use Tor\Common\Utils;
|
||||||
|
|
||||||
|
use Gac\Routing\Request;
|
||||||
|
|
||||||
|
class ServiceApi extends BaseApi {
|
||||||
|
|
||||||
|
// System: Provision new token or auth level for service
|
||||||
|
// Create service if it doesn't exist
|
||||||
|
function provision(\Gac\Routing\Request $request, string $serviceName) {
|
||||||
|
global $data;
|
||||||
|
|
||||||
|
$exists = $data->exists(Service::class, $serviceName);
|
||||||
|
|
||||||
|
$post_data = Utils::FetchPostData();
|
||||||
|
|
||||||
|
if ($post_data != false) {
|
||||||
|
$new_token = trim($post_data['token'] ?? "");
|
||||||
|
$new_level = trim($post_data['level'] ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$exists) {
|
||||||
|
$service = new Service();
|
||||||
|
$service->id = $serviceName;
|
||||||
|
$service->level = AuthLevel::External;
|
||||||
|
$service->token = Utils::TokenFactory();
|
||||||
|
} else {
|
||||||
|
$service = $data->load(Service::class, $serviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($new_token))
|
||||||
|
$service->token = $new_token;
|
||||||
|
|
||||||
|
if (!empty($new_level))
|
||||||
|
$service->level = AuthLevel::from($new_level);
|
||||||
|
|
||||||
|
$data->save($service);
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'service' => $service->flatIdentifier(),
|
||||||
|
'token' => $service->token,
|
||||||
|
'level' => $service->level->value,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!$exists) {
|
||||||
|
$result['result'] = 'created';
|
||||||
|
$request->status(201, 'Created')
|
||||||
|
->send($result);
|
||||||
|
} else {
|
||||||
|
$result['result'] = 'provisioned';
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// System: Delete a service and all dependencies
|
||||||
|
function delete(\Gac\Routing\Request $request, string $serviceName) {
|
||||||
|
global $data;
|
||||||
|
|
||||||
|
$exists = $data->exists(Service::class, $serviceName);
|
||||||
|
|
||||||
|
if (!$exists) {
|
||||||
|
$request->status(404, 'Not Found')->send(["error" => ["message" => "not found"]]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$grants = $data->fromIndex(Service::class, $serviceName, findClass: Grant::class);
|
||||||
|
|
||||||
|
if (!empty($grants))
|
||||||
|
{
|
||||||
|
foreach ($grants as $grant)
|
||||||
|
{
|
||||||
|
$tickets = $data->fromIndex(Grant::class, $grant->flatIdentifier(), findClass: Ticket::class);
|
||||||
|
|
||||||
|
$data->delete(Grant::class, $grant->flatIdentifier());
|
||||||
|
|
||||||
|
foreach ($tickets as $ticket)
|
||||||
|
$data->delete(Ticket::class, $ticket->flatIdentifier());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$data->delete(Service::class, $serviceName);
|
||||||
|
$request->status(200, 'OK')->send([ 'result' => 'destroyed' ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// System: Fetch info about a Service
|
||||||
|
function info(\Gac\Routing\Request $request, string $serviceName) {
|
||||||
|
global $data;
|
||||||
|
|
||||||
|
$exists = $data->exists(Service::class, $serviceName);
|
||||||
|
|
||||||
|
if (!$exists) {
|
||||||
|
$request->status(404, 'Not Found')->send(["error" => ["message" => "not found"]]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$service = $data->load(Service::class, $serviceName);
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'service' => $service->flatIdentifier(),
|
||||||
|
'token' => $service->token,
|
||||||
|
'level' => $service->level->value,
|
||||||
|
'result' => 'ok'
|
||||||
|
];
|
||||||
|
|
||||||
|
$request->status(200, 'OK')->send($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// System: List all Services
|
||||||
|
function list(\Gac\Routing\Request $request) {
|
||||||
|
global $data;
|
||||||
|
|
||||||
|
$services = $data->listAll(Service::class);
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'services' => $services,
|
||||||
|
'result' => 'ok'
|
||||||
|
];
|
||||||
|
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default response if no action is defined
|
||||||
|
function response(\Gac\Routing\Request $request) {
|
||||||
|
$request->status(200, 'OK')->send([ 'result' => 'ok' ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -10,54 +10,94 @@ use Gac\Routing\Request;
|
|||||||
class TicketApi extends BaseApi {
|
class TicketApi extends BaseApi {
|
||||||
|
|
||||||
// Fetch ID of Session Ticket by using authorized Grant ID
|
// Fetch ID of Session Ticket by using authorized Grant ID
|
||||||
function fetch(\Gac\Routing\Request $request) {
|
function fetch(\Gac\Routing\Request $request, string $grantId) {
|
||||||
global $data;
|
global $data;
|
||||||
|
|
||||||
// TODO: Identifier
|
$ticket = $data->fromIndex(Grant::class, $id, Ticket::class)[0] ?? null;
|
||||||
$identifier = 'cats';
|
|
||||||
$exists = $data->exists(Grant::class, $identifier);
|
|
||||||
|
|
||||||
if (!$exists)
|
if ($ticket == null)
|
||||||
|
$request->status(200, 'OK')->send([ 'ticket' => $ticket->id, 'result' => 'found' ]);
|
||||||
|
else
|
||||||
$request->status(404, 'Not Found')->send(["error" => ["message" => "not found"]]);
|
$request->status(404, 'Not Found')->send(["error" => ["message" => "not found"]]);
|
||||||
else {
|
|
||||||
// TODO: Create Ticket on Approval instead and ... load by index
|
|
||||||
$ticket = new Ticket();
|
|
||||||
$ticket->grant = $data->load(Grant::class, $identifier);
|
|
||||||
$ticket->user = new User();
|
|
||||||
$ticket->start = time();
|
|
||||||
$ticket->end = time() + 86400;
|
|
||||||
|
|
||||||
$data->save($ticket);
|
|
||||||
|
|
||||||
$request->status(201, 'Created')
|
|
||||||
->send([ 'ticket' => $ticket->id, 'result' => 'created' ]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy active session
|
// Destroy active session
|
||||||
function destroy(\Gac\Routing\Request $request) {
|
function destroy(\Gac\Routing\Request $request, string $ticketId) {
|
||||||
global $data;
|
global $data;
|
||||||
|
|
||||||
// TODO: Identifier
|
$exists = $data->exists(Ticket::class, $ticketId);
|
||||||
$identifier = 'cats';
|
|
||||||
$exists = $data->exists(Grant::class, $identifier);
|
|
||||||
|
|
||||||
if (!$exists)
|
if (!$exists) {
|
||||||
$request->status(404, 'Not Found')->send(["error" => ["message" => "not found"]]);
|
$request->status(404, 'Not Found')->send(["error" => ["message" => "not found"]]);
|
||||||
else
|
} else {
|
||||||
$data->delete(Grant::class, $identifier);
|
$ticket = $data->load(Ticket::class, $ticketId);
|
||||||
|
$data->delete(Ticket::class, $ticketId);
|
||||||
|
|
||||||
|
// Also delete grant
|
||||||
|
$data->delete(Grant::class, $ticket->grant->flatIdentifier());
|
||||||
|
|
||||||
|
$request->status(200, 'OK')->send([ 'result' => 'destroyed' ]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll authorization status and start / end date for session ticket
|
// Poll authorization status and start / end date for session ticket
|
||||||
function status(\Gac\Routing\Request $request) {
|
function status(\Gac\Routing\Request $request, string $ticketId) {
|
||||||
$request->status(200, 'OK')
|
global $data;
|
||||||
->send([ 'result' => 'ok' ]);
|
|
||||||
|
$exists = $data->exists(Ticket::class, $ticketId);
|
||||||
|
|
||||||
|
if (!$exists) {
|
||||||
|
$request->status(404, 'Not Found')->send(["error" => ["message" => "not found"]]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$ticket = $data->load(Ticket::class, $ticketId);
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'user' => $ticket->user->flatIdentifier(),
|
||||||
|
'grant' => $ticket->grant->flatIdentifier(),
|
||||||
|
'start' => $ticket->start,
|
||||||
|
'end' = $ticket->end,
|
||||||
|
'status' => 'valid',
|
||||||
|
'result' => 'ok'
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($ticket->end < time())
|
||||||
|
$result["status"] = "expired";
|
||||||
|
|
||||||
|
$request->status(200, 'OK')->send($result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session KeepAlive
|
// Session KeepAlive
|
||||||
function heartbeat(\Gac\Routing\Request $request) {
|
function heartbeat(\Gac\Routing\Request $request) {
|
||||||
$request->status(200, 'OK')
|
global $data;
|
||||||
->send([ 'result' => 'ok' ]);
|
|
||||||
|
$exists = $data->exists(Ticket::class, $ticketId);
|
||||||
|
|
||||||
|
if (!$exists) {
|
||||||
|
$request->status(404, 'Not Found')->send(["error" => ["message" => "not found"]]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$ticket = $data->load(Ticket::class, $ticketId);
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'start' => $ticket->start,
|
||||||
|
'end' = $ticket->end,
|
||||||
|
'result' => 'expired'
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($ticket->end >= time())
|
||||||
|
{
|
||||||
|
$ticket->end = time() + 86400;
|
||||||
|
$data->save($ticket);
|
||||||
|
|
||||||
|
$result['end'] = $ticket->end;
|
||||||
|
$result['result'] = 'ok';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->status(200, 'OK')->send($result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal: List all tickets
|
// Internal: List all tickets
|
||||||
|
144
src/Tor/Controller/UserApi.php
Normal file
144
src/Tor/Controller/UserApi.php
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tor\Controller;
|
||||||
|
|
||||||
|
use Tor\Data\Group;
|
||||||
|
use Tor\Data\User;
|
||||||
|
|
||||||
|
use Gac\Routing\Request;
|
||||||
|
|
||||||
|
class UserApi extends BaseApi {
|
||||||
|
|
||||||
|
// System: Provision new token or auth level for service
|
||||||
|
// Create service if it doesn't exist
|
||||||
|
function provision(\Gac\Routing\Request $request, string $serviceName) {
|
||||||
|
global $data;
|
||||||
|
|
||||||
|
$exists = $data->exists(Service::class, $serviceName);
|
||||||
|
|
||||||
|
$post_data = Utils::FetchPostData();
|
||||||
|
|
||||||
|
if ($post_data != false) {
|
||||||
|
$new_token = trim($post_data['token'] ?? "");
|
||||||
|
$new_level = trim($post_data['level'] ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$exists) {
|
||||||
|
$service = new Service();
|
||||||
|
$service->id = $serviceName;
|
||||||
|
$service->level = AuthLevel::External;
|
||||||
|
$service->token = Utils::TokenFactory();
|
||||||
|
} else {
|
||||||
|
$service = $data->load(Service::class, $serviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($new_token))
|
||||||
|
$service->token = $new_token;
|
||||||
|
|
||||||
|
if (!empty($new_level))
|
||||||
|
$service->level = AuthLevel::from($new_level);
|
||||||
|
|
||||||
|
$data->save($service);
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'service' => $service->flatIdentifier(),
|
||||||
|
'token' => $service->token,
|
||||||
|
'level' => $service->level->value,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!$exists) {
|
||||||
|
$result['result'] = 'created';
|
||||||
|
$request->status(201, 'Created')
|
||||||
|
->send($result);
|
||||||
|
} else {
|
||||||
|
$result['result'] = 'provisioned';
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// System: Delete an User and all dependencies
|
||||||
|
function delete(\Gac\Routing\Request $request, string $userName) {
|
||||||
|
global $data;
|
||||||
|
|
||||||
|
$exists = $data->exists(User::class, $userName);
|
||||||
|
|
||||||
|
if (!$exists) {
|
||||||
|
$request->status(404, 'Not Found')->send(["error" => ["message" => "not found"]]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$tickets = $data->fromIndex(User::class, $userName, findClass: Tickets::class);
|
||||||
|
|
||||||
|
if (!empty($tickets))
|
||||||
|
{
|
||||||
|
foreach ($tickets as $ticket)
|
||||||
|
{
|
||||||
|
$grants = $data->fromIndex(Ticket::class, $ticket->flatIdentifier(), findClass: Grant::class);
|
||||||
|
|
||||||
|
$data->delete(Ticket::class, $ticket->flatIdentifier());
|
||||||
|
|
||||||
|
foreach ($grants as $grant)
|
||||||
|
$data->delete(Grant::class, $grant->flatIdentifier());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$data->delete(User::class, $userName);
|
||||||
|
$request->status(200, 'OK')->send([ 'result' => 'destroyed' ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// System: Fetch info about an User
|
||||||
|
function info(\Gac\Routing\Request $request, string $userName) {
|
||||||
|
global $data;
|
||||||
|
|
||||||
|
$exists = $data->exists(User::class, $userName);
|
||||||
|
|
||||||
|
if (!$exists) {
|
||||||
|
$request->status(404, 'Not Found')->send(["error" => ["message" => "not found"]]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$user = $data->load(User::class, $userName);
|
||||||
|
|
||||||
|
$groups = [];
|
||||||
|
|
||||||
|
if (isset($user->groups) && $user->groups != null)
|
||||||
|
{
|
||||||
|
foreach ($user->groups as $group)
|
||||||
|
$groups[] = $group->flatIdentifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'user' => $user->flatIdentifier(),
|
||||||
|
'cert' => $user->cert,
|
||||||
|
'serial' => $user->serial,
|
||||||
|
'create' => $user->create,
|
||||||
|
'expire' => $user->expire,
|
||||||
|
'groups' => $groups,
|
||||||
|
'result' => 'ok'
|
||||||
|
];
|
||||||
|
|
||||||
|
$request->status(200, 'OK')->send($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// System: List all Users
|
||||||
|
function list(\Gac\Routing\Request $request) {
|
||||||
|
global $data;
|
||||||
|
|
||||||
|
$users = $data->listAll(User::class);
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'users' => $users,
|
||||||
|
'result' => 'ok'
|
||||||
|
];
|
||||||
|
|
||||||
|
$request->status(200, 'OK')
|
||||||
|
->send($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default response if no action is defined
|
||||||
|
function response(\Gac\Routing\Request $request) {
|
||||||
|
$request->status(200, 'OK')->send([ 'result' => 'ok' ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
src/Tor/Data/AuthLevel.php
Normal file
11
src/Tor/Data/AuthLevel.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
namespace Tor\Data;
|
||||||
|
|
||||||
|
enum AuthLevel: int
|
||||||
|
{
|
||||||
|
case Disabled = -1;
|
||||||
|
case External = 0;
|
||||||
|
case Internal = 1;
|
||||||
|
case System = 9;
|
||||||
|
}
|
||||||
|
?>
|
11
src/Tor/Data/Decision.php
Normal file
11
src/Tor/Data/Decision.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tor\Data;
|
||||||
|
|
||||||
|
use Tor\Data\Status;
|
||||||
|
|
||||||
|
class Decision extends BaseEntity {
|
||||||
|
public string $otp;
|
||||||
|
public int $date;
|
||||||
|
public Status $result;
|
||||||
|
}
|
@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
namespace Tor\Data;
|
namespace Tor\Data;
|
||||||
|
|
||||||
|
use Tor\Data\Service;
|
||||||
|
use Tor\Data\Status;
|
||||||
|
|
||||||
class Grant extends BaseEntity {
|
class Grant extends BaseEntity {
|
||||||
public Service $service;
|
public Service $service;
|
||||||
|
public Status $status;
|
||||||
public int $create;
|
public int $create;
|
||||||
}
|
}
|
||||||
|
10
src/Tor/Data/Group.php
Normal file
10
src/Tor/Data/Group.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tor\Data;
|
||||||
|
|
||||||
|
use Tor\Data\Service;
|
||||||
|
|
||||||
|
class Group extends BaseEntity {
|
||||||
|
public Service $service;
|
||||||
|
public bool $default;
|
||||||
|
}
|
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace Tor\Data;
|
namespace Tor\Data;
|
||||||
|
|
||||||
|
use Tor\Data\AuthLevel;
|
||||||
|
|
||||||
class Service extends BaseEntity {
|
class Service extends BaseEntity {
|
||||||
public string $token;
|
public string $token;
|
||||||
|
public AuthLevel $level;
|
||||||
}
|
}
|
||||||
|
10
src/Tor/Data/Status.php
Normal file
10
src/Tor/Data/Status.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
namespace Tor\Data;
|
||||||
|
|
||||||
|
enum Status: int
|
||||||
|
{
|
||||||
|
case Pending = -1;
|
||||||
|
case Rejected = 0;
|
||||||
|
case Granted = 1;
|
||||||
|
}
|
||||||
|
?>
|
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace Tor\Data;
|
namespace Tor\Data;
|
||||||
|
|
||||||
|
use Tor\Data\Grant;
|
||||||
|
use Tor\Data\User;
|
||||||
|
|
||||||
class Ticket extends BaseEntity {
|
class Ticket extends BaseEntity {
|
||||||
public Grant $grant;
|
public Grant $grant;
|
||||||
public User $user;
|
public User $user;
|
||||||
|
@ -5,4 +5,10 @@ namespace Tor\Data;
|
|||||||
class User extends BaseEntity {
|
class User extends BaseEntity {
|
||||||
public string $serial;
|
public string $serial;
|
||||||
public string $cert;
|
public string $cert;
|
||||||
|
public int $create;
|
||||||
|
public int $expire;
|
||||||
|
/**
|
||||||
|
* @type Tor\Data\Group
|
||||||
|
*/
|
||||||
|
public array $groups = [];
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,8 @@
|
|||||||
<form action="/consent" method="post" style="display: inline;">
|
<form action="/consent" method="post" style="display: inline;">
|
||||||
A service has requested access to your Identity<br><br><label><b>Username:</b></label><br><input name="username" value="{{ username | e }}Hankypants" disabled><br>
|
A service has requested access to your Identity<br><br><label><b>Username:</b></label><br><input name="username" value="{{ username | e }}Hankypants" disabled><br>
|
||||||
<textarea rows=10 cols=40 disabled>
|
<textarea rows=10 cols=40 disabled>
|
||||||
Common Name:
|
|
||||||
Hankypants
|
</textarea><br>
|
||||||
Issuer:
|
|
||||||
tor.161.sh
|
|
||||||
Key Fingerprint:
|
|
||||||
CA:3A:85:33:03:1F:39:7B
|
|
||||||
Key ID:
|
|
||||||
A5:CE:37:EA:EB:B0:75:0E</textarea><br>
|
|
||||||
<i>Please verify before continuing.</i><br>
|
<i>Please verify before continuing.</i><br>
|
||||||
<input type="submit" name="confirm" value="Consent" onclick="myagent.play('Print'); myagent.play('GetTechy');"><input type="submit" name="deny" value="Deny Access" onclick="myagent.play('Print'); myagent.play('GetTechy');">
|
<input type="submit" name="confirm" value="Consent" onclick="myagent.play('Print'); myagent.play('GetTechy');"><input type="submit" name="deny" value="Deny Access" onclick="myagent.play('Print'); myagent.play('GetTechy');">
|
||||||
</form>
|
</form>
|
||||||
|
Loading…
Reference in New Issue
Block a user