Chaining callback with exception filter to provide authorization

44 views
Skip to first unread message

stev...@exabeam.com

unread,
Nov 1, 2017, 5:56:04 PM11/1/17
to finatra-users
First, I'm using a rather dated version of Finatra (2.2.0). Also, I'm not quite sure how to format code in this post so I apologize - please take a look at https://pastebin.com/raw/hGhejgeW

I have several endpoints already defined and I'm at the stage where I need to add in authorization. I have added a filter that extracts user permissions from the headers. I need to use this information to compare against the permissions that is unique to each route. I'm thinking I can do this by chaining the callback and with an Exception Filter.

Define an GET endpoint with Swagger documentation (see https://github.com/xiaodongw/swagger-finatra/blob/313702144bc6234823e80fb82cc7e9f8ba62966c/src/main/scala/com/twitter/finatra/http/SwaggerRouteDSL.scala):


def getWithDoc[RequestType: Manifest, ResponseType: Manifest](route: String, name: String = "", admin: Boolean = false, adminIndexInfo: Option[AdminIndexInfo] = None)
(doc: Operation => Unit)
(callback: RequestType => ResponseType): Unit = {
registerOperation(route, "get")(doc)
dsl.get(route, name, admin, adminIndexInfo)(callback)
}

An example usage:


getWithDoc("/api/hello") {
_.summary("Hello World")
} { request: HelloWorldRequest => // this request has information injected into it via filters
// stuff
}


I'd like to replace the above 'route' with MyRoute, like so:

sealed case class MyRoute(route: String, requiredPermissions: Set[String] = Set.empty)

// The only way I can think of to retrieve the request
trait FinagleHttpRequest {
def request: Request
}

case class HelloWorldRequest(@Inject override val request: Request) extends FinagleHttpRequest

case object ForbiddenRequest extends Exception

def authGetWithDoc[RequestType: Manifest, ResponseType: Manifest](route: MyRoute, name: String = "", admin: Boolean = false, adminIndexInfo: Option[AdminIndexInfo] = None)
(doc: Operation => Unit)
(callback: RequestType => ResponseType): Unit = {

val callbackWithAuth: RequestType => ResponseType = {
request: RequestType =>
val userPermissions = request.asInstanceOf[FinagleHttpRequest].request.userPermission // what was set by my filter
if isRequestAuthorized(userPermissions, route) {
callback(request)
} else {
// TODO Can't do response.forbidden because it's not of ResponseType
throw ForbiddenRequest // then use an exception filter to actually change the response to a forbidden
}
}

getWithDoc(route.route, name, admin, adminIndexInfo)(doc)(callbackWithAuth)
}

val HelloWorldRoute = MyRoute(route = "/api/hello", requiredPermissions = Set("foo", "bar"))
authGetWithDoc(HelloWorldRoute) {
_.summary("Hello World")
} { request: HelloWorldRequest =>
// stuff
}

This I think is the least invasive change and least amount of duplicated code.

I'm looking for some feedback on this approach and I'm open to any suggestions / improvements! Thanks!

stev...@exabeam.com

unread,
Nov 1, 2017, 5:59:07 PM11/1/17
to finatra-users
I should probably wrap request.asInstanceOf[FinagleHttpRequest].request.userPermission in a Try to gracefully handle programmer forgetting to use the right RequestType

Christopher Coco

unread,
Nov 5, 2017, 11:23:07 AM11/5/17
to stev...@exabeam.com, finatra-users
You probably want to look at using the Request Scope or the more recommended Request#ctx.


Thanks!
-c

On Wed, Nov 1, 2017 at 2:59 PM, <stev...@exabeam.com> wrote:
I should probably wrap request.asInstanceOf[FinagleHttpRequest].request.userPermission in a Try to gracefully handle programmer forgetting to use the right RequestType

--
You received this message because you are subscribed to the Google Groups "finatra-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to finatra-users+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

stev...@exabeam.com

unread,
Nov 6, 2017, 2:42:58 PM11/6/17
to finatra-users
Currently that's how I'm extracting permissions from headers. I set up a filter and enrich the context so that information can be available in the controller. However, to enforce the permissions, I'm not sure if a filter and context will work because it's unique to each route and request.

On Sunday, November 5, 2017 at 8:23:07 AM UTC-8, Christopher Coco wrote:
> You probably want to look at using the Request Scope or the more recommended Request#ctx.
>
>
>
>
> Thanks!
> -c
>
>
> On Wed, Nov 1, 2017 at 2:59 PM, <stev...@exabeam.com> wrote:
> I should probably wrap request.asInstanceOf[FinagleHttpRequest].request.userPermission in a Try to gracefully handle programmer forgetting to use the right RequestType
>
>
>
>
>
> --
>
> You received this message because you are subscribed to the Google Groups "finatra-users" group.
>

> To unsubscribe from this group and stop receiving emails from it, send an email to finatra-user...@googlegroups.com.

Christopher Coco

unread,
Nov 6, 2017, 10:55:29 PM11/6/17
to stev...@exabeam.com, finatra-users
If it's unique per route and request then it probably needs to be in the route callback. Trying to extend or override the finagle Request will only cause you headaches.

Thanks,
-c

To unsubscribe from this group and stop receiving emails from it, send an email to finatra-users+unsubscribe@googlegroups.com.
Message has been deleted

stev...@exabeam.com

unread,
Nov 7, 2017, 2:46:34 PM11/7/17
to finatra-users
The workflow:

As endpoints is defined, I add some permission logic before the actual callback (see callbackWithAuth). This is to reduce copying the permission checking logic into all the endpoints. I couldn't figure out how to use only a filter since each route has different permissions.

When requests comes in:

"User" Filter -> Extracts information from header and sets it in Request's context - this is normal filter usage.

The "enhanced" callback is called which has permission checking and that happens. I throw an exception since I don't know anything about the ResponseType of the route. If permissions are valid then the original callback is called.

"Exception" filter catches the forbidden request and sends a 403

--

So I'm not extending the Request, I'm just requiring that any routes with permissions have to extend FinagleHttpRequest which has the request so I can actually use it to retrieve what was set in the user filter.

Reply all
Reply to author
Forward
0 new messages