Skip to content

Commit 87e63d6

Browse files
committed
CSRF initial implementation in middelware and routing modules
1 parent c887f90 commit 87e63d6

3 files changed

Lines changed: 64 additions & 20 deletions

File tree

src/Oxpecker/HttpContextExtensions.fs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ open System
44
open System.Collections.Generic
55
open System.IO
66
open System.Runtime.CompilerServices
7+
open System.Runtime.ExceptionServices
78
open System.Text
89
open System.Threading.Tasks
10+
open Microsoft.AspNetCore.Antiforgery
911
open Microsoft.AspNetCore.Hosting
1012
open Microsoft.AspNetCore.Http
1113
open Microsoft.AspNetCore.Http.Extensions
@@ -366,6 +368,19 @@ type HttpContextExtensions() =
366368
[<Extension>]
367369
static member BindForm<'T>(ctx: HttpContext) =
368370
let binder = ctx.GetModelBinder()
371+
// CSRF check
372+
let feature = ctx.Features.Get<IAntiforgeryValidationFeature>()
373+
match feature with
374+
| null -> ()
375+
| f ->
376+
match f.IsValid with
377+
| true -> ()
378+
| false ->
379+
match f.Error with
380+
| null -> ()
381+
| err ->
382+
ctx.Response.StatusCode <- StatusCodes.Status403Forbidden
383+
ExceptionDispatchInfo.Throw err
369384
task {
370385
try
371386
let! form = ctx.Request.ReadFormAsync()

src/Oxpecker/Middleware.fs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
module Oxpecker.Middleware
33

44
open System.Runtime.CompilerServices
5+
open Microsoft.AspNetCore.Antiforgery
56
open Microsoft.AspNetCore.Builder
67
open Microsoft.AspNetCore.Hosting
78
open Microsoft.Extensions.DependencyInjection
@@ -15,14 +16,22 @@ type ApplicationBuilderExtensions() =
1516
/// </summary>
1617
[<Extension>]
1718
static member UseOxpecker(builder: IApplicationBuilder, endpoints: Endpoint seq) =
18-
builder.UseEndpoints(fun builder -> builder.MapOxpeckerEndpoints endpoints)
19+
let addAntiforgery =
20+
match builder.ApplicationServices.GetService(typeof<IAntiforgery>) with
21+
| null -> false
22+
| _ -> true
23+
builder.UseEndpoints(_.MapOxpeckerEndpoints(endpoints, addAntiforgery))
1924

2025
/// <summary>
2126
/// Uses ASP.NET Core's Endpoint Routing middleware to register single Oxpecker endpoint.
2227
/// </summary>
2328
[<Extension>]
2429
static member UseOxpecker(builder: IApplicationBuilder, endpoint: Endpoint) =
25-
builder.UseEndpoints(fun builder -> builder.MapOxpeckerEndpoint endpoint)
30+
let addAntiforgery =
31+
match builder.ApplicationServices.GetService(typeof<IAntiforgery>) with
32+
| null -> false
33+
| _ -> true
34+
builder.UseEndpoints(_.MapOxpeckerEndpoint(endpoint, addAntiforgery))
2635

2736
type ServiceCollectionExtensions() =
2837
/// <summary>

src/Oxpecker/Routing.fs

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ open System.Reflection
55
open System.Runtime.CompilerServices
66
open System.Text.RegularExpressions
77
open System.Threading.Tasks
8+
open Microsoft.AspNetCore.Antiforgery
89
open Microsoft.AspNetCore.Http
910
open Microsoft.AspNetCore.Routing
1011
open Microsoft.AspNetCore.Builder
@@ -13,7 +14,7 @@ open Oxpecker
1314

1415
[<AutoOpen>]
1516
module RoutingTypes =
16-
[<Struct>]
17+
1718
type HttpVerb =
1819
| GET
1920
| POST
@@ -256,13 +257,29 @@ type EndpointRouteBuilderExtensions() =
256257
verb: HttpVerbs,
257258
routeTemplate: RouteTemplate,
258259
requestDelegate: RequestDelegate,
259-
configure: ConfigureEndpoint
260+
configure: ConfigureEndpoint,
261+
addAntiforgery: bool
260262
) =
261263
match verb with
262-
| Any -> builder.Map(routeTemplate, requestDelegate) |> configure
264+
| Any ->
265+
builder.Map(routeTemplate, requestDelegate)
266+
|>
267+
if addAntiforgery then
268+
_.WithMetadata(RequireAntiforgeryTokenAttribute()) >> configure
269+
else
270+
configure
263271
| Verbs verbs ->
264272
builder.MapMethods(routeTemplate, verbs |> Seq.map string, requestDelegate)
265-
|> configure
273+
|>
274+
if addAntiforgery then
275+
let canHaveForm = verbs |> Seq.exists (
276+
fun verb -> verb = HttpVerb.POST || verb = HttpVerb.PUT || verb = HttpVerb.PATCH)
277+
if canHaveForm then
278+
_.WithMetadata(RequireAntiforgeryTokenAttribute()) >> configure
279+
else
280+
configure
281+
else
282+
configure
266283
|> ignore
267284

268285
[<Extension>]
@@ -271,38 +288,41 @@ type EndpointRouteBuilderExtensions() =
271288
builder: IEndpointRouteBuilder,
272289
parentTemplate: RouteTemplate,
273290
endpoints: Endpoint seq,
274-
parentConfigure: ConfigureEndpoint
291+
parentConfigure: ConfigureEndpoint,
292+
addAntiforgery: bool
275293
) =
276294
let groupBuilder = builder.MapGroup(parentTemplate)
277295
for endpoint in endpoints do
278296
match endpoint with
279297
| SimpleEndpoint(verb, template, handler, configure) ->
280-
groupBuilder.MapSingleEndpoint(verb, template, handler, parentConfigure >> configure)
298+
groupBuilder.MapSingleEndpoint(verb, template, handler, parentConfigure >> configure, addAntiforgery)
281299
| NestedEndpoint(template, endpoints, configure) ->
282-
groupBuilder.MapNestedEndpoint(template, endpoints, parentConfigure >> configure)
283-
| MultiEndpoint endpoints -> groupBuilder.MapMultiEndpoint(endpoints, parentConfigure)
300+
groupBuilder.MapNestedEndpoint(template, endpoints, parentConfigure >> configure, addAntiforgery)
301+
| MultiEndpoint endpoints -> groupBuilder.MapMultiEndpoint(endpoints, parentConfigure, addAntiforgery)
284302

285303
[<Extension>]
286304
static member private MapMultiEndpoint
287-
(builder: IEndpointRouteBuilder, endpoints: Endpoint seq, parentConfigure: ConfigureEndpoint)
305+
(builder: IEndpointRouteBuilder, endpoints: Endpoint seq, parentConfigure: ConfigureEndpoint,
306+
addAntiforgery: bool)
288307
=
289308
for endpoint in endpoints do
290309
match endpoint with
291310
| SimpleEndpoint(verb, template, handler, configure) ->
292-
builder.MapSingleEndpoint(verb, template, handler, parentConfigure >> configure)
311+
builder.MapSingleEndpoint(verb, template, handler, parentConfigure >> configure, addAntiforgery)
293312
| NestedEndpoint(template, endpoints, configure) ->
294-
builder.MapNestedEndpoint(template, endpoints, parentConfigure >> configure)
295-
| MultiEndpoint endpoints -> builder.MapMultiEndpoint(endpoints, parentConfigure)
313+
builder.MapNestedEndpoint(template, endpoints, parentConfigure >> configure, addAntiforgery)
314+
| MultiEndpoint endpoints -> builder.MapMultiEndpoint(endpoints, parentConfigure, addAntiforgery)
296315

297316
[<Extension>]
298-
static member MapOxpeckerEndpoint(builder: IEndpointRouteBuilder, endpoint: Endpoint) =
317+
static member internal MapOxpeckerEndpoint(builder: IEndpointRouteBuilder, endpoint: Endpoint, addAntiforgery: bool) =
299318
match endpoint with
300319
| SimpleEndpoint(verb, template, handler, configure) ->
301-
builder.MapSingleEndpoint(verb, template, handler, configure)
302-
| NestedEndpoint(template, endpoints, configure) -> builder.MapNestedEndpoint(template, endpoints, configure)
303-
| MultiEndpoint endpoints -> builder.MapOxpeckerEndpoints endpoints
320+
builder.MapSingleEndpoint(verb, template, handler, configure, addAntiforgery)
321+
| NestedEndpoint(template, endpoints, configure) ->
322+
builder.MapNestedEndpoint(template, endpoints, configure, addAntiforgery)
323+
| MultiEndpoint endpoints -> builder.MapOxpeckerEndpoints(endpoints, addAntiforgery)
304324

305325
[<Extension>]
306-
static member MapOxpeckerEndpoints(builder: IEndpointRouteBuilder, endpoints: Endpoint seq) =
326+
static member internal MapOxpeckerEndpoints(builder: IEndpointRouteBuilder, endpoints: Endpoint seq, addAntiforgery: bool) =
307327
for endpoint in endpoints do
308-
builder.MapOxpeckerEndpoint(endpoint)
328+
builder.MapOxpeckerEndpoint(endpoint, addAntiforgery)

0 commit comments

Comments
 (0)