Simple PHP/MySQL ACL System - php

I have a simple ACL system in PHP and MYSQL started. I need help finishing it though...
I have 2 Database tables shown below...
user_link_permissions : Holds a record for every user, on every entity/link that permissions apply to...
--
-- Table structure for table `user_link_permissions`
--
CREATE TABLE IF NOT EXISTS `user_link_permissions` (
`id` int(100) NOT NULL AUTO_INCREMENT,
`user_id` int(30) NOT NULL,
`link_id` int(30) NOT NULL,
`permission` int(2) NOT NULL DEFAULT '0',
KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=2055 ;
intranet_links : Is basically the entity that the permission gives or revokes user access to
--
-- Table structure for table `intranet_links`
--
CREATE TABLE IF NOT EXISTS `intranet_links` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
`description` text NOT NULL,
`url` varchar(255) DEFAULT NULL,
`notes` text,
`user_login` varchar(255) DEFAULT NULL,
`user_pw` varchar(255) DEFAULT NULL,
`active` int(2) NOT NULL DEFAULT '1',
`sort_order` int(11) DEFAULT NULL,
`parent` int(10) NOT NULL DEFAULT '1',
`local_route` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`),
UNIQUE KEY `local_route` (`local_route`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=34 ;
To save these permissions settings I have a matrix style grid like this below where each checkbox is a record in the user_link_permissions table...
I need help creating a simple ACL function in PHP which can check if a user has permission or not to view a link/entity based on the database results.
On page load I am thinking I can query the user_link_permissions DB table for all records with a matching user ID of the logged in user and store them to a session array variable.
A function could then use that array to check for a link/entity permission using that array value on the entity key.
I just can't visualize how it might look at the moment in PHP.
Any help please?
function aclCanAccess($user_id, $entity_id){
}
$entity_id = 123;
if(aclCanAccess(1, $entity_id){
// yes user can see this item
}else{
// NO user permission denied
}

I will leave writing the code to you for fun.
Assume you are storing all the previously queried permissions in a variable called $_SESSION['acl']
Your ACL function should:
check the session if you already queried that entity
if it is not set, read it from the db
in short
function..... {
if(!isset($_SESSION['acl'][$entity_id])) {
$_SESSION['acl'][$entity_id] = query here to return to you if he has access or not
}
return $_SESSION['acl'][$entity_id];
}
You can also read the entire array when you log in the user. That might also be appropriate. In that case you should be able to just
return $_SESSION['acl'][$entity_id];
But I would then try and catch an exception in case it is not set.

Related

How do you query a child and parent in laravel eloquent

I want to figure out how to query to get a result that was either created by the current user or the current users parent.
I have two tables users and workouts. Both tables have a created_by column, which stores the user id of whoever created said user or workout record.
CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`created_by` int(10) unsigned DEFAULT NULL,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
KEY `users_created_by_foreign` (`created_by`),
CONSTRAINT `users_created_by_foreign` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`)
);
CREATE TABLE `workouts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`created_by` int(10) unsigned NOT NULL,
`description` text COLLATE utf8_unicode_ci,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
KEY `workouts_created_by_foreign` (`created_by`),
CONSTRAINT `workouts_created_by_foreign` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`)
);
In my user model I have a function to return all the workouts that the user has created.
public function createdWorkouts(): HasMany
{
return $this->hasMany(Workout::class, 'created_by');
}
What I want to figure out is, how can I query to find a workout using a ID but only for workouts the current user has created or the creator of the current user has created. Below is my current route logic, which only returns a workout created by the current user.
$user = User::find(1); // This would come from the JWT
if (!$workout = $user->createdWorkouts()->find($args[‘workout_id’])) {
return $response->withJson([], 404);
}
For this instance, I probably wouldn't use an eloquent relationship as it could be either the users id or their created_by value.
Instead, I would change the method on your user model to read:
public function createdWorkouts()
{
return Workout::where('created_by', $this->id)->orWhere('created_by', $this->created_by)->get();
}
Then you can do $user->createdWorkouts() which will return you a collection of all workouts where it was created by the user, or created by the user that created the user.
As it is a collection, you then have access to all of the other methods on a collection such as find etc

Laravel: Use model to return database row based on linked table.

I need to use my Website model to get a row from my database within the websites table, however this row is identified through my domains table.
So basically it would be great to do a query on my domains table and match the row, then from that get the website row from the websites table using the website_id column.
But I want to simply pass this data into my controller by just referencing the Model within the method.
class WebsiteController extends Controller {
public function index(Website $website) {
print_r($website);
return view('index');
}
}
My domains table:
CREATE TABLE `domains` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`website_id` INT(11) NOT NULL DEFAULT '0',
`domain` VARCHAR(255) NOT NULL DEFAULT '0',
`active` INT(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
INDEX `website_id` (`website_id`),
CONSTRAINT `website_id` FOREIGN KEY (`website_id`) REFERENCES `websites` (`id`)
)
COMMENT='This table will contain all of the domains registered on MarvWeb, this will link to the website record. '
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=3;
And websites table:
CREATE TABLE `websites` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NULL DEFAULT NULL,
`tagline` VARCHAR(255) NULL DEFAULT NULL,
`description` VARCHAR(255) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
COMMENT='This table will contain all the websites data. '
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=2;
Does this make sense?
Add a website function to your Domain model.
class Domain extends Model{
public function website(){
return $this->hasOne('App\Website');
}
// remainder of model.
}
When you retrieve the Domain query results, the website can be accessed by
print_r($domainRowResult->$website->tagline);

Speed up MySQL inner join with LIKE clause

I have the following 2 tables, api_analytics_data, and telecordia.
CREATE TABLE `api_analytics_data` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`upload_file_id` bigint(20) NOT NULL,
`partNumber` varchar(100) DEFAULT NULL,
`clei` varchar(45) DEFAULT NULL,
`description` varchar(150) DEFAULT NULL,
`processed` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_aad_clei` (`clei`),
KEY `idx_aad_pn` (`partNumber`),
KEY `id_aad_processed` (`processed`),
KEY `idx_combo1` (`partNumber`,`clei`,`upload_file_id`)
) ENGINE=InnoDB CHARSET=latin1;
CREATE TABLE `telecordia` (
`tid` int(11) NOT NULL AUTO_INCREMENT,
`ProdID` varchar(50) DEFAULT NULL,
`Mfg` varchar(20) DEFAULT NULL,
`Pn` varchar(50) DEFAULT NULL,
`Clei` varchar(50) DEFAULT NULL,
`Series` varchar(50) DEFAULT NULL,
`Dsc` varchar(50) DEFAULT NULL,
`Eci` varchar(50) DEFAULT NULL,
`AddDate` date DEFAULT NULL,
`ChangeDate` date DEFAULT NULL,
`Cost` float DEFAULT NULL,
PRIMARY KEY (`tid`),
KEY `telecordia.ProdID` (`ProdID`) USING BTREE,
KEY `telecordia.clei` (`Clei`),
KEY `telecordia.pn` (`Pn`),
KEY `telcordia.eci` (`Eci`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Users upload data via a web interface using Excel/CSV files into api_analytics_data. The data contains EITHER the partNumbers or CLEIs. I then update the api_analytics_data table by joining the telecordia table. The telecordia table is the master list of partNumber and Cleis.
So if a user uploads a file of CLEIs, the update/join I use is:
update api_analytics_data aad
inner join telecordia t on aad.clei = t.Clei
set aad.partNumber = t.Pn
where aad.partNumber is null
and aad.upload_file_id = 5;
It works quickly, but not very thoroughly. The problem I have is that the CLEI uploaded may only be a substring of the CLEI in the telecordia table.
For example, the uploaded CLEI may be "5SC1DX0". In the telcordia table, the correct matching row is:
tid: 184324
ProdID: 472467
Mfg: PLSE
Pn: AUA58-2-REV-E
Clei: 5SC1DX04AA
Series: null
Dsc: DL SGL-PTY POTS CU RT
Eci: 205756
AddDate: 1994-03-18
ChangeDate: 1998-04-13
Cost: null
So obviously my update doesn't work in this case, even though 5SC1DX0 and 5SC1DX04AA are the same part.
What I need is a wildcard search. However, when I try this, it is crazy slow. With about 4500 rows uploaded into the api_analytics_data table, it runs for about 10 minutes, and then loses the connection with the server.
update api_analytics_data aad
inner join telecordia t on aad.clei like concat(t.Clei,'%')
set aad.partNumber = t.Pn
where aad.partNumber is null
and aad.upload_file_id = 5;
Is there a way to optimize this so that it runs quickly?
The correct answer is "no". The better course of action is to create a new column in telecordia with the correct Clei value in it, one that can be used for joining the tables. In the most recent versions of MySQL, this can even be a computed column and be indexed.
That said, you might be able to do something if the matching portion is always the same length. If so, try this:
update api_analytics_data aad inner join
telecordia t
on t.Clei = left(aad.clei, 7)
set aad.partNumber = t.Pn
where aad.partNumber is null and aad.upload_file_id = 5;
For this query, you want an index on api_analytics_data(upload_fiel_id, partNumber, clei) and telecordia(clei, pn).

PHP SQL query slow?

Here is my function which i am using to un-follow users.It first DELETE the relationship between users and all the notifications that are related to this relationship.Then it INSERT a new notification for user which we are going to un-follow and then UPDATE his followers count (as one follower has left).I am using multi_query and this query seems to be bit slower on large database and i want to know whether it's a good practice or not or is there is any more complex form of query to get the job done.
PHP Function
// 'By' is the array that hold logged user and 'followed' is the user id which we are going to unfollow
function unFollowUser($followed,$by) {
$following = $this->getUserByID($followed);// Return fetch_assoc of user row
if(!empty($following['idu'])) { // if user exists
// return user followers as number of rows
$followers = $this->db->real_escape_string($this->numberFollowers($following['idu'])) - 1;
$followed_esc = $this->db->real_escape_string($following['idu']);
$by_user_esc = $this->db->real_escape_string($by['idu']);
// delete relationship
$query = "DELETE FROM `relationships` WHERE `relationships`.`user2` = '$followed_esc' AND `relationships`.`user1` = '$by_user_esc' ;" ;
// delete notification (user started following you )
$query.= "DELETE FROM `notifications` WHERE `notifications`.`not_from` = '$by_user_esc' AND `notifications`.`not_to` = '$followed_esc' ;" ;
// Insert a new notification( user has unfollowed you)
$query.= "INSERT INTO `notifications`(`id`, `not_from`, `not_to`, `not_content_id`,`not_content`,`not_type`,`not_read`, `not_time`) VALUES (NULL, '$by_user_esc', '$followed_esc', '0','0','5','0', CURRENT_TIMESTAMP) ;" ;
// update user followers (-1)
$query .= "UPDATE `users` SET `followers` = '$followers' WHERE `users`.`idu` = '$followed_esc' ;" ;
if($this->db->multi_query($query) === TRUE) {
return 1;
} else {
return 0;
}
} else {
return 0;
}
}
Table structures
--
-- Table structure for table `notifications`
--
CREATE TABLE IF NOT EXISTS `notifications` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`not_from` int(11) NOT NULL,
`not_to` int(11) NOT NULL,
`not_content_id` int(11) NOT NULL,
`not_content` int(11) NOT NULL,
`not_type` int(11) NOT NULL,
`not_read` int(11) NOT NULL,
`not_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
--
-- Table structure for table `relationships`
--
CREATE TABLE IF NOT EXISTS `relationships` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user1` int(11) NOT NULL,
`user2` int(11) NOT NULL,
`status` int(11) NOT NULL,
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
--
-- Table structure for table `users`
--
CREATE TABLE IF NOT EXISTS `users` (
`idu` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL,
`password` varchar(256) NOT NULL,
`email` varchar(256) NOT NULL,
`first_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`last_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`verified` int(11) NOT NULL,
`posts` text CHARACTER SET utf32 NOT NULL,
`photos` text CHARACTER SET utf32 NOT NULL,
`followers` text CHARACTER SET utf32 NOT NULL,
UNIQUE KEY `id` (`idu`),
UNIQUE KEY `idu` (`idu`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
In my testing, multi_query has been the fastest way to execute multiple different queries. Why do you feel it's running slow? Compared to what?
Anyway, improvements could come from adding indexes to some of the columns you search frequently:
relationships.users2
relationships.users1
notifications.not_from
notifications.not_to
users.idu
Adding indexes makes searching faster, but it has at least two downsides:
Makes the DB a lot more resource hungry, which could affect your server performance
Makes writing operations take longer
I don't see any problem with your current queries. Really consider whether the slow performance you're seeing comes from the DB queries themselves, or from the rest of your PHP process. Try measuring the script time with the queries, then skipping the queries and taking another measurement (you could hardcode query results). It will give you an idea of whether the slowness is attributable to something else.
Either way, benchmark.
Try creating index on user where deletes are running , this may speed up query

Cant "give" null value in a field of my sql table phpmyadmin

Im using phpmyadmin and I have a table categories with 4 fields:
-> id
-> id_father_category (I put this with predefined: NULL) and I also put a checkbox Null
-> name
-> content
I want to give null values to my id_father_category field.
But Im having this error:
Warning: #1366 Incorrect integer value: '' for column 'id_father_category field' at row 1
And my field id_father_category stays automatically with value 0, and I dont want that.
Somebody there knows how I can solve this problem?
CREATE TABLE IF NOT EXISTS `categories` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_father_category` int(11) DEFAULT NULL,
`name` varchar(64) NOT NULL,
`content` varchar(256) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
Hope this will solve your problem...
This is an addition to #Nirjhor's answer.
ALTER TABLE categories MODIFY id_father_category INT(11) DEFAULT NULL;
or you can recreate the whole thing:
CREATE TABLE IF NOT EXISTS `categories` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_father_category` int(11) DEFAULT NULL,
`name` varchar(64) NOT NULL,
`content` varchar(256) NOT NULL,
PRIMARY KEY (`id`) )
ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
Setting the default value to NULL will handle your issue as well as set the field to NULL. One thing to note here is to make sure you don't have some weird index on that field cause that can also create an issue.

Categories