[ASP.NET Core 2.1]カスタムポリシーの作成

# はじめに

ポリシーベースの承認やその使い方については他に詳しいサイトがたくさんあるので、軽く実装例を載せてみたいと思います。

# ポリシーの定義

例として、あるタスクの作成者しかアクセスできないポリシーを作成します。

```csharp:TaskAuthorOnly.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace TestWebSystem.Policy
{
    public class TaskAuthorOnlyRequirement : IAuthorizationRequirement
    {

    }

    public class TaskAuthorOnlyHandler : AuthorizationHandler<TaskAuthorOnlyRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                       TaskAuthorOnlyRequirement requirement)
        {
            if (!context.User.HasClaim(c => c.Type == ClaimTypes.NameIdentifier))
            {
                return Task.CompletedTask;
            }

            if (context.Resource is FilterContext filterContext)
            {
                // コントローラで使用されるときはこっちが呼び出される。
                var routeValues = filterContext.RouteData.Values;
                if (((string)routeValues["controller"]) == "Tasks" && routeValues.ContainsKey("id"))
                {
                    var dbContex = ApplicationDbContext.GetDbContext();
                    var userId = context.User.FindFirst(c => c.Type == ClaimTypes.NameIdentifier).Value;
                    var taskId = (string)routeValues["id"];

                    var task = dbContex.Tasks.Find(taskId);

                    if (task != null && task.AuthorId == userId)
                    {
                        context.Succeed(requirement);
                    }
                }
            }
            else if (context.Resource is Task task)
            {
                // ビューで使用するときは、Resourceにビューモデルを与えるのでこっちが呼び出される。
                var userId = context.User.FindFirst(c => c.Type == ClaimTypes.NameIdentifier).Value;
                if (task.AuthorId == userId)
                {
                    context.Succeed(requirement);
                }
            }

            return Task.CompletedTask;
        }
    }
}
```

これをStartup.csに登録してあげます。

```csharp:Startup.cs
services.AddAuthorization(options =>
{
    options.AddPolicy("TaskAuthorOnly", policy =>
        policy.Requirements.Add(new TaskAuthorOnlyRequirement()));
});

services.AddSingleton<IAuthorizationHandler, TaskAuthorOnlyHandler>();
```

## 余談：ポリシー内でApplicationDbContextを取得するには？

独自定義のポリシー内でApplicationDbContextをDIから取得する方法が見つからなかったので、下記のようなコードを追加してお茶を濁しました。
ご存じの方がいたら教えてください...。

```csharp:Startup.cs

ApplicationDbContext.ConnectionString = Configuration.GetConnectionString("DefaultConnection");

services.AddDbContext<ApplicationDbContext>(options =>
    options
    .UseLazyLoadingProxies()
    .UseNpgsql(ApplicationDbContext.ConnectionString)
);
```

```csharp:ApplicationDbContext.cs
public static string ConnectionString { get; set; }

public static ApplicationDbContext GetDbContext()
{
    var option = new DbContextOptionsBuilder<ApplicationDbContext>()
        .UseLazyLoadingProxies()
        .UseNpgsql(ConnectionString)
        .Options;

    var context = new ApplicationDbContext(option);
    context.Database.OpenConnection();
    context.Database.EnsureCreated();

    return context;
}
```

# ポリシーの利用

## コントローラでの利用

単にアノテーションを付けるだけです。

```csharp
[Authorize(Policy = "TaskAuthorOnly")]
public async Task<IActionResult> Edit(
```

## ビューでの利用

次のような感じで利用します。

```csharp
@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

@if ((await AuthorizationService.AuthorizeAsync(User, Model, "TaskAuthorOnly")).Succeeded)
{

}
```

# おわり

以上、カスタムポリシーを定義して利用する方法でした。