Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 76 additions & 9 deletions Web/Services/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@
'You must be authenticated in order to access this service.<br/>' . $server->GetFullServiceUrl(WebServices::Login)
);
}

$userSession = ServiceLocator::GetUserSession();
// Admin users can always use the API
if ($userSession->IsAdmin) {
return;
}

// Check if the user is allowed API access to the route
if (!$registry->IsUserAllowedApiAccess(routeName: $routeName, userId: $userSession->UserId)) {
$app->halt(
RestResponse::FORBIDDEN,
'You are not authorized to access this service.<br/>'
);
}
}
});

Expand Down Expand Up @@ -84,7 +98,12 @@ function RegisterHelp(SlimWebServiceRegistry $registry, \Slim\Slim $app)

function RegisterAuthentication(SlimServer $server, SlimWebServiceRegistry $registry)
{
$webService = new AuthenticationWebService($server, new WebServiceAuthentication(PluginManager::Instance()->LoadAuthentication(), new UserSessionRepository()));
$api_access_group_id = GetConfigGroup(config_group: "Authentication.group");
$webService = new AuthenticationWebService(
$server,
new WebServiceAuthentication(PluginManager::Instance()->LoadAuthentication(), new UserSessionRepository()),
api_access_group_id: $api_access_group_id
);

$category = new SlimWebServiceRegistryCategory('Authentication');
$category->AddPost('SignOut/', [$webService, 'SignOut'], WebServices::Logout);
Expand All @@ -97,7 +116,10 @@ function RegisterReservations(SlimServer $server, SlimWebServiceRegistry $regist
$readService = new ReservationsWebService($server, new ReservationViewRepository(), new PrivacyFilter(new ReservationAuthorization(PluginManager::Instance()->LoadAuthorization())), new AttributeService(new AttributeRepository()));
$writeService = new ReservationWriteWebService($server, new ReservationSaveController(new ReservationPresenterFactory()));

$category = new SlimWebServiceRegistryCategory('Reservations');
$roGroupId = GetConfigGroup('Reservations.ro.group');
$rwGroupId = GetConfigGroup('Reservations.rw.group');
$category = new SlimWebServiceRegistryCategory('Reservations', roGroupId: $roGroupId, rwGroupId: $rwGroupId);

$category->AddSecurePost('/', [$writeService, 'Create'], WebServices::CreateReservation);
$category->AddSecureGet('/', [$readService, 'GetReservations'], WebServices::AllReservations);
$category->AddSecureGet('/:referenceNumber', [$readService, 'GetReservation'], WebServices::GetReservation);
Expand All @@ -116,7 +138,10 @@ function RegisterResources(SlimServer $server, SlimWebServiceRegistry $registry)
$attributeService = new AttributeService(new AttributeRepository());
$webService = new ResourcesWebService($server, $resourceRepository, $attributeService, new ReservationViewRepository());
$writeWebService = new ResourcesWriteWebService($server, new ResourceSaveController($resourceRepository, new ResourceRequestValidator($attributeService)));
$category = new SlimWebServiceRegistryCategory('Resources');

$roGroupId = GetConfigGroup('Resources.ro.group');
$category = new SlimWebServiceRegistryCategory('Resources', roGroupId: $roGroupId);

$category->AddGet('/Status', [$webService, 'GetStatuses'], WebServices::GetStatuses);
$category->AddSecureGet('/', [$webService, 'GetAll'], WebServices::AllResources);
$category->AddSecureGet('/Status/Reasons', [$webService, 'GetStatusReasons'], WebServices::GetStatusReasons);
Expand All @@ -133,7 +158,10 @@ function RegisterResources(SlimServer $server, SlimWebServiceRegistry $registry)
function RegisterAccessories(SlimServer $server, SlimWebServiceRegistry $registry)
{
$webService = new AccessoriesWebService($server, new ResourceRepository(), new AccessoryRepository());
$category = new SlimWebServiceRegistryCategory('Accessories');

$roGroupId = GetConfigGroup('Accessories.ro.group');
$category = new SlimWebServiceRegistryCategory('Accessories', roGroupId: $roGroupId);

$category->AddSecureGet('/', [$webService, 'GetAll'], WebServices::AllAccessories);
$category->AddSecureGet('/:accessoryId', [$webService, 'GetAccessory'], WebServices::GetAccessory);
$registry->AddCategory($category);
Expand All @@ -147,7 +175,10 @@ function RegisterUsers(SlimServer $server, SlimWebServiceRegistry $registry)
$server,
new UserSaveController(new ManageUsersServiceFactory(), new UserRequestValidator($attributeService, new UserRepository()))
);
$category = new SlimWebServiceRegistryCategory('Users');

$roGroupId = GetConfigGroup('Users.ro.group');
$category = new SlimWebServiceRegistryCategory('Users', roGroupId: $roGroupId);

$category->AddSecureGet('/', [$webService, 'GetUsers'], WebServices::AllUsers);
$category->AddSecureGet('/:userId', [$webService, 'GetUser'], WebServices::GetUser);
$category->AddAdminPost('/', [$writeWebService, 'Create'], WebServices::CreateUser);
Expand All @@ -160,7 +191,10 @@ function RegisterUsers(SlimServer $server, SlimWebServiceRegistry $registry)
function RegisterSchedules(SlimServer $server, SlimWebServiceRegistry $registry)
{
$webService = new SchedulesWebService($server, new ScheduleRepository(), new PrivacyFilter(new ReservationAuthorization(PluginManager::Instance()->LoadAuthorization())));
$category = new SlimWebServiceRegistryCategory('Schedules');

$roGroupId = GetConfigGroup('Schedules.ro.group');
$category = new SlimWebServiceRegistryCategory('Schedules', roGroupId: $roGroupId);

$category->AddSecureGet('/', [$webService, 'GetSchedules'], WebServices::AllSchedules);
$category->AddSecureGet('/:scheduleId', [$webService, 'GetSchedule'], WebServices::GetSchedule);
$category->AddSecureGet('/:scheduleId/Slots', [$webService, 'GetSlots'], WebServices::GetScheduleSlots);
Expand All @@ -172,7 +206,9 @@ function RegisterAttributes(SlimServer $server, SlimWebServiceRegistry $registry
$webService = new AttributesWebService($server, new AttributeService(new AttributeRepository()));
$writeWebService = new AttributesWriteWebService($server, new AttributeSaveController(new AttributeRepository()));

$category = new SlimWebServiceRegistryCategory('Attributes');
$roGroupId = GetConfigGroup('Attributes.ro.group');
$category = new SlimWebServiceRegistryCategory('Attributes', roGroupId: $roGroupId);

$category->AddSecureGet('Category/:categoryId', [$webService, 'GetAttributes'], WebServices::AllCustomAttributes);
$category->AddSecureGet('/:attributeId', [$webService, 'GetAttribute'], WebServices::GetCustomAttribute);
$category->AddAdminPost('/', [$writeWebService, 'Create'], WebServices::CreateCustomAttribute);
Expand All @@ -187,7 +223,8 @@ function RegisterGroups(SlimServer $server, SlimWebServiceRegistry $registry)
$webService = new GroupsWebService($server, $groupRepository, $groupRepository);
$writeWebService = new GroupsWriteWebService($server, new GroupSaveController($groupRepository, new ResourceRepository(), new ScheduleRepository()));

$category = new SlimWebServiceRegistryCategory('Groups');
$roGroupId = GetConfigGroup('Groups.ro.group');
$category = new SlimWebServiceRegistryCategory('Groups', roGroupId: $roGroupId);

$category->AddSecureGet('/', [$webService, 'GetGroups'], WebServices::AllGroups);
$category->AddSecureGet('/:groupId', [$webService, 'GetGroup'], WebServices::GetGroup);
Expand All @@ -211,11 +248,41 @@ function RegisterAccounts(SlimServer $server, SlimWebServiceRegistry $registry)

$webService = new AccountWebService($server, $controller);

$category = new SlimWebServiceRegistryCategory('Accounts');
$roGroupId = GetConfigGroup('Accounts.ro.group');
$rwGroupId = GetConfigGroup('Accounts.rw.group');
$category = new SlimWebServiceRegistryCategory('Accounts', roGroupId: $roGroupId, rwGroupId: $rwGroupId);

$category->AddPost('/', [$webService, 'Create'], WebServices::CreateAccount);
$category->AddSecurePost('/:userId', [$webService, 'Update'], WebServices::UpdateAccount);
$category->AddSecurePost('/:userId/Password', [$webService, 'UpdatePassword'], WebServices::UpdateAccountPassword);
$category->AddSecureGet('/:userId', [$webService, 'GetAccount'], WebServices::GetAccount);

$registry->AddCategory($category);
}

function GetConfigGroup(string $config_group): string|null {
$group_name = Configuration::Instance()->GetSectionKey(ConfigSection::API, $config_group) ?? '';
if ($group_name == '') {
return null;
}
$groupRepository = new GroupRepository();
$groups = $groupRepository->GetList()->Results();
foreach ($groups as $group) {
if ($group->Name == $group_name) {
return $group->Id();
}
}
die("Unable to find group: '$group_name' for API group '$config_group'. Please contact the administrator to resolve this issue in the `config.php` file.");
return null;
}

function IsUserInGroup(string|int $groupId, string|int $userId): bool {
$groupRepository = new GroupRepository();
$group = $groupRepository->LoadById($groupId);
foreach ($group->UserIds() as $groupUserId) {
if ($groupUserId == $userId) {
return true;
}
}
return false;
}
14 changes: 12 additions & 2 deletions WebServices/AuthenticationWebService.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ class AuthenticationWebService
* @var IWebServiceAuthentication
*/
private $authentication;
private int|string|null $api_access_group_id; // If specified and user not in group then authentication will be denied

public function __construct(IRestServer $server, IWebServiceAuthentication $authentication)
public function __construct(IRestServer $server, IWebServiceAuthentication $authentication, int|string|null $api_access_group_id = null)
{
$this->server = $server;
$this->authentication = $authentication;
$this->api_access_group_id = $api_access_group_id;
}

/**
Expand All @@ -51,6 +53,15 @@ public function Authenticate()
$isValid = $this->authentication->Validate($username, $password);
if ($isValid) {
Log::Debug('WebService Authenticate, user %s was authenticated', $username);
$session = $this->authentication->Login($username);

if (!$session->IsAdmin && is_numeric($this->api_access_group_id)) {
if (!IsUserInGroup(groupId: $this->api_access_group_id, userId: $session->UserId)) {
Log::Debug('WebService Authenticate, user %s was denied API access', $username);
$this->server->WriteResponse(AuthenticationResponse::NotAuthorized(), statusCode: RestResponse::FORBIDDEN);
return;
}
}

$version = 0;
$reader = ServiceLocator::GetDatabase()->Query(new GetVersionCommand());
Expand All @@ -59,7 +70,6 @@ public function Authenticate()
}
$reader->Free();

$session = $this->authentication->Login($username);
Log::Debug('SessionToken=%s', $session->SessionToken);
$this->server->WriteResponse(AuthenticationResponse::Success($this->server, $session, $version));
} else {
Expand Down
7 changes: 7 additions & 0 deletions WebServices/Responses/AuthenticationResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ public static function Failed()
return $response;
}

public static function NotAuthorized()
{
$response = new AuthenticationResponse();
$response->message = 'Login failed. API access not authorized.';
return $response;
}

public static function Example()
{
return new ExampleAuthenticationResponse();
Expand Down
46 changes: 46 additions & 0 deletions config/config.devel.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,49 @@
$conf['settings']['logging']['folder'] = '/var/log/librebooking/log'; //Absolute path to folder were the log will be written, writing permissions to the folder are required
$conf['settings']['logging']['level'] = 'debug'; //Set to none disable logs, error to only log errors or debug to log all messages to the app.log file
$conf['settings']['logging']['sql'] = 'false'; //Set to true no enable the creation of and sql.log file


/**
* API Granularity Settings
*/
$conf['settings']['api']['Authentication.group'] = ''; // If a group is specified then a user must be in the group in order to sucessfully authenticate. Unless the user is an Admin.
/**
* API access restrictions. These only provide additional restrictions. They do
* not provide additional permissions.
*
* If desired can specify a single group to limit access to an API category.
* Access per category can be limited to RO (Read-Only) and/or RW (Read-Write)
* access.
* RO access means they can only do GET actions.
* RW access means they can do GET/POST/PUT/DELETE actions.
* If a group is specified and the user is not in the group then they will be
* denied access, unless the user is an Admin.
* If a group is NOT specified then normal access permissions will apply.
*/

$conf['settings']['api']['Accessories.ro.group'] = '';
// NOTE: There are no "write" APIs for `Accessories`

$conf['settings']['api']['Accounts.ro.group'] = '';
$conf['settings']['api']['Accounts.rw.group'] = '';

$conf['settings']['api']['Attributes.ro.group'] = '';
// NOTE: Only application administrators can "write" to `Attributes`

$conf['settings']['api']['Groups.ro.group'] = '';
// NOTE: Only application administrators can "write" to `Groups`

$conf['settings']['api']['Reservations.ro.group'] = '';
$conf['settings']['api']['Reservations.rw.group'] = '';

$conf['settings']['api']['Resources.ro.group'] = '';
// NOTE: Only application administrators can "write" to `Resources`

$conf['settings']['api']['Schedules.ro.group'] = '';
// NOTE: There are no "write" APIs for `Schedules`

$conf['settings']['api']['Users.ro.group'] = '';
// NOTE: Only application administrators can "write" to `Users`

$conf['settings']['api']['Schedules.ro.group'] = '';
// NOTE: There are no "write" APIs for `Schedules`
46 changes: 46 additions & 0 deletions config/config.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,49 @@
$conf['settings']['delete.old.data']['delete.old.announcements'] = 'false'; //Choose if this feature deletes old announcements from database
$conf['settings']['delete.old.data']['delete.old.blackouts'] = 'false'; //Choose if this feature deletes old blackouts from database
$conf['settings']['delete.old.data']['delete.old.reservations'] = 'false'; //Choose if this feature deletes old reservations from database


/**
* API Granularity Settings
*/
$conf['settings']['api']['Authentication.group'] = ''; // If a group is specified then a user must be in the group in order to sucessfully authenticate. Unless the user is an Admin.
/**
* API access restrictions. These only provide additional restrictions. They do
* not provide additional permissions.
*
* If desired can specify a single group to limit access to an API category.
* Access per category can be limited to RO (Read-Only) and/or RW (Read-Write)
* access.
* RO access means they can only do GET actions.
* RW access means they can do GET/POST/PUT/DELETE actions.
* If a group is specified and the user is not in the group then they will be
* denied access, unless the user is an Admin.
* If a group is NOT specified then normal access permissions will apply.
*/

$conf['settings']['api']['Accessories.ro.group'] = '';
// NOTE: There are no "write" APIs for `Accessories`

$conf['settings']['api']['Accounts.ro.group'] = '';
$conf['settings']['api']['Accounts.rw.group'] = '';

$conf['settings']['api']['Attributes.ro.group'] = '';
// NOTE: Only application administrators can "write" to `Attributes`

$conf['settings']['api']['Groups.ro.group'] = '';
// NOTE: Only application administrators can "write" to `Groups`

$conf['settings']['api']['Reservations.ro.group'] = '';
$conf['settings']['api']['Reservations.rw.group'] = '';

$conf['settings']['api']['Resources.ro.group'] = '';
// NOTE: Only application administrators can "write" to `Resources`

$conf['settings']['api']['Schedules.ro.group'] = '';
// NOTE: There are no "write" APIs for `Schedules`

$conf['settings']['api']['Users.ro.group'] = '';
// NOTE: Only application administrators can "write" to `Users`

$conf['settings']['api']['Schedules.ro.group'] = '';
// NOTE: There are no "write" APIs for `Schedules`
1 change: 1 addition & 0 deletions lib/WebService/RestResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class RestResponse
public const CREATED_CODE = 201;
public const BAD_REQUEST_CODE = 400;
public const UNAUTHORIZED_CODE = 401;
public const FORBIDDEN = 403;
public const NOT_FOUND_CODE = 404;
public const SERVER_ERROR = 500;

Expand Down
Loading