Nesting authorize directives in spray - scala

It seems it is impossible to nest authorized directives in spray due to this line: https://github.com/spray/spray/blob/76ab89c25ce6d4ff2c4b286efcc92ee02ced6eff/spray-routing/src/main/scala/spray/routing/directives/SecurityDirectives.scala#L55
I'm referring to doing things like this:
val route = {
...
authorize(userIsAdmin) {
path("generic" / "admin" / "stuff") { ... } ~
path("users" / Segment) { u =>
authorize(canModifyUser) {
...
}
} ~
path("quotas") {
authorize(canModifyQuotas) {
...
}
}
}
}
One could of course refactor this to include userIsAdmin into the canModifyUser and canModifyQuota checks, but with orthogonal access rights, that could get out of hand fast.
What is the reason for that line? It doesn't seem logical to me why we are cancelling any further authorization failures.
Full disclosure: The route will actually get rejected if one of the nested checks fail, but it will give a 404 error (EmptyRejection) instead of the expected AuthorizationFailedRejection.

Related

Akka: Cancel Routing Due To Incorrect Query Parameters

So I have a route structure something like this
pathPrexix("root"){
concat {
path("path") {
get {
parameters("someId".as[String], 'fixedValue ! "requiredValue") { params =>
}
}
},
path(Segment) { extractedValue =>
.....
}
}
}
If the user ends a request to the /root/path endpoint with the incorrect query parameters (either someId missing or fixedValue not equal to value) then the request will be routed further on to the next route, root/Segment. extractedValue would in this case be path which would fail and send the user back error handled by the second route.
The preferred behaviour would be to tell the user that they either missed a query parameters or that the query parameter must be one of the given values. Is there any way to make sure that happen?
If I move the second path above the first, it will capture all requests sent.
You just need to complete with an appropriate error code if the get does not match:
path("path") {
concat(
get {
parameters("someId".as[String], 'fixedValue ! "requiredValue") { params =>
}
},
complete(StatusCodes.NotFound)
)
},
You could put additional information in the reply message, but it would be non-standard and therefore would require the client to be aware of it.

Nesting CRUD paths in akka-http directives

I am just starting with Scala and Akka. I am writing a small REST service. I am trying to create following paths:
GET localhost:8080/api/my-service (return collection of resources)
GET localhost:8080/api/my-service/6 (return resource with specified id)
POST localhost:8080/api/my-service (create new resource with data in body)
UPDATE localhost:8080/api/my-service/4 (update requested resource with data in body)
DELETE localhost:8080/api/my-service/5 (deletes resource with specified id)
I have managed to create nested paths, but however only GET for fetching the collections (first example in bullet points) is returning the results. Example GET with id in the path is returning The requested resource could not be found. I have tried many different variations, but nothing seems to work.
Here is excerpt of my routes:
val routes = {
logRequestResult("my-service-api") {
pathPrefix("api") {
path("my-service") {
get {
pathEndOrSingleSlash {
complete("end of the story")
} ~
pathPrefix(IntNumber) { id =>
complete("Id: %d".format(id))
}
} ~
(post & pathEndOrSingleSlash & entity(as[MyResource])) { myResourceBody =>
// do something ...
}
}
}
}
}
I have already checked many solutions on the web and some tests from Akka itself, but somehow I am missing here something.
I found a solution. Problem was in path("my-service") part. I've changed it to pathPrefix("my-service") and now both GET paths are working.
So the correct route setup should look something like this.
val routes = {
logRequestResult("my-service-api") {
pathPrefix("api") {
pathPrefix("my-service") {
get {
pathEndOrSingleSlash {
complete("end of the story")
} ~
pathPrefix(IntNumber) { id =>
complete("Id: %d".format(id))
}
} ~
(post & pathEndOrSingleSlash & entity(as[MyResource])) { myResourceBody =>
// do something ...
}
}
}
}
}

Spray routing works for single slash but nothing else

So i have asked about this before and have changed a lot of code around.
Spray Routing Doesn't match anything
Now I am executing my functions that return HTTPresponses insided detach() blocks so that i dont block. These then are completed and return to the client, but I still can't seem to get my routing to work.
In my tests, a request to a single slash works fine, but anything else, such as this create user path shown below fails. I can't seem to figure out why, and spray routing uses so many constructs I'm having a hard time figuring out how the system works well enough to find out whats happening.
I tried inserting logRequest blocks around certain paths thinking that might show me whats happening, but none of them seem to get hit. Any help would be greatly appreciated.
val route: Route = {
host("fakebook.com", "www.fakebook.com") {
pathSingleSlash {
complete("pong")
} ~
pathPrefix("users") { req =>
path("newuser") {
put {
detach() {
complete(genericPut(CreateUser(req.request)))
}
}
} ~
... rest of routing
And here is what my scalatests look like, the simple Put passes, but the put with newuser doesn't
val createUserSuccessRequest = Put(Uri("http://www.fakebook.com/users/newuser") withQuery(F_User.lastNameString -> "Doe", F_User.firstNameString -> "John", F_User.bioString -> "i like foobar",
F_User.ageString -> "42", F_User.dobString -> dateFormatter.format(new Date(1970 - 1900, 5, 7))))
"The FakeBook route" when {
"When sending a single slash request" should {
"respond with a simple pong" in {
Get() ~> logRequestResponse("plain get final request and response")(sealRoute(route)) ~> check {
assert(responseAs[String] == "pong")
}
}
}
"Running route to create a user with PUT" should {
"forward a message to the backbone to create a new user" in {
createUserSuccessRequest ~> logRequest("create user final request and response"){sealRoute(route)} ~> check {
expectMsg(CreateUser(createUserSuccessRequest))
}
}
}
}
For anyone else trying to solve this issue:
a lot of these directives actually DONT extract anything, so having the lambda inputs i have like req => and req2 => will not work.
It turns out, spray routing is designed so that you never have to touch the RequestContext as I have done with my functions (which is why I try to access it). They extract only the useful data. Rather than do things as I should and change my function signatures, i am going to (for now) do a hotfix that has worked.
if you absolutely must have the requestcontext, so long as you don't break it somehow, you can extract it by making your own extraction directive like so
val extractRequestContext = extract(x => x) and wrap your code in that
I did this
path("somepath") {
detach() {
extractRequestContext { request => complete(someFunc(request)) }
}
}
In the future I should learn to use the DSL more correctly and extract what I need from the request context using directives and pass THOSE to the functions

How to use string directive extractor in a nested route in Spray

Answering my own question here because this took me over a day to figure out and it was a really simple gotcha that I think others might run into.
While working on a RESTful-esk service I'm creating using spray, I wanted to match routes that had an alphanumeric id as part of the path. This is what I originally started out with:
case class APIPagination(val page: Option[Int], val perPage: Option[Int])
get {
pathPrefix("v0" / "things") {
pathEndOrSingleSlash {
parameters('page ? 0, 'perPage ? 10).as(APIPagination) { pagination =>
respondWithMediaType(`application/json`) {
complete("things")
}
}
} ~
path(Segment) { thingStringId =>
pathEnd {
complete(thingStringId)
} ~
pathSuffix("subthings") {
pathEndOrSingleSlash {
complete("subthings")
}
} ~
pathSuffix("othersubthings") {
pathEndOrSingleSlash {
complete("othersubthings")
}
}
}
}
} ~ //more routes...
And this has no issue compiling, however when using scalatest to verify that the routing structure is correct, I was surprised to find this type of output:
"ThingServiceTests:"
"Thing Service Routes should not reject:"
- should /v0/things
- should /v0/things/thingId
- should /v0/things/thingId/subthings *** FAILED ***
Request was not handled (RouteTest.scala:64)
- should /v0/things/thingId/othersubthings *** FAILED ***
Request was not handled (RouteTest.scala:64)
What's wrong with my route?
I looked at a number of resources, like this SO Question and this blog post but couldn't seem to find anything about using string Id's as a toplevel part of a route structure. I looked through the spray scaladoc as well as beat my head against the documentation on Path matchers for a while before spotting this important test (duplicated below):
"pathPrefix(Segment)" should {
val test = testFor(pathPrefix(Segment) { echoCaptureAndUnmatchedPath })
"accept [/abc]" in test("abc:")
"accept [/abc/]" in test("abc:/")
"accept [/abc/def]" in test("abc:/def")
"reject [/]" in test()
}
This tipped me off to a couple things. That I should try out using pathPrefix instead of path. So I changed my route to look like this:
get {
pathPrefix("v0" / "things") {
pathEndOrSingleSlash {
parameters('page ? 0, 'perPage ? 10).as(APIPagination) { pagination =>
respondWithMediaType(`application/json`) {
listThings(pagination)
}
}
} ~
pathPrefix(Segment) { thingStringId =>
pathEnd {
showThing(thingStringId)
} ~
pathPrefix("subthings") {
pathEndOrSingleSlash {
listSubThingsForMasterThing(thingStringId)
}
} ~
pathPrefix("othersubthings") {
pathEndOrSingleSlash {
listOtherSubThingsForMasterThing(thingStringId)
}
}
}
}
} ~
And was happy to get all my tests passing and the route structure working properly. then I update it to use a Regex matcher instead:
pathPrefix(new scala.util.matching.Regex("[a-zA-Z0-9]*")) { thingStringId =>
and decided to post on SO for anyone else who runs into a similar issue. As jrudolph points out in the comments, this is because Segment is expecting to match <Segment><PathEnd> and not to be used in the middle of a path. Which is what pathPrefix is more useful for

Routing design in Spray

I'm trying to make a rest service for profile management. So i have the following uri for profiles:
host/profile/id123123/:action
But there are different profile types, for different users with different dashboards, so i want to extract profileType and id as the top path and work with different actions under this. I've tried to write it in DRY way:
path(Segment / "id" ~ Segment) { (profileType, id) ⇒
get {
profileType match {
case "admin" ⇒ loadProfilePage[Admin](id)
}
} ~
path("update") {
complete("Profile updated")
}
}
But if i type the following in the address bar:
localhost/admin/id123123/update
It throws server exception. What's the problem?
You have wrong routing structure. If you want have different logic for different paths under some common one, in your case path(Segment / "id" ~ Segment), then you need to use pathPrefix directive. Then the right route would look like this:
pathPrefix(Segment / "id" ~ Segment) { (profileType, id) ⇒
path("") {
get {
profileType match {
case "admin" ⇒ loadProfilePage[Admin](id)
}
}
} ~
path("update") {
complete("Profile updated")
}
}
But still early extraction not a very good thing, if you have a very complex route structure this will slow down the overall performance, not much, but your inner route, after extraction point, gonna be evaluated dynamically.
Update
I just though of a little optimisation. I think that you are going to have not only update path bu others too. So in this case that would be cleaner to make the following route:
pathPrefix(Segment / "id" ~ Segment) { (profileType, id) ⇒
(get | put) {
profileType match {
case "user" ⇒
path("")(loadProfilePage[User](id)) ~
path("update")(updateProfile[User](id)) ~
path("delete")(deleteProfile[User](id)
}
}
}
}
Still that's not a perfect way, i would generalize it further, cause the only thing, as i understand, that would change is profileType