Jump to content


  • Posts

  • Joined

  • Last visited

  • Days Won


Everything posted by NotionCommotion

  1. The following four methods were changed. The first three seem to be correct but not sure about the last one (hasPhysicalMedia). Agree? Best way to fix is just type casting the returned value? Thanks public function getPhysicalMedia(): ?PhysicalMedia { - return $this->activeMedia?$this->activeMedia->getPhysicalMedia():null; + return $this->activeMedia?->getPhysicalMedia(); } public function getMediaType(): ?MediaType { - return $this->activeMedia?$this->activeMedia->getMediaType():null; + return $this->activeMedia?->getMediaType(); } public function getFilename(): ?string { - return $this->activeMedia?$this->activeMedia->getFilename():null; + return $this->activeMedia?->getFilename(); } public function hasPhysicalMedia(): bool { - return $this->activeMedia && $this->activeMedia->hasPhysicalMedia(); + return $this->activeMedia?->hasPhysicalMedia(); } class ActiveMedia { public function getPhysicalMedia(): PhysicalMedia { return $this->physicalMedia; } public function getMediaType(): ?MediaType { return $this->mediaType; } public function getFilename(): ?string { return $this->filename; } public function hasPhysicalMedia(): bool { return (bool) $this->physicalMedia; } }
  2. Thanks requinix, Seem like if a user doesn't have a home directory, php assumes it is at the typical /home/abtfile. Ended up going with "abtfile:x:1001:1001::/var/www/abtfile:/usr/sbin/nologin".
  3. Finally left Centos and gave Ubuntu a try. Also, changed from apache to nginix. All went much easier than Centos but have one issue. I wish to have a non-human user dedicated to each website which PHP will run under and postgresql will use. I created my user but didn't provide a home directory (useradd -M abtfile). My configuration is shown below and phpinfo shows abtfile as the user but /home/abtfile as the home. I am now thinking I should have created a home for the user should keys or similar be needed for it, and think my options are: Home directory: /home/abtfile Host site: /var/www/abtfile/public. Doesn't seem right. Home directory: /home/abtfile Host site: /home/abtfile/public. Better but not sure. Home directory: /var/www/abtfile Host site: /var/www/abtfile/public. Likely but not sure. Questions. Should abtfile user have a home directory? Which of my three options or some other approach should be used? Do I define the home directory location the same way as for any linux user or must it also be defined under some php or nginix config file? Thanks! PS. Not having issues (yet), however, if you see any issues under my below configuration files, please let me know. /etc/php/8.1/fpm/pool.d/abtfile.conf [abtfile] user = abtfile group = abtfile ; Call whatever I want. Use ls -l /run/php/ to see existing sockets. listen = /var/run/php8.1-fpm-abtfile.sock ; Must match to the user and group on which NGINX is running listen.owner = www-data listen.group = www-data ; Consider changing below valves. ; mandatory pm = dynamic pm.max_children = 5 pm.min_spare_servers = 1 pm.max_spare_servers = 3 ; Use default values. ; pm.start_servers = 2 ; pm.max_spawn_rate = 32 ; pm.process_idle_timeout = 10s ; Not sure if necessary or correct ; Allows to set custom php configuration values. ; php_admin_value[disable_functions] = exec,passthru,shell_exec,system ; Allows to set PHP boolean flags ; php_admin_flag[allow_url_fopen] = off ; Add environmental data if desired. ; env[HOSTNAME] = $HOSTNAME ; env[TMP] = /tmp /etc/nginx/sites-available/abtfile server { server_name abtfile.testing.com; listen 80; listen [::]:80; root /var/www/abtfile/public; index index.php index.html index.htm; access_log /var/log/nginx/abtfile-access.log; error_log /var/log/nginx/abtfile-error.log; location / { try_files $uri $uri/ =404; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/var/run/php8.1-fpm-abtfile.sock; fastcgi_split_path_info ^(.+\.php)(/.+)$; # What does this do? #fastcgi_index index.php; # Causes error. Maybe remove from above? } } /etc/nginx/php_fastcgi.conf try_files $fastcgi_script_name =404; include fastcgi_params; fastcgi_pass unix:/run/php/php-fpm.sock; fastcgi_index index.php; fastcgi_buffers 8 16k; fastcgi_buffer_size 32k; fastcgi_hide_header X-Powered-By; fastcgi_hide_header X-CF-Powered-By;
  4. Ended up forking the git repo, adding it as a repository in composer.json, and manually adding 'hautelook/alice-bundle": "2.10" to require.dev.
  5. Trying to install a package but received a version error: composer require --dev alice Using version ^2.10 for hautelook/alice-bundle ./composer.json has been updated Running composer update hautelook/alice-bundle Loading composer repositories with package information Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - Root composer.json requires hautelook/alice-bundle ^2.10 -> satisfiable by hautelook/alice-bundle[2.10.0]. - hautelook/alice-bundle 2.10.0 requires doctrine/persistence ^2.2 -> found doctrine/persistence[2.2.0, ..., 2.5.3] but the package is fixed to 3.0.2 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command. Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions. You can also try re-running composer require with an explicit version constraint, e.g. "composer require hautelook/alice-bundle:*" to figure out if any version is installable, or "composer require hautelook/alice-bundle:^2.1" if you know which you need. Installation failed, reverting ./composer.json and ./composer.lock to their original content. An issue has been created and a pull request fixing the issue has been made. The fix just changed composer.json "doctrine/persistence": "^2.2", to "doctrine/persistence": "^2.2 || ^3.0" What are my options to use this package before the github and composer package is updated? Ideally, I would like to instruct composer to allow hautelook/alice-bundle to use 3.0 either by adding some argument when I call composer on the command line or by manually editing composer.json as applicable. Is this possible? If this is not possible, I suppose my next best choice is to downgrade doctrine/persistence from 3.02 to 2.2, however, doctrine/persistence is not in my composer.json file and only in my composer.lock file which I suspect I shouldn't be manually editing it. Guess my third choice would be to clone it from github, make the changes, and then modify my composer file to use a local copy. Maybe there are other approaches? Thanks
  6. class IteratorIterator implements OuterIterator and this class permits access to methods of the inner iterator via the __call magic method. class RecursiveIteratorIterator implements OuterIterator interface OuterIterator extends Iterator Should I conclude that any class that implements OuterIterator permits access to methods of the inner iterator via the __call magic method, and not just IteratorIterator? Thanks PS. Totally off topic, but is there a way to embed a link when one quotes something (i.e. your quote would include https://www.php.net/manual/en/class.iteratoriterator.php)? Array ( [0] => RecursiveIteratorIterator [1] => IteratorIterator [2] => FilterIterator [3] => RecursiveFilterIterator [4] => CallbackFilterIterator [5] => RecursiveCallbackFilterIterator [6] => ParentIterator [7] => LimitIterator [8] => CachingIterator [9] => RecursiveCachingIterator [10] => NoRewindIterator [11] => AppendIterator [12] => InfiniteIterator [13] => RegexIterator [14] => RecursiveRegexIterator [15] => RecursiveTreeIterator )
  7. Thanks requinix, I suspected something like that but could find no documentation. Also suspect it is documented somewhere but I just looked at the wrong place. Must say, though, that it is well hidden documentation.
  8. Start commenting out code until you don't get the error. Or better yet, comment out all first and try just echo('hello');. If still an error, try just an html page without php. Check your php error logs and http logs. If php runs but not all your script, check what versions of php are running on both local and kkgramping.
  9. What's not working? Do you have erroring enabled? If not, enable it. Also, do sanity checks such as echo('SITE_ROOT: '.SITE_ROOT); to make sure they are what you think they should be.
  10. I'm confused. Why do I not get an error with $it->getSubPathName() and $it->getSubPath()? $it = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir)); $m = array_flip(get_class_methods($it)); printf('class: %20s getSubPathName: %s getSubPath: %s key: %s'.PHP_EOL, get_class($it), isset($m['getSubPathName'])?'y':'n', isset($m['getSubPath'])?'y':'n', isset($m['key'])?'y':'n'); print_r(get_class_methods($it)); echo 'SubPathName: ' . $it->getSubPathName() . PHP_EOL; echo 'SubPath: ' . $it->getSubPath() . PHP_EOL; echo 'Key: ' . $it->key() . PHP_EOL.PHP_EOL; output: class: RecursiveIteratorIterator getSubPathName: n getSubPath: n key: y Array ( [0] => __construct [1] => rewind [2] => valid [3] => key [4] => current [5] => next [6] => getDepth [7] => getSubIterator [8] => getInnerIterator [9] => beginIteration [10] => endIteration [11] => callHasChildren [12] => callGetChildren [13] => beginChildren [14] => endChildren [15] => nextElement [16] => setMaxDepth [17] => getMaxDepth ) SubPathName: . SubPath: Key: src/.
  11. Thanks for the Party model link. I will definitely spend some time going over it as well as its predecessor Bill of Materials. AFAIK roles dictate the user's job title (ROLE_ADMIN , ROLE_HR_MANAGER) and can also be used slightly differently by dictating some capability of the job title (ROLE_CAN_FIRE_PEOPLE) and grant permission by resource type but not per resource. For my requirements, I would need to create individual roles for every project (ROLE_CAN_VIEW_PROJECT_123, etc) which doesn't sound right. I agree that voters give me an easy and consistent way to hook and handling things, but don't dictate how they are handled and leave that to me. They will give me some attribute string (could be an action such as view/edit or whatever I want), the resource, and user (actually token) which is great, but I will still need other information like whether a given user can view content in a given project, and therefor would need "something" similar to the original schema I showed. No? So, in order to grant permission by individual resources, I need an identifier. Just realized I had project.access_control_id referencing project_access_control.id and project_access_control.resource_id referencing project.id which would identify, but don't think it is the right way. I also had project_access_control.resource_id reference project.id which will also identify, however, don't know whether I like my use of subtyping and extending abstract_access_control by project_access_control (and asset_access_control and vendor_access_control). So, maybe a third way to identify the thing? The following represents how the Symfony ACL bundle does it. Not sure whether their exact schema will work for me but sure they are doing things that I haven't thought of. I like how it is less coupled to my domain, but at the same time don't like how it is less coupled to my domain (no FKs acl_object_identifier to naics_code.id, etc, acl_sercurity_identifiers to user.id). Not sure why they don't have a FK between acl_classes.id and acl_object_identifier.class_id. My confusion is how I would expose security settings to an admin user. The admin user will have some role ROLE_MANAGE_ACL so no problem there but will need to perform some queries to show all resources that user 123 has been given permission and all users that have permission to act on resource 321. Normally this is done with FK's and joins but I don't have them and instead have tables which include the names of the resources and users. Maybe not possible?
  12. Thanks again gizmola, What I heard is when I have OOP code that clearly benefits from inheritance and am persisting in a relational database, I should not be ashamed of using the supertype/subtype model. Guess it paid off asking the same question over and over again until I got the answer I wanted! By the way, can't claim credit myself and it was Rudy years ago who set me in this direction. I also heard the party model mentioned. benanamen recommended this approach a few months back. Didn't make the connection until now, but this appears to be a model which has super/sub types at its core.
  13. I completely agree! Most of the time, I am controlling "what content should I see based on my place within an organization or department". For a couple edge cases, however, I need to control whether "I have access to this feature or function". I don't really understand attribute based access control, however, based on my recent research, looks like the first need is best handled by using RBAC (role based) and the second is best handled by using ACL. Agree? If so, then I will need to use two models. Also, I am using Symfony which really focuses on RBAC, and while they have an ACL bundle, they recommend not using it (and it also doesn't appear to support changing the required resource permission). There is also Laminas's ACL component which you previously brought to my attention and I have since spent some time investigating. I was very interesting, had some great features, and was highly decoupled, however, I kind of concluded that it was overkill. Maybe not? High level requirements and proposed solutions are: Normal role access control. Use Symfony's normal approach (which happens to be voters) Ability to give regular users additional permissions on a per resource basis. While not a must have feature, it is pretty simple since the required permission of the resource doesn't change. If this was the only one-off, I would likely just use a resource/user junction table to store extra roles and call it good, but have the next one... Ability to define both the required permission to perform some action on a given resource as well as the authority of each user for that specific resource. Unfortunately, this is an important requirement. Are you thinking that Laminas's ACL component would be well suited to meet this need? If so, would like to discuss more. I haven't yet gotten through the details, but also felt that groups were the key. Sometimes called them groups and other times called them memberships. For my use case, I don't think I need multiple groups but just one group per project. If more requirements are later added, maybe then more groups, but oh well. If project contains content, all the content in some projects needs to be available to all and for other projects only is available to some, how do you think your groups approach would work? PS. Would you agree that Unix file permissions and ACL are both examples of Discretionary Access Control? https://ebrary.net/26657/computer_science/access_control_strategies says they are and just want to make sure I should believe them. The remained of my reply is really to respond to your other comments. I will completely take and appreciate any critique you offer, however, I am not necessarily asking for it. public_id is an identifier ID which is unique per entity type for a given tenant, and is just used so I don't have to expose the DB PK or use a UUID or anything. I actually think my homegrown solution is pretty clean, however, I am always concerned when I need to use a homegrown solution for what seems to be a common need. tenant_user belongs to tenant and is joined by tenant_id. vendor_user belongs to vendor, is joined by vendor_id, and vendor belongs to tenant and is joined by tenant_id. As such, I really don't need tenant_id in vendur_user, but I wish to keep content completely isolated between tenants, and ended up adding tenant_id to every entity except (for a few common entities which are not owned by a tenant). tenant/vendor_default_permission is like unix permission 0755 and represents my Permission class. I didn't show tenant but if I did, you would see similar, and they act as prototypes until they get to their final home. I copy them from tenant to project_access_control so that the project_access_control version could be changed without changing the tenant version, and then again copy it to project_member for the same reason. tenant/vendor_read_permission represents my PermissionEnum class, and several bits in tenant/vendor_default_permission hold the same content which I know is a big SQL no-no. It is just used so I can do queries without indexing concern when using bitwise operators in the where clause, and is on my list of things to maybe change. class Permission { private const READ = 0b00000011, CREATE = 0b00001100, MODIFY = 0b00110000, COWORKER= 0b01000000, OWNER = 0b10000000 ; public function __construct( private PermissionEnum $read, private PermissionEnum $create, private PermissionEnum $modify, private bool $restrictToOwner, private bool $coworkers, ) { } public static function create(int $value): self { return new self( PermissionEnum::from($value&self::READ), PermissionEnum::from($value&self::CREATE >> 2), PermissionEnum::from($value&self::MODIFY >> 4), boolval($value&self::COWORKER), boolval($value&self::OWNER), ); } // other methods... public function getValue(): int { $rs = $this->read->value | $this->create->value << 2 | $this->modify->value << 4; $rs = $this->coworkers?$rs|self::COWORKER:$rs & (~ self::COWORKER); $rs = $this->restrictToOwner?$rs|self::OWNER:$rs & (~ self::OWNER); return $rs; } } enum PermissionEnum: int { private const MAP = [ 'public' => 0b00, 'owner' => 0b01, 'restrict' => 0b10, 'override' => 0b11 //Not sure what this should be used for. ]; case public = 0b00; case owner = 0b01; case restrict = 0b10; case override = 0b11; //Not sure what this should be used for. }
  14. Thank you gizmola, While the subtyping is somewhat of a different topic, would like your thoughts and will reply with two separate responses. I use this approach all the time and it seems like others rarely if at all do. Either I know more than everyone else in the world or there is something I don't know which makes this model less necessary. Humm, which one is it... Let's use your example from learndatamodeling. Why would I use this model? It looks pretty with all the common data in the employee table. I don't think there is any normalization rule to go this way. Agree? If so, not a good reason. I have one or more other tables that need to reference employee. Yes, very often. Sure, I could have two joins to both full time and hourly employee, but that is more complicated from both a SQL perspective (can't use non-null, require triggers to check) and PHP perspective (no AbstractEmployee to type declare), and is also less performant (will require unions instead of joins) and less flexible. I keep on hearing favor composition over inheritance, and while I finally understand and agree that it should be used in many cases, don't see the value for modelling entities such as this one. Is there a better way to handle requirements such as this? Or maybe it is a common design pattern and I just think I am the only one using it? Some of my attempts searching for this holy grail.
  15. Was thinking "class table inheritance" which is my understanding just using super/subtype SQL to help enforce inheritance on the DB level. PS. Sorry, after posting this question, started thinking it wasn't appropriate. Thing is programming it totally not my day job but only my hope to quit my day job and do something else, and the only people I know that know programming are here.
  16. I think I am close on how I should implement access control, however, considering that I am making up much of the approach myself, I expect that there are some flaws in my approach and would appreciate a reality check. Use Case and User Requirements The application is a content management system which allows capital project users to request content from vendors for a specific project, vendor users provide the content, and facility operation users to utilize the content upon project completion. Note that multiple content records will always remain associated with one project. Capital project users and facility operation users both belong to the tenant (owner) organization. Vendor users belong to a vendor organization which has a many-to-one relationship to tenant owner. As such, I have a AbstractUser which is extended using CTI by TenantUser and VendorUser. Allowed permissions per user type are: Vendor users can: Read the organization they belong to (the vendor company) and read/update their own profile. View projects and perform CRUD operations on project content based on the project, user, specification, and whether they own/uploaded the original content. Tenant normal users can: Read the organization they belong to (the tenant/owner company) and read/update their own profile. Manage a given resource type (i.e. all vendors) when allowed by admin. Manage a specific resource (i.e. Acme Pipe and Fittings Company) when allowed by admin. Read everything except for projects and content contained within projects unless the projects have been made available to them. View projects and perform CRUD operations on project content based on the project, user, specification, and whether they own/uploaded the original content. Tenant admin users can: Do everything a normal user can plus manage resources. Assign permission to a normal tenant user to manage all resources of a given type (i.e. allow them to manage all vendors). Assign permission to a normal tenant user to manage a specific resources (i.e. allow them to manage Acme Pipe and Fittings Company). The utilized access control strategy should also promote: a simple and intuitive ux which reflects the way normal people think without requiring large amounts of script to maintain. consistency among projects yet allow flexibility by having new projects be prepopulated with a clone of the tenant wide default permissions prototype. Proposed Implementation It seems like role based access control provides the most intuitive for most needs, however, as far as I could tell, will not allow all functionality and my intent is to use multiple strategies. To implement RBAC, I will: Create action roles such as ROLE_VIEW_VENDOR and ROLE_MANAGE_VENDOR, ROLE_VIEW_PROJECT and ROLE_MANAGE_PROJECT, ROLE_VIEW_ASSET and ROLE_MANAGE_ASSET, etc. Create user roles such as: ROLE_USER which is any authenticated user and can only view/edit their own profile. ROLE_VENDOR_USER which uses hierarchy to extend ROLE_USER. ROLE_TENANT_USER which uses hierarchy to extend ROLE_USER, ROLE_VIEW_VENDOR, ROLE_VIEW_ASSET, etc ROLE_ADMIN_USER which uses hierarchy to extend ROLE_TENANT_USER, ROLE_MANAGE_VENDOR, ROLE_MANAGE_PROJECT, ROLE_MANAGE_ASSET, ROLE_MANAGE_USER, etc. Then to allow a tenant user to manage a given resource type (i.e. all vendors) when allowed by, ROLE_MANAGE_VENDOR can be added to the user's role array. For the remaining functionality, I will need to join the user to the resource, and I refer to this intersecting entity as "ResourceMember". Then to allow a tenant user to manage a specific resource (i.e. Acme Pipe and Fittings Company) when allowed by admin, ROLE_MANAGE_VENDOR can be added to ResourceMember user roles property. Or maybe since ResourceMember joins a single resource thus we already know it is a vendor resource, could just add role ROLE_MANAGE. The CRUD operations on project content gets more complicated. My plan for this is a quasi Unix file permissions where each project is analogous to a directory and content added to the project is analogous to a file. The project holds the tenant and vendor user required permission settings needed to create, read, update, etc content, and each could be "other" (any authenticated user), "owner" (they uploaded it and admin hasn't reassigned), and "restrict". If "restrict" and they do not belong to the membership group (i.e. joined by resource_member), they are denied, but if they do, a similar check is performed using resource required permissions for that specific user stored in ResourceMember and its restrict will be final. Note that the ERD below shows these permissions as integers but they represent permissions such as 0755 (but no execution of course!). I don't plan on allowing the project (container) to have an owner so create/owner doesn't really make sense but the others seem to. When creating a new project, the project permissions and project-member permissions for both a tenant and vendor user will be cloned from the tenant's default prototypes and stored in the project, and when adding a member, the appropriate copy of the project-member permission stored in the project will be cloned and stored in the membership entity. One other detail. User roles and additional group member entity roles are stored in a JSON array, and I might have performance issues when performing a GET collection request. I thought that some DB's might be able to handle this, but am suspect, and instead will have some duplicating read flags persisted in the DB which will be used to dynamically create the appropriate SQL without needing to hydrate the entities. Up to now, I am just asking whether my approach is viable or not and if so whether you have any recommendations, advice, caution, etc, however, do have a specific question. Up above, I said "I will need to join the user to the resource". No issue with the user but not so with the resource side. My current approach is to join another table which has a one-to-one relationship to the resource (see below schema). One thing I liked about this approach is doesn't pollute my resources with permission prototypes, owner, redundant read flags, and other properties just used for access control. Don't like how it uses CTI which I think I overuse. Also, just noticed that the resource (i.e. Project) has the access control primary key and not the other way around and should fix this. Possible changes to my other table approach are: Have a separate junction table for Project, Vendor, Asset, etc. Doesn't seem as "SQL proper" and just have some gut feel I will later run into issues. Not use a FK constraint but use the resource class and its PK as the identifier. I've always shied away from this approach and have the same guy feel concern, but maybe applicable. If option 1 (separate junction tables) or option 2 (no FKs) are used, think that I should locate the access control properties in the resource (project) and get rid of the extra tables? Use super/sub SQL schema so that Project, Vendor, Asset, etc all share a single PK. Makes the join easy and no gut feel concern about issues, but definitely way more difficult to later add some resource, and will also complicate adding a resource which is already using CTI (like User). I am on uncharted territory and typically when so in the past, never ended well and I needed to later do differently.
  17. Look into both using relative time formats such as new DateTime('monday midnight') to get your boundaries and datetime::diff() to get your durations.
  18. Thanks requnix, Deep down, I knew what you are saying is true and that my desire to make it look pretty and compact should not be the goal. Still it is still tempting... Since it is complete and working, probably will give it a try for a bit and if issues, go back to a transitional approach. While not the purpose of my post, if you have a better word than "public", I would like to hear it. It does not mean the public in general but users who have logged on and authenticated using their username and password, but are not the owner of the resource and do not belong to the resource's group. While not exact, pretty close to Unix permissions and maybe other or world is more appropriate...
  19. Currently, I implemented adding additional columns which are redundant to the single integer stored in the DB but can be used only when filtering on the ability to read. This permission integer is follows and has contains some enums and binary flags: class Permission { public function __construct( private PermissionEnum $read, private PermissionEnum $create, private PermissionEnum $modify, private bool $restrictToOwner, private bool $coworkers, ) { } } enum PermissionEnum: int { case public = 0b00; case owner = 0b01; case restrict = 0b10; case override = 0b11; //currently not used. } Schema is as follows. As you can see, separate copies of the permission values are stored multiple places. tenant - id - tenant_default_permission_prototype (integer defining multiple boolean and enum properties) - vendor_default_permission_prototype (same as for tenant but will be used when vendor's log on) - tenant_member_permission_prototype (integer defining multiple boolean and enum properties for any members that are added) - vendor_member_permission_prototype (same as for tenant but will be used when vendor's log on) project - id project_access_control - id (one-to-one to project.id) - tenant_default_permission (integer defining multiple boolean and enum properties. Will be copied from tenant.tenant_default_permission_prototype) - vendor_default_permission (same as for tenant but will be used when vendor's log on) - tenant_member_permission_prototype (integer defining multiple boolean and enum properties for any members that are added) - vendor_member_permission_prototype (same as for tenant but will be used when vendor's log on. Will be copied from tenant.tenant_member_permission_prototype) - tenant_read_permission (options are public, owner, or restrict. duplicates information in tenant_default_permission.read) - vendor_read_permission (same as for tenant but will be used when vendor's log on) project_member - project_access_control_id (or maybe project_id?) - user_id - permission (integer defining multiple boolean and enum properties for any members that are added. Will be copied from the applicable project permission prototype) - read_permission (options are public, owner, or restrict. duplicates information in permission.read) user - id - descriminator tenant_user - id (one-to-one to user.id) vendor_user - id (one-to-one to user.id) A query when a tenant user logs on looks like the following when using my redundant read permissions, and will be almost the same except will use vendor_read_permission instead of tenant_read_permission. SELECT * FROM project p INNER JOIN project_access_control pac ON p.access_control_id = pac.id LEFT JOIN project_member pm ON pac.id = pm.access_control_id WHERE p.tenant_id = 123 AND ( pac.tenant_read_permission = 0 OR pm.read_permission = 0 OR (pac.tenant_read_permission = 2 AND pac.owned_by_id = 321) OR (pm.read_permission = 1 AND pac.owned_by_id = 321) ) A fictional query which I want to use (or maybe I just keep with the previous) looks like the following. Instead of filtering based on discrete columns, my integer permission columns would be used and the SQL would need to define which bit or bits to use. Not sure if it works this way but envision an index being a bunch of numbers from small to large, and since the read operations are the ones which would value from this index, thinking the read bits should be the bits on the left (or big values or whatever they are called). SELECT * FROM project p INNER JOIN project_access_control pac ON p.access_control_id = pac.id LEFT JOIN project_member pm ON pac.id = pm.access_control_id WHERE p.tenant_id = 123 AND ( areBitsSet(pac.tenant_default_permission, 0, 0) OR areBitsSet(pm.permission, 1, 0) OR (areBitsSet(pac.tenant_default_permission, 2, 2) AND pac.owned_by_id = 321) OR (areBitsSet(pm.permission, 3, 1) AND pac.owned_by_id = 321) )
  20. Instead of using Laminas or Symfony's ACL component, I ended up coming up with my own, and only providing the following to provide context: For each entity where access control is needed, permissions for read, edit, and create can be public, owner, or restrict. If restrict, then it will check if a user is a member of the specific entity in question, and if so, get the same read/entity/create permissions for the member. There are also two types of users: those that belong to the tenant and those who belong to vendors who in turn belong to a given tenant. In addition, I wish to allow each tenant to define their own sitewide permissions which are then cloned when adding a new entity which is under access control, and also something similar when adding members. Bottom line is I have a bunch of SQL tables which have a bunch of columns which are either boolean or some string with limited enum options. In addition, I am using doctrine's value object solution (embeddables) which givens the columns some long name (granted, sure I can fix this part). I expect I should let it go but it is just damn ugly, so I created a class which takes a small integer stored in the DB and creates my permission object, and all works as desired. When dealing with a single resource (read, delete, edit, etc), I have no concerns about performance as PHP only has to deal with a single record provided by the DB. But when requesting a collection of resources, I wish SQL to do the work, and it will need to filter based on this small integer described in the previous paragraph. Obviously, I will want to index it, but I don't know how indexing works when using a bitwise operator in the WHERE clause. How does indexing work when using bitwise operators in the WHERE clause? Is there anything that can be done to improve performance if an issue? For instance: Locating the read permission bits on the high side of the bit string so the index reflects them more than edit/delete/etc which won't need an index. Add additional columns which are redundant to the single integer stored in the DB but can be used only when filtering on the ability to read.
  21. But you are the owner and have control over the content that a given URI returns, right? If so, you could use JS to modify the behavior of these links. Regardless of what you do, give thought whether you want state to be held by the client or server.
  22. Yes, I have considered Symfony's bundle. Agree more integrated is a mixed blessing. It too needs unique IDs for ACLs and has some helper methods which are pretty close to your proposed way to do so. The symfony ACL bundle uses the following schema instead of coupling the permissions with the entity data as I originally planned on doing. I found documentation on this bundle is pretty sparse and maybe shouldn't be even using them per this warning.
  23. Thanks gizmola, Laminas use of resources does a good job implementing my concept of groups. I also like how it provides flexibility regarding role inherence and also allows one to accommodate entity ownership. While I am always nervous about needing to integrating yet to another 3rd party platform, I like how decoupled it is and that it doesn't even deal with persistence. I see it only has 25 stars on github, however, if it meets the task at hand, not an issue. Questions/uncertainties/concerns I have with it are: Must the ID returned by Resource::getResourceId() and Role::getRoleId() be unique only for the given entity type or across all entities? For instance, can Project, Document, and SomethingElse all provide an ID of #123? I expect not and will likely need to implement either some super/sub DB schema or store some identifier hash of each, but don't know for sure. Best approach to database persistence. Per the docs, one option is to serialize the ACL object before storage. Also, existing integration to 3rd party persistence systems exist. Guess I could figure this out after getting the above figured out other parts first. How can it be used to filter multiple records directly from a DB without first creating objects of each first? While I acknowledge that I might need to re-think my workflow if using Laminas, I would like to use my original example. I have Projects which have permissions assigned to it, and therefore will create a resource for each (i.e. resource_project_123). Then, I want to add users to the project to allow additional access, but can't add users but only roles, so I create a new "member_of_project_123" role and give that role permissions on resource_project_123 as applicable, and assign resource_project_123 to User #321. But this just doesn't seem right... Regardless, so, now User #321 logs on and requests the list of all documents, and I need to create a query which will only return records allowed by $acl. The documentation shows examples of whether the user can perform some operation on a given entity only.
  24. Thanks requinix, This access control thing gets complicated fast. Yeah, was using Unix permissions for inspiration (wish I could say I independently discovered it!). For some cases, I have containers (i.e. documents live in a project container) but don't have infinite hierarchy like Unix files have and other entities don't have this concept at all. But assigning a given entity type to one model today makes changing tomorrow difficult so "maybe" implement some generic approach today... I am sure that Linux doesn't use SQL but likely some c++ or even assembly language code to do similar as the following. Not sure where this rabbit hole is leading me... AbstractControlledEntity - id - owner (FK to User. not null?) - permission (initially an object but maybe future a byte for optimization) - discriminator SomeResource extends AbstractControlledEntity User - id - permission - other_properties Group - resource_id - user_id - permission
  25. I am working on niche documentation management application (PDF, Excel, etc), need to set up access permissions, and will be creating classes, persisting them in a DB, and transmitting them via a REST API using JSON. For my application, group is the strongest, then owner, and finally public. For example, maybe I want to allow everyone to read and create, owner (and group) to update, and only group to delete. One way to do it is like the following, and have first positive prevails (i.e. first check if public can delete, then check if owner can, and finally determine that only group can do so). With this approach, I would ignore that owner.read is false because public.read already allows. Also, it doesn't really make sense for owner to be able to create because there is no document in the first place. { "public": { "read": true, "create": true, "update": false, "delete": false }, "owner": { "read": false, "create": true, "update": true, "delete": false }, "group": { "read": true, "create": true, "update": true, "delete": true } } Alternatively, I could do the following (and while not really important, a personal added benefit is getting to try PHP8's enumerations for the first time). { "read": "public", "create": "public", "update": "owner", "delete": "group" } The anticipated workflow is for a user who belongs to a given tenant to first create a "project" and add access requirements to it and for all documents added to that project to be subject to those access requirements. To minimize user interaction, there will be a default tenant permission prototype which will be cloned and injected in the project and the user can then make changes to the project permission without changing the default prototype (the default tenant permission prototype can also be changed so that future projects will use the new settings, and permission will have a one-to-one to both tenant and project). Our access control logic has been fairly thought out, but not completely and I am certain there will be some tweaks. While currently, there is no requirement for individual documentation access permissions, maybe doing so will some day in the future be required, and would like to allow some flexibility. Also, there is no real need for the classes, database structure, and JSON requests/responses to mimic each other unless it makes it simpler to implement and maintain. Any thoughts of whether one of the two above approaches or some other approach should be taken? Thanks
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.