تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Design Secure Document Access0:23

Someone fills out name, address, phoneNumber, socialSecurityNumber, a bunch of really sensitive stuff. And we don't want that to be available to anyone with a link. We also want to be able to control who gets to see it. Maybe only people in HR get to see that application versus just anyone who's logged in. So we need to find a way to do document uploads, but also control who is allowed to see them. So with this feature, I want to use an approach that I like to use pretty regularly, which is to design or build the API I want to use, and then go ahead and make that API work. So let's go into the code here, and I've got this openNewWindow.svg. You can see it right here.

Create Application Link API0:58

So let's go into the code here, and I've got this open new window SVG. You can see it right here. So when I click that, I want to open in a new window the application that's associated with this User. So let me go build the API I want to use. Let me just take this, and I want to surround it with a <a> tag, and I want this to be user.application URL. And let's also target _blank. And so I want to build this application URL so that that will give me a URL that I can use to see it only if I have the permission to see it.

And so I want to build this application URL so that that will give me a URL that I can use to see it only if I have the permission to see it. So let's get started first by going to the User model. And let's add that. There's a couple of additional methods I've added here, but we'll have a function for application URL. And for now, we're just going to return that. So obviously, it doesn't work yet. We're going to make that work. So if I refresh the page, nothing works here, but we're going to make it work.

Wire Up File Upload1:55

We're going to make that work. So if I refresh the page, nothing works here, but we're going to make it work. So the first thing we need to do is actually upload the document into our private S3 bucket. So let's go to add a team member. And very similar to what we had before, I've already built out the UI, but let's just make it functional. So we need to go into our code, go to the addUser. And this is the UI, but it's not wired up at all, I just have an input that doesn't have any wire:model or anything. So let's add that now, we'll say wire:model.

have any wire:model or anything. So let's add that now, we'll say wire:model. And this is going to be my application. All right, and this is going to work very similar to the one we did before. So now let's go to our AddUser component, we are going to need a public property for an application, we do also want to validate that application. So in this case, I want to say that let's work with PDFs. So it's a file, and the mimes, we're only going to allow a PDF. And let's also give it a max of 1024, just so it doesn't get too big on us. So now we're getting to the point where we could store this application in the database,

Create Document Model2:51

And let's also give it a max of 1024, just so it doesn't get too big on us. So now we're getting to the point where we could store this application in the database, but we don't actually have a place for that yet. And so I think in this app, we're going to end up having a lot of different documents. So I don't just want to put it on the User model, like I did this photo, I want to create a new model for Document. So let's go into our terminal and do php artisan make:model Document. And eventually, I'm going to want a controller and I do want a migration now. So I'm going to generate both of those right now. Okay, now that that's done, let's go to the migration first.

So I'm going to generate both of those right now. Okay, now that that's done, let's go to the migration first. So create documents table. And so you can see if you remember, we have this multi-tenant application, and we created have a tenant_id that's associated to this document. And that's really important, because we want to make sure if you're not in our tenant, that automatically means you cannot get the record out of the database, which means you can't see it. So in the same way that we make sure data out of the database is not going to leak to other tenants, this is going to ensure also the documents or the files that we want to

So in the same way that we make sure data out of the database is not going to leak to other tenants, this is going to ensure also the documents or the files that we want to keep private won't leak out either. Now I've already designed this table. So I'm going to paste in the columns right now. Okay, and I just pasted those in. So you can see we have a type, the first one we're going to be working on is an application. But like I said, in the future, we may want to add more types, we do want to know what user it's assigned to. And we also need to get a file name, we need to know an extension because PDFs work a little.

user it's assigned to. And we also need to get a file name, we need to know an extension because PDFs work a little bit different from images. So I want to capture that when it's uploaded. And then it might be nice to know the size too. So now that we have this, let's get this relationship working really quick. Let's go back to the User model. And I'm just going to put it at the bottom, a User has many Documents. So we have the Document model there. That looks good.

So we have the Document model there. That looks good. And then if we go to the Document model, we're going to want to first let's unguard it. And secondly, we're going to say a Document belongs to a User. All right, everything looks good there. So let's go back to our terminal. And let's see if we can migrate our database. So that works. So let's go back to our Livewire component, we've got the AddUser component here. So once we validate the application is a PDF, we do want to create the User first, because

So let's go back to our Livewire component, we've got the AddUser component here. So once we validate the application is a PDF, we do want to create the User first, because the application is going to be associated to the User. So let's create a $user variable here. And then after we create the User, we're going to want to create the document. So to create this document, I want to do a few things, I want to get a fileName, I also want to store it to our private s3. And then the last thing I want to do is create a document in the database. So the first thing if we look up here, we have a just auto generated fileName here, which was fine for this avatar photo, but I want to create kind of a custom fileName.

So the first thing if we look up here, we have a just auto generated file name here, which was fine for this avatar photo, but I want to create kind of a custom file name here. And so let me actually paste in some code. And let's go over this now. So for the file name, what I want this to be is I want the actual name of whatever document the person uploaded, I don't want just this random string. So this pathinfo is actually going to get me it's going to take the original client name and just give me everything before the dot .pdf or dot .png or whatever it was. So then after that, what I want to do is concatenate with an underscore and get a timestamp.

name and just give me everything before the .pdf or .png or whatever it was. So then after that, what I want to do is concatenate with an underscore and get a timestamp. Now this is important because I don't want to have duplicate files with the exact same name. So if I get a timestamp, that's always going to make sure it's a unique file name. And then at the back, of course, I want to have a . and then pdf. So getClientOriginalExtension is going to get me .pdf in this case. Now these methods here like getClientOriginalName, getOriginalExtension. If you want to know more about these, we can go into the temporary uploaded file. And you can say getOriginalClientOriginalName.

If you want to know more about these, we can go into the temporary uploaded file. And you can say getOriginalClientOriginalName. So this is where all of this comes from. in this temporary uploaded file, which is what the application and the photo are in Livewire. So just to reiterate, this is going to give me something like docName_someTimestampHere.pdf. The next thing we want to do is store it into our private s3 bucket. So I'm going to say this application, I want to store as this is a little bit different than the store method here because I'm able to pass in the file name I want to use.

So I'm going to say this application, I want to store as this is a little bit different than the store method here because I'm able to pass in the file name I want to use. So the directory I want to use here is /documents. And I want to have the userId. So every User is going to have their own directory in this file system where all of their documents are stored. Let's do that. And then the next argument I'm going to pass in is this file name. And then finally, which bucket do I want to put it in and it's going to be s3. So if you remember, we named s3 our private bucket and s3 public, our public bucket.

And then finally, which bucket do I want to put it in and it's going to be s3. So if you remember, we named s3 our private bucket and s3 public, our public bucket. So then lastly, we just want to create a document in the database. So we'll do that with User, we have a relationship with Documents, and we're going to create one. So the type of Document again, we are working on an application. So we'll do that, we do need to know the fileName. And so I'm going to store that here as the fileName. We also want to know the extension. And I can use the same thing I use to generate the fileName.

We also want to know the extension. And I can use the same thing I use to generate the file name. Let's just grab this from right here. And we'll copy it there. So now we know the extension, in this case, it will always be a PDF because we validated that it was a PDF. And then lastly, the size. And again, we can just get this off of the temporary file upload. And there's a method here that's just called getSize. And that will give me an integer value for the size.

And there's a method here that's just called getSize. And that will give me an integer value for the size. So if we did this, right, I'm going to go back to my AddUser page. And let's just add something to the back of that. So that's a duplicate. Let me get a photo, we'll choose Brandon this time. For my application, I just have a generic job application not filled out. I don't actually have any user feedback that the upload was successful or anything. We can work on that later. And let's just add the TeamMember.

We can work on that later. And let's just add the team member. I don't have any loading indicator, but I did get a message that shows we did it. So let's take a look at our database. If I refresh here, I should have a documents. And I do have the file name, just like I was expecting job_application_with_the_timestamp.pdf. If I go back to the users, you can see the user was created, which is really nice. And the last thing I want to check is in my Amazon S3 bucket, it's going to my private bucket. And you can see now I have a documents folder, user_id_19.

bucket. And you can see now I have a documents folder, userId 19. And there is that's the file name. So we're in a good spot right now. But again, like I mentioned before, we cannot use this link, this is not ever going to work, this will always be access denied. Because we can't just allow anyone to use this s3 link, we need this to come through our Laravel application, we have to do the validation on our server, and then decide do we want to show you this application, or it could be any other type of a document that we put in the system.

Build Authorized Document Route9:57

do we want to show you this application, or it could be any other type of a document that we put in the system. So let's work on that now. And to do that, I'm going to go back to my Laravel app, and I'm going to head over to my routes file in web.php. And so down here, inside off number one, we're let's start thinking about how do we make sure only the people we want to see this can actually see it? Well, we definitely want to make sure they're authenticated. So let's put a route in here. And let's just make it a get route.

So let's put a route in here. And let's just make it a GET route. And I want this to be /documents, slash, let's get a User. And then let's get a fileName. Now I want this to go to the DocumentsController, which is right there. And let's just create a show method on that controller. So now let's go in here, we've got public function show. And in here, what I want to do is, let's route model binding with a User. And then we'll also get the fileName passed in there. Now the goal inside this controller is number one, let's find the document from the database.

And then we'll also get the file name passed in there. Now the goal inside this controller is number one, let's find the document from the database. Secondly, we will authorize the person making the request. So the user making the request. And then lastly, if they're authorized, we want to stream the file to the user or to the browser. Okay, so let's start with finding the document from the database. So we already have user, we know the user has documents. So we can get the documents and we can say where the file name matches the file name that was passed in, based on how we generated the file name, we know there's only going

So we can get the documents and we can say where the file name matches the file name that was passed in, based on how we generated the file name, we know there's only going to be one. So we'll just get all of them and grab the first. So that's going to give us again, in the documents, it's going to just give us this database entry. So we're not going to get the file yet from the file system, we just need to know, right, what's the database entry that corresponds with this file. Now here is where we can authorize, okay, now that we know who the user is, because we have the request user, so we know who the user is, we also know what the document is, we can do whatever we need to authorize this user and determine if they can see it or not.

we have the request User, so we know who the User is, we also know what the document is, we can do whatever we need to authorize this User and determine if they can see it or not. So we can do something like if request that User, I have a couple methods I created, like isAdmin, or let's say they're not an admin. So if it's not an admin, we'll just say abort, and 403 means not authorized. So however you want to do your authorization, you can put that right here. And again, I would clean it up. But we're not going to worry about cleaning it up right now, I just want to show you how it works. And then lastly, we're going to stream the file to the browser.

it works. And then lastly, we're going to stream the file to the browser. And before we do that, we actually need to store this. So let's call this a document variable. And now we've got that document. Okay, so part of the reason I wanted to know when we save the document, is it a PDF, a PNG, whatever, because how you stream the file to the browser is a little bit different if it's a PDF versus an image. So we're just going to focus on the PDF right now. So let's say if document extension equals PDF.

So we're just going to focus on the PDF right now. So let's say if $documentExtension equals PDF. So in this case, we're going to return a response. And so we're going to respond to the browser, we're going to go out to the storage disk. We need our disk to be S3. This is where we're keeping our private files. And then we want to just go get whatever is in documents. And then we have the $userId. And then we have the $fileName. And so that's just the directory structure that we created in Amazon S3.

And then we have the file name. And so that's just the directory structure that we created in Amazon S3. And then lastly, we do need to add a header here. And again, this is why it's important to know what type of file it is. And here we just need to say application/pdf. Of course, to make this complete, we would have an else and then do something for the image. But we're not going to worry about that right now. So I think we're about done here. So let's go back to our web.php.

So I think we're about done here. So let's go back to our web.php. And so we want the documents user file name. So now let's go to the browser. Let's just open some link in a new tab. And let's throw documents. Let's go back to our database and see it's 19. And then let me copy this. So I've got the user should be 19. And the file name.

So I've got the user should be 19. And the file name. So if I hit Enter, there we go, we were able to stream this from S3. Now I want you to understand how powerful that was. Maybe it didn't seem that powerful to you. Let me go back to our application. Let's go into the controller. And let's change this to isHR. So my user is not HR. So let me try to reload this again, 403.

So my User is not HR. So let me try to reload this again, 403. So now I think you're seeing the power here. The other thing that we're doing, as far as this request, so if you go back to web.php, you have to be logged in. But the other thing, because we are in a multi tenant application, we have to go to the database and find the User. Well, if you remember, the User has this belongsToTenant trait, which has this tenantScope, which makes sure that if you're logged in, the tenant ID is set in the session and the User has to match the tenant ID.

Generate Application URL15:11

which makes sure that if you're logged in, the tenant ID is set in the session and the user has to match the tenant ID. So with what we've done here, you can't even get to this controller method without being a logged in user within the same tenant as the user ID that was passed in, then you have additional authorization here. And then finally, if it passes this authorization, you stream it to the browser. So the last thing we need to do to bring this full circle, let's go back to our web one more time. And I'm just going to grab this and copy it again, let me go to my User model. And what I want to do is take this application URL, and we were returning this.

And I'm just going to grab this and copy it again, let me go to my User model. And what I want to do is take this applicationURL, and we were returning this. But what we really want to return is some URL like this. Now obviously, this is not correct. So to get this working, we need to get the application. So again, let's make another function for application. And this can just be returned this->documents, and we'll say where type is application, and then get the first. So now up here, we can say if this application, then we'll return a URL. And that would be documents/this->id/this->application_file_name.

So now up here, we can say if this application, then we'll return a URL. And that would be documents//. And then of course, if there is no application for this User, let's just return maybe a hash or something. So now let's go to our browser and see if this actually works. So if we go back to the team, all right, this, it looks like it just has that hash because there's nothing there. And then this one, if we click it, we're still getting that link and it's still forbidden. So let's go back and let's make sure it's not forbidden. Go to our DocumentController.

So let's go back and let's make sure it's not forbidden. Go to our DocumentController. And for the authorization here, let's just say isAdmin. And let's close that out and do it one more time. Whoops, wrong one here. And there we go, we streamed it directly from s3. So now if we go back to our application, we basically have a working application. Now it doesn't really do very much. But the point is, you would build out your application using this structure and make your application work just the way you want it to work.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟