What is 'The right way' to authorize this REST API request? - php

I'm building a REST Api and I'm sitting here with a problem. By no means I am an expert on this subject, so I want to learn how to address REST architecture the 'right way' (or at least in a way that makes sense).
I'm building a web application with a Angular Frontend and a laravel-based, RESTfull backend API. The app has these 3 tables: Albums, Posts and Comments. A user can write a post in an album if he/she is a member of that album.
A user can be invited to become member of an album and then see all it's posts and the comments for those posts. If an user isn't (invited to be) a member of an album it can't comment on posts in that album.
In other words: if a user comments on a post, the post has to be from an album the user is a member of.
My dilemma is: how do I check/authorize this request?
My Eloquent relationships are as follows:
The user table has a many to many relationship with albums
Albums have many posts
Posts have many comments
The incoming request is a POST request that has 2 parameters:
album_id (the album that the post is in)
post_id (for the post that is being commented on)
body (The actual comment itself)
The author for the post is retrieved via Auth::user();
My initial thoughts for addressing this problem are:
I check for all the albums a user is a member of
Build an array of al the ID's of the found albums the user is a member of
Check if the post_id parameter is in that array
If it's not, the user can't comment and if it is, the user can comment
My code so far:
// Validate the Request Body
$input = $this->request->only([ 'album_id', 'post_id', 'comment' ]);
$validator = Validator::make( $input, [
'album_id' => 'required|integer',
'post_id' => 'required|integer',
'comment' => 'required',
]);
// Authorize the Request
$albumIdList = Auth::user()->albums()->get()->pluck('id')->toArray();
$postIdList = Post::whereIn( 'album_id', $albumIdList )->select('id')->get()->toArray();
if( ! in_array($this->request->get('post_id'), $albumIdList))
return abort(403);
// Store the new comment
$comment = Comment::create([
'user_id' => Auth::user()->id,
'post_id' => $input['post_id'],
'comment' => $input['comment'],
]);
return $comment;
I think this is working properly, but what if a album has 1000 posts? Building the array wit all post ID's becomes really intensive for the server... How would a professional company (like Facebook, Twitter, Pinterest) tackle this in their web application?
Thanks in advance!

You're looking for the whereHas and exists methods:
$allowed = Auth::user()->albums()->whereHas('post', function ($query) {
$query->where($this->request->only('post_id'));
})->exists();
Also, there's no reason to pass in the album_id. Your code can be reduced to this:
$this->validate($this->request, [
'post_id' => 'required|integer',
'comment' => 'required',
]);
$allowed = Auth::user()->albums()->whereHas('posts', function ($query) {
$query->where($this->request->only('post_id'));
})->exists();
if (! $allowed) return abort(403);
$input = $this->request->only('post_id', 'comment');
return Comment::create($input + ['user_id' => Auth::id()]);
If you want to clean this up further, you should look into Laravel's authorization facilities.

Related

Adding value to a model from url params in laravel 5

Playing around with a referral system in laravel 5.4. I have been able to create a unique shareable link for each user.
When another user clicks on that, I want the portion of the url with the referral id of the link's original owner to be added to the referrer field of the user.
I tried this method and been getting this error, what better way is there to do this.
use Illuminate\Support\Facades\Input;
protected function create(array $data)
{
$ref = Input::get('ref');
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
'referrer' => $ref
]);
return $user;
}
I am getting an error complaining that the referrer column cannot be null even though there is a ref on the link.
Sample link.
http://localhost:8000/register?ref=1b0a6294-043d-11e7-86bf-56847afe9799
User Model
protected $fillable = [
'name', 'email', 'password', 'level', 'btc_address', 'referrer'
];`
I think the problem might be your routes and url you use in form for registering user.
The url you showed is probably for displaying registration form, but when you send the form, you send it to http://localhost:8000/register url so you don't have ref defined in your url.
You should make sure, that you send form also to http://localhost:8000/register?ref=1b0a6294-043d-11e7-86bf-56847afe9799 url or put hidden field with ref value from get action.

CakePHP: Check for line item in iframe before submitting parent form

The app I'm working on (an order form) allows the user to enter multiple sub-records within an iframe. These sub-records are joined to the main record via a foreign key.
main_records line_items
----------- ----------
id int(11) PK etc. id int(11) PK etc.
main_record_id (FK)
I need the app to check whether at least one line item exists within this iframe before form submission. I would like to take advantage of the $validate functionality within the model, but I'm unsure how to proceed. Here's what I've tried in the Main model:
App::uses('AppModel', 'Model', 'LineItem');
public $hasMany = array(
'LineItem' => array(
'className' => 'LineItem',
'foreignKey' => 'main_record_id',
'dependent' => false
)
);
public $validate = array(
'main_record_id' = array(
'allowEmpty' => false,
'rule' => 'checkForLineItem',
'message' => 'You must enter at least one line item!'
)
);
//Check to make sure there is at least one line item before saving changes/submitting for approval
function checkForLineItem($id) {
$lines = $this->LineItem->find('all', array(
'fields' => array('LineItem.main_record_id'),
'conditions' => array('LineItem.main_record_id'=>$id, 'LineItem.deleted_record'=>0))
);
if(!empty($lines)) {
return true;
} else {
return false;
}
}
I also track whether the line item has been deleted. If it has, then it is not added to $lines.
I know I can accomplish this in the Controller, but as far as I know, that would require the form to post, and the user would lose any changes upon postback (I haven't yet implemented jQuery on this form). Am I on the right track with how to do this? What changes should I make to get this to work?
Your code looks about right, but validation indeed happens in form submit. If you want to check it prior to that you have to do in JavaScript (jquery). E.g. create a controller action that return if there are existing line items for given main record id and call it via AJAX.

Laravel 5.1 and Socialite Questions

This isn't a post about a problem I'm having right now. I'm planning on using the Laravel Socialite plugin in my App, I already have a fully working login/register system using Laravel's built in Auth system but I'm wondering if there is a good way to allow users that already have their accounts in my app to link their social profiles to the already existing account?
The plan would be that they can login with their username and password or any of their social profiles. All the research I've done so far explains how to set up Socialite and utilise that data for registering/logging in new users, however I can't find anything to marry the two together.
I'm not looking for anyone to write this for me so please don't get confused by this post.
Thanks for any info,
Andy
Absolutely.
I have FB and Twitter OAuth on one site. The way I do it is by adding oauth_facebook_id and oauth_twitter_id columns to the users table (as well as an avatar column, if you want that).
I have the routes set up as /auth/{provider} and /auth/{provider}/callback, so /auth/facebook and /auth/facebook/callback for example.
In my handleProviderCallback($provider) method on my AuthController, I grab the returned user's details and check to see if they already exist in my database with their email, Facebook OAuth ID or Twitter OAuth ID. If they exist, I set their email, OAuth IDs and their avatar, if they don't exist I create them. Then I log them in.
$user = Socialite::driver($provider)->user();
$user_query = User::where('email', $user->email)
->orWhere('oauth_facebook_id', $user->id)
->orWhere('oauth_twitter_id', $user->id)
->get();
if($user_query->count() > 0) {
$the_user = $user_query->first();
if($provider === 'facebook') {
$the_user->oauth_facebook_id = $user->id;
}
if($provider === 'twitter') {
$the_user->oauth_twitter_id = $user->id;
}
$the_user->avatar = $user->avatar;
$the_user->save();
\Auth::login($the_user, true);
return redirect('/');
}
$new_user = User::create([
'name' => $user->name,
'email' => $user->email,
'oauth_facebook_id' => $provider === 'facebook' ? $user->id : NULL,
'oauth_twitter_id' => $provider === 'twitter' ? $user->id : NULL,
'avatar' => $user->avatar
]);
\Auth::login($new_user, true);
return redirect('/');
Hopefully this helps you make sense of how to do it.

Find multiple elements of a sub-array entry

So, I'ld like to add a "Like" system on something I'm working on and I have a bit of trouble with it.
So my DB's table for my likes is like this :
========================================
|| *id* | created | user_id | post_id ||
========================================
I'm with with counting the number of likes for a post and all. The problem I have is I want to find if the logged user has already liked the post is is viewing so the "Like" link would become an "Unlike" link. And that's my problem. I can't manage to imagine how to do it.
Any help ?
Look at Model::hasAny() method:
$userHasLikedThisPost = $this->Like->hasAny(array(
'user_id' => $this->Auth->user('id'),
'post_id' => $postId
));
Then simply set a view var and output the corresponding link.
Make a helper method in the model so its more re-usable:
public function hasLike($postId, $userId) {
return $this->hasAny(array(
'user_id' => $userId,
'post_id' => $postId,
));
}

Create album with friends access

im using $create_album = $facebook->api("/{$user_profile['id']}/albums", 'post', $album_details); to create a album, and the album sharing is set to public, how i create a album with friends share?
In most calls to the facebook api you can add to the details the "privacy" field:
privacy = Array('value'=>'ALL_FRIENDS');
$privacy = (object)$privacy;
$albumDetails = array(
'name' => 'Album name',
'privacy' => $privacy
);
Possible values for 'value' are: EVERYONE, ALL_FRIENDS, NETWORKS_FRIENDS, FRIENDS_OF_FRIENDS, CUSTOM. Please check the URL in the source (Below) for more information
Then make the API call normally. (Note that the privacy field might be overridden in some cases)
Sources:
http://developers.facebook.com/docs/reference/api/post/ (Scroll down to privacy)

Categories