Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Lucky you! You found an early release chapter - it will be fully polished and published shortly!

Treasure Security

This Chapter isn't
quite ready...

Rest assured, the gnomes are hard at work
completing this video!

Browse Tutorials

Coming soon...

All right, so we have our Dragon Treasure API really working for the most part. Though we did have quite a few customizations that we added last time. We had all those customizations well tested inside this test, and now we're just kind of putting the pieces back together, seeing how we can implement some of those custom things in a simpler way with our new setup. So, if you run all of the tests, we can see we have quite a few failures right now. And I can see one of them is response status goes 422, but expected 403. So, test post to create treasure denied without scope. So, this is related to security, and that makes sense. We haven't added any of the security stuff to our Dragon Treasure API. So, let's do that. Now, I'm going to start like I did with the user API by specifying exactly what operations I want. Right now, we're supporting all of them, including put and patch. Let's just do the same ones we had before. So, I'm going to say new Git, new Git collection, new post, and post is actually one where we had security on there. And post is actually one where we had security on there. So, security of is granted. And then we have a role in our system called role treasure create. So, this actually has to do with that test that failed. It's checking to see if we have an API token that has that scope or has that role on it. Oh, I saw create correctly. And then we also had new patch, and we had security for that as well. This is calling a voter. We're going to talk about this in a second. We have a custom voter that's checking to see if you can edit this Dragon Treasure. And finally, we also included the delete. And last time, we just said, hey, only admins can delete. So, is granted, and we were checking for role admin on that. Cool. So, this puts back the same permissions that we had before. All right. So, we had six failures a second ago. If we try the test now. All right, better. We are down to five failures. And we're going to zoom in on this test patch to update Treasure. So, I'm going to run just that test. And let's spin over here and actually take a look at what that's doing. So, very simple. The first thing this does is it creates a user, creates a Treasure, logs in as the owner, and then just tries to change the value of that Treasure. Make sure we get back 200 status code. Make sure we see the updated value. Right now, we are getting a 403 status code. But a 200 was expected. So, 403 is a security failure. So, for some reason, we're not allowed to make a patch request to this Treasure, even though we're the owner. So, if we look back a second ago, our patch request is using this is granted edit object. This is something we built in the previous tutorial. And this edit object thing here is handled by a custom voter called Dragon Treasure Voter. So, for some reason, this voter isn't being called or it's failing. Again,

So to see what's going on in here, I'm actually just going to dump attribute and subject. So this supports method should be called any time any security decision is made in the entire system. So should be getting hit. And when we hit it, yeah, it is going to hit look in scheme pass edit. So this is actually coming from our thing here, we're passing it edit, and we're passing it the object. But here's the kicker. The object is now dragon treasure API. That makes sense. That's what object we are inside of right now. But our dragon treasure voter was written to work with our entity, not our API objects. So this is simple enough. I'm going to rewrite this to work with our API class. So just for clarity, I'm going to rename it to dragon treasure API voter. And then we will support if dragon treasure API is the subject. And now down here, we'll change this subject should now be a dragon treasure API. So let's actually just DD that subject right there. But actually, I'll go ahead and try to fix the code here. So we have some code here that says that if you don't have this role, that's actually a scope. This relates to our token scopes during false, but here's really the logic here. We're checking to see if subject our dragon treasure API equals a user, which is the currently authenticated user. So what we're probably going to want here is just something like, let me comment that out for a second, subject error owner equals user. And that's actually not quite right yet. I'm going to put that DD subject back so we can see why. Because see what dumps here is a dragon treasure API, of course. And remember, the owner property is not a user entity, it's the user API object. So we can't just compare the user API object to the user entity object. That's not going to match each other. We also need to be careful here because of our mapper. Notice that the user API is actually not populated. It's a shallow mapping here. So that's OK. We do have the ID we can use, though. So it's a long way of saying that we can check the owner's ID against the currently authenticated user's Git ID. And notice it didn't autocomplete that Git ID. That's just because up here, we can tighten up this instance of check. This will definitely be our user entity down here. Now we can say Git ID, but I'll actually code defensively, too, just to make sure. In case the subject doesn't have an owner, we won't explode. I'm recording right now. I'll be done in just a minute. All right, so I'm going to try it now. OK, that gets further. So current response status code 415, 200 expected. This is actually a really small detail we've talked about a couple of times. It's the content type application JSON is not supported. You need to use application merge plus JSON. So whenever you make a patch request, you need to have a headers here set to content type with application slash merge. I love you, too, bud. Patch plus JSON. Now, the real reason we didn't have to have that before, I mentioned in the last tutorials, we did some funny business with some formats, and it actually made that not necessary. But really, we do need that on there. So I'm actually going to quickly add that to all of our patch requests here. There's a bunch of them. And there we go. All right. Now, let's see if we hit any luck. And ooh, it dies. It hits our dump. As a reminder, that dump is coming from our Dragon Treasure API to Entity Mapper when the owner is being set. So actually, let me comment this out right now so we can see the full picture of what's going on. All right. So the current response status goes 200, but 422 expected. This is coming down from line 157. So if we look at our test, most of our test is actually passing. So 157 is way down here. So that means that we are able to send a patch request and have that update. Now, the flow here is kind of cool to think about. When we make a patch request to a treasure, it's first going to use our data provider to find the Dragon Treasure Entity. And then we map that to the Dragon Treasure API object. Then the new value is serialized onto our Dragon Treasure API. And then finally, in our processor, we map the updated Dragon Treasure API back to a Dragon Entity. And that's ultimately what saves. And it's the Dragon Treasure API is then returned in the JSON. So this is working. It's just kind of cool to think about how all the different pieces come together. Now where we're failing is all the way down here. This is actually checking to see that we aren't allowed to change the owner to someone else. So what we're actually doing here is we're logging in as a user, editing our own treasure, but then trying to change the treasure to somewhere else. And previously, we actually had a custom validator that prevented that. So let's re-add that validator now. So let's go to Dragon Treasure API. And above our owner property, previously, we had an isValidOwner validator. So if we look in the validator directory, this is going to work exactly the same, except that this validator is expecting to be put on top of a property that holds a user entity. Now we're putting it on a property that holds a user API. So just like with everything else, we just need to adjust it to its new situation. So we'll assert that this is an instance of user API. And then same thing down here. We just need to see if the value, meaning the user API that's on this property, we want to check to see if it's not equal to the currently authenticated user. So once again, we'll actually use the IDs to compare this. And like last time, I'm going to use a little assert to help my editor. And you can see now it's happy about the getID. All right. Now I'm going to run that test. Oh, and let me put my semicolon on there. Now I'm going to run that test. It passes. So just a lot of putting pieces back together. Let's run all of our Dragon resource tests, and we're down to just three failures. And if you look into these, these are all related to the same thing, the isPublished property. Our Dragon Treasure API doesn't have an isPublished property yet. We're saving it for last because it's a little bit special. Let's tackle it next.

Leave a comment!

Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "3.1.x-dev", // 3.1.x-dev
        "doctrine/annotations": "^2.0", // 2.0.1
        "doctrine/doctrine-bundle": "^2.8", // 2.10.2
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.4
        "doctrine/orm": "^2.14", // 2.16.1
        "nelmio/cors-bundle": "^2.2", // 2.3.1
        "nesbot/carbon": "^2.64", // 2.69.0
        "phpdocumentor/reflection-docblock": "^5.3", // 5.3.0
        "phpstan/phpdoc-parser": "^1.15", // 1.23.1
        "symfony/asset": "6.3.*", // v6.3.0
        "symfony/console": "6.3.*", // v6.3.2
        "symfony/dotenv": "6.3.*", // v6.3.0
        "symfony/expression-language": "6.3.*", // v6.3.0
        "symfony/flex": "^2", // v2.3.3
        "symfony/framework-bundle": "6.3.*", // v6.3.2
        "symfony/property-access": "6.3.*", // v6.3.2
        "symfony/property-info": "6.3.*", // v6.3.0
        "symfony/runtime": "6.3.*", // v6.3.2
        "symfony/security-bundle": "6.3.*", // v6.3.3
        "symfony/serializer": "6.3.*", // v6.3.3
        "symfony/stimulus-bundle": "^2.9", // v2.10.0
        "symfony/string": "6.3.*", // v6.3.2
        "symfony/twig-bundle": "6.3.*", // v6.3.0
        "symfony/ux-react": "^2.6", // v2.10.0
        "symfony/ux-vue": "^2.7", // v2.10.0
        "symfony/validator": "6.3.*", // v6.3.2
        "symfony/webpack-encore-bundle": "^2.0", // v2.0.1
        "symfony/yaml": "6.3.*", // v6.3.3
        "symfonycasts/micro-mapper": "^0.1.0" // v0.1.1
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.4
        "mtdowling/jmespath.php": "^2.6", // 2.6.1
        "phpunit/phpunit": "^9.5", // 9.6.11
        "symfony/browser-kit": "6.3.*", // v6.3.2
        "symfony/css-selector": "6.3.*", // v6.3.2
        "symfony/debug-bundle": "6.3.*", // v6.3.2
        "symfony/maker-bundle": "^1.48", // v1.50.0
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/phpunit-bridge": "^6.2", // v6.3.2
        "symfony/stopwatch": "6.3.*", // v6.3.0
        "symfony/web-profiler-bundle": "6.3.*", // v6.3.2
        "zenstruck/browser": "^1.2", // v1.4.0
        "zenstruck/foundry": "^1.26" // v1.35.0