Added permissions schema per entity fields

now api has ability to set per user role ( group )
which fields is allowed to edit per role and per entity

Change-Id: I824ede72dfe9e68b55531a8d2685aa911cb3270e
This commit is contained in:
Sebastian Marcet 2018-07-23 16:53:37 -03:00
parent 6abf5a30ac
commit 4f6aac0d4c
15 changed files with 273 additions and 21 deletions

View File

@ -23,6 +23,7 @@ use Illuminate\Support\Facades\Validator;
use libs\utils\HTMLCleaner;
use models\exceptions\EntityNotFoundException;
use models\exceptions\ValidationException;
use models\main\IMemberRepository;
use models\oauth2\IResourceServerContext;
use models\summit\IEventFeedbackRepository;
use models\summit\ISpeakerRepository;
@ -62,6 +63,11 @@ final class OAuth2SummitEventsApiController extends OAuth2ProtectedController
*/
private $event_feedback_repository;
/**
* @var IMemberRepository
*/
private $member_repository;
public function __construct
(
@ -69,6 +75,7 @@ final class OAuth2SummitEventsApiController extends OAuth2ProtectedController
ISummitEventRepository $event_repository,
ISpeakerRepository $speaker_repository,
IEventFeedbackRepository $event_feedback_repository,
IMemberRepository $member_repository,
ISummitService $service,
IResourceServerContext $resource_server_context
) {
@ -77,6 +84,7 @@ final class OAuth2SummitEventsApiController extends OAuth2ProtectedController
$this->speaker_repository = $speaker_repository;
$this->event_repository = $event_repository;
$this->event_feedback_repository = $event_feedback_repository;
$this->member_repository = $member_repository;
$this->service = $service;
}
@ -408,6 +416,12 @@ final class OAuth2SummitEventsApiController extends OAuth2ProtectedController
if(!Request::isJson()) return $this->error400();
$data = Input::json();
$current_member = null;
$member_id = $this->resource_server_context->getCurrentUserExternalId();
if (!is_null($member_id)){
$current_member = $this->member_repository->getById($member_id);
}
$rules = [
// summit event rules
'title' => 'sometimes|string|max:100',
@ -427,13 +441,14 @@ final class OAuth2SummitEventsApiController extends OAuth2ProtectedController
'tags' => 'sometimes|string_array',
'sponsors' => 'sometimes|int_array',
// presentation rules
'attendees_expected_learnt' => 'sometimes|string|max:1000',
'attending_media' => 'sometimes|boolean',
'to_record' => 'sometimes|boolean',
'speakers' => 'sometimes|int_array',
'moderator_speaker_id' => 'sometimes|integer',
'attendees_expected_learnt' => 'sometimes|string|max:1000',
'attending_media' => 'sometimes|boolean',
'to_record' => 'sometimes|boolean',
'speakers' => 'sometimes|int_array',
'moderator_speaker_id' => 'sometimes|integer',
// group event
'groups' => 'sometimes|int_array',
'groups' => 'sometimes|int_array',
'occupancy' => 'sometimes|in:EMPTY,25%,50%,75%,FULL'
];
// Creates a Validator instance and validates the data.
@ -454,7 +469,7 @@ final class OAuth2SummitEventsApiController extends OAuth2ProtectedController
'social_summary',
];
$event = $this->service->updateEvent($summit, $event_id, HTMLCleaner::cleanData($data->all(), $fields));
$event = $this->service->updateEvent($summit, $event_id, HTMLCleaner::cleanData($data->all(), $fields), $current_member);
return $this->ok(SerializerRegistry::getInstance()->getSerializer($event)->serialize());

View File

@ -258,7 +258,7 @@ Route::group([
Route::get('', 'OAuth2SummitEventsApiController@getEvent');
Route::get('/published', [ 'middleware' => 'cache:'.Config::get('cache_api_response.get_published_event_response_lifetime', 300), 'uses' => 'OAuth2SummitEventsApiController@getScheduledEvent']);
Route::put('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitEventsApiController@updateEvent' ]);
Route::put('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators|summit-room-administrators', 'uses' => 'OAuth2SummitEventsApiController@updateEvent' ]);
Route::delete('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitEventsApiController@deleteEvent' ]);
Route::put('/publish', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitEventsApiController@publishEvent']);
Route::delete('/publish', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitEventsApiController@unPublishEvent']);

View File

@ -11,6 +11,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use App\Models\Foundation\Main\IGroup;
use models\main\IMemberRepository;
use models\oauth2\IResourceServerContext;
use models\main\Group;
@ -54,7 +55,7 @@ final class BaseSerializerTypeSelector implements ISerializerTypeSelector
$serializer_type = SerializerRegistry::SerializerType_Public;
$current_member_id = $this->resource_server_context->getCurrentUserExternalId();
if(!is_null($current_member_id) && $member = $this->member_repository->getById($current_member_id)){
if($member->isOnGroup(Group::SummitAdministrators)){
if($member->isOnGroup(IGroup::SummitAdministrators)){
$serializer_type = SerializerRegistry::SerializerType_Private;
}
}

View File

@ -23,10 +23,6 @@ use models\utils\SilverstripeBaseModel;
*/
class Group extends SilverstripeBaseModel
{
const AdminGroupCode = 'administrators';
const CommunityMembersCode = 'community-members';
const FoundationMembersCode = 'foundation-members';
const SummitAdministrators = 'summit-front-end-administrators';
public function __construct(){
parent::__construct();

View File

@ -0,0 +1,28 @@
<?php namespace App\Models\Foundation\Main;
/**
* Copyright 2018 OpenStack Foundation
* 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.
**/
/**
* Interface IGroup
* @package App\Models\Foundation\Main
*/
interface IGroup
{
const Administrators = 'administrators';
const CommunityMembers = 'community-members';
const FoundationMembers = 'foundation-members';
const SummitAdministrators = 'summit-front-end-administrators';
const SummitRoomAdministrators = 'summit-room-administrators';
}

View File

@ -11,6 +11,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use App\Models\Foundation\Main\IGroup;
use Models\Foundation\Main\CCLA\Team;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
@ -580,7 +581,7 @@ class Member extends SilverstripeBaseModel
*/
public function isAdmin()
{
$admin_group = $this->getGroupByCode(Group::AdminGroupCode);
$admin_group = $this->getGroupByCode(IGroup::Administrators);
return $admin_group != false && !is_null($admin_group);
}

View File

@ -58,6 +58,12 @@ class SummitEvent extends SilverstripeBaseModel
*/
protected $social_summary;
/**
* @ORM\Column(name="Occupancy", type="string")
* @var string
*/
protected $occupancy;
/**
* @ORM\Column(name="StartDate", type="datetime")
* @var \DateTime
@ -907,4 +913,21 @@ class SummitEvent extends SilverstripeBaseModel
$this->rsvp_max_user_wait_list_number = $rsvp_max_user_wait_list_number;
}
/**
* @return string
*/
public function getOccupancy()
{
return $this->occupancy;
}
/**
* @param string $occupancy
*/
public function setOccupancy($occupancy)
{
$this->occupancy = $occupancy;
}
}

View File

@ -0,0 +1,28 @@
<?php namespace App\Permissions;
/**
* Copyright 2018 OpenStack Foundation
* 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.
**/
use models\main\Member;
/**
* Interface IPermissionsManager
* @package App\Permissions
*/
interface IPermissionsManager
{
/**
* @param Member $current_user
* @param string $entity_name
* @param array $data
* @return bool
*/
public function canEditFields(Member $current_user, $entity_name, array $data);
}

View File

@ -0,0 +1,82 @@
<?php namespace App\Permissions;
/**
* Copyright 2018 OpenStack Foundation
* 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.
**/
use models\main\Member;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;
/**
* Class PermissionsManager
* @package App\Permissions
*/
final class PermissionsManager implements IPermissionsManager
{
/**
* @var array
*/
private $config = [];
const AllFieldsWillCard = "__all__";
private function loadConfiguration(){
if(count($this->config) > 0 ) return;
$path = sprintf("%s/permissions.yml", dirname(__FILE__));
$yaml = Yaml::parse(file_get_contents($path));
if(!is_null($yaml) && count($yaml))
{
foreach($yaml as $entities => $entity){
foreach ($entity as $permissions_per_role){
$entity_name = array_keys($entity)[0];
$entity_config = [];
foreach ($permissions_per_role as $permission_per_role){
$role = array_keys($permission_per_role)[0];
$entity_config[$role] = [];
foreach($permission_per_role as $permissions){
foreach($permissions as $permission)
$entity_config[$role][] = $permission;
}
}
$this->config[$entity_name] = $entity_config;
}
}}
}
/**
* @param Member $current_user
* @param string $entity_name
* @param array $data
* @return bool
*/
public function canEditFields(Member $current_user, $entity_name, array $data){
$this->loadConfiguration();
$groups = $current_user->getGroupsCodes();
if(!isset($this->config[$entity_name]))
throw new \InvalidArgumentException(sprintf("entity %s does not has a configuration set", $entity_name));
$entity_config = $this->config[$entity_name];
foreach($groups as $group_code) {
if(!isset($entity_config[$group_code])) continue;
$allowed_fields = $entity_config[$group_code];
if(count($allowed_fields) == 0) return false;
if(in_array(self::AllFieldsWillCard, $allowed_fields)) return true;
$fields = array_keys($data);
$diff = array_diff($fields, $allowed_fields);
if(count($diff) > 0)
return false;
return true;
}
return false;
}
}

View File

@ -0,0 +1,13 @@
# here you define per Entity ClassName allowed fields to edit per user role
- SummitEvent:
- administrators:
- __all__
- summit-front-end-administrators:
- __all__
- summit-room-administrators:
- occupancy
- Presentation:
- administrators:
- __all__
- summit-front-end-administrators:
- __all__

View File

@ -11,6 +11,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use App\Models\Foundation\Main\IGroup;
use Doctrine\ORM\Tools\Pagination\Paginator;
use models\main\Group;
use models\summit\ISummitEventRepository;
@ -214,7 +215,7 @@ final class DoctrineSummitEventRepository
$query = $query->leftJoin('sp.registration_request', "sprr", Join::LEFT_JOIN);
}
$can_view_private_events = self::isCurrentMemberOnGroup(Group::SummitAdministrators);
$can_view_private_events = self::isCurrentMemberOnGroup(IGroup::SummitAdministrators);
if(!$can_view_private_events){
$query = $query
@ -305,7 +306,7 @@ final class DoctrineSummitEventRepository
}
$can_view_private_events = self::isCurrentMemberOnGroup(Group::SummitAdministrators);
$can_view_private_events = self::isCurrentMemberOnGroup(IGroup::SummitAdministrators);
if(!$can_view_private_events){
$query = $query

View File

@ -40,9 +40,10 @@ interface ISummitService
* @param Summit $summit
* @param int $event_id
* @param array $data
* @param null|Member $current_member
* @return SummitEvent
*/
public function updateEvent(Summit $summit, $event_id, array $data);
public function updateEvent(Summit $summit, $event_id, array $data, Member $current_member = null);
/**
* @param Summit $summit

View File

@ -21,6 +21,7 @@ use App\Http\Utils\FileUploader;
use App\Models\Foundation\Summit\Factories\SummitFactory;
use App\Models\Foundation\Summit\Repositories\IDefaultSummitEventTypeRepository;
use App\Models\Utils\IntervalParser;
use App\Permissions\IPermissionsManager;
use App\Services\Model\AbstractService;
use App\Services\Model\IFolderService;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
@ -161,6 +162,11 @@ final class SummitService extends AbstractService implements ISummitService
*/
private $default_event_types_repository;
/**
* @var IPermissionsManager
*/
private $permissions_manager;
/**
* SummitService constructor.
* @param ISummitRepository $summit_repository
@ -178,6 +184,7 @@ final class SummitService extends AbstractService implements ISummitService
* @param ICompanyRepository $company_repository
* @param IGroupRepository $group_repository,
* @param IDefaultSummitEventTypeRepository $default_event_types_repository
* @param IPermissionsManager $permissions_manager
* @param ITransactionService $tx_service
*/
public function __construct
@ -197,6 +204,7 @@ final class SummitService extends AbstractService implements ISummitService
ICompanyRepository $company_repository,
IGroupRepository $group_repository,
IDefaultSummitEventTypeRepository $default_event_types_repository,
IPermissionsManager $permissions_manager,
ITransactionService $tx_service
)
{
@ -216,6 +224,7 @@ final class SummitService extends AbstractService implements ISummitService
$this->company_repository = $company_repository;
$this->group_repository = $group_repository;
$this->default_event_types_repository = $default_event_types_repository;
$this->permissions_manager = $permissions_manager;
}
/**
@ -529,11 +538,12 @@ final class SummitService extends AbstractService implements ISummitService
* @param Summit $summit
* @param int $event_id
* @param array $data
* @param null|Member $current_member
* @return SummitEvent
*/
public function updateEvent(Summit $summit, $event_id, array $data)
public function updateEvent(Summit $summit, $event_id, array $data, Member $current_member = null)
{
return $this->saveOrUpdateEvent($summit, $data, $event_id);
return $this->saveOrUpdateEvent($summit, $data, $event_id, $current_member);
}
/**
@ -616,16 +626,25 @@ final class SummitService extends AbstractService implements ISummitService
return true;
}
/**
* @param Summit $summit
* @param array $data
* @param null|int $event_id
* @param Member|null $current_member
* @return SummitEvent
* @throws EntityNotFoundException
* @throws ValidationException
* @throws Exception
*/
private function saveOrUpdateEvent(Summit $summit, array $data, $event_id = null)
private function saveOrUpdateEvent(Summit $summit, array $data, $event_id = null, Member $current_member = null)
{
return $this->tx_service->transaction(function () use ($summit, $data, $event_id) {
return $this->tx_service->transaction(function () use ($summit, $data, $event_id, $current_member) {
if(!is_null($current_member) && !$this->permissions_manager->canEditFields($current_member, 'SummitEvent', $data)){
throw new ValidationException(sprintf("user %s cant set requested summit event fields", $current_member->getId()));
}
$event_type = null;
@ -721,6 +740,9 @@ final class SummitService extends AbstractService implements ISummitService
if (isset($data['social_description']))
$event->setSocialSummary(strip_tags(trim($data['social_description'])));
if (isset($data['occupancy']))
$event->setOccupancy($data['occupancy']);
$event->setAllowFeedBack(isset($data['allow_feedback'])?
filter_var($data['allow_feedback'], FILTER_VALIDATE_BOOLEAN) :
false);

View File

@ -11,6 +11,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use App\Permissions\IPermissionsManager;
use App\Permissions\PermissionsManager;
use App\Services\Apis\CalendarSync\ICalendarSyncRemoteFacadeFactory;
use App\Services\Apis\GoogleGeoCodingAPI;
use App\Services\Apis\IGeoCodingAPI;
@ -72,6 +74,8 @@ final class ServicesProvider extends ServiceProvider
{
App::singleton(ICacheService::class, RedisCacheService::class);
App::singleton(IPermissionsManager::class, PermissionsManager::class);
App::singleton(\libs\utils\ITransactionService::class, function(){
return new \services\utils\DoctrineTransactionService('ss');
});

View File

@ -335,6 +335,43 @@ final class OAuth2SummitEventsApiTest extends ProtectedApiTest
return $event;
}
public function testUpdateEventOccupancy(){
$params = array
(
'id' => 23,
'event_id' => 20345,
);
$data = [
'occupancy' => '25%'
];
$headers = array
(
"HTTP_Authorization" => " Bearer " . $this->access_token,
"CONTENT_TYPE" => "application/json"
);
$response = $this->action
(
"PUT",
"OAuth2SummitEventsApiController@updateEvent",
$params,
array(),
array(),
array(),
$headers,
json_encode($data)
);
$content = $response->getContent();
$this->assertResponseStatus(200);
$event = json_decode($content);
$this->assertTrue($event->id > 0);
return $event;
}
public function testUnPublishEvent()
{
$event = $this->testPublishEvent(1461529800, 1461533400);