ADFSでSQL Server以外のDBから属性を取得する場合

#1.はじめに
以前、SAMLの調査でいろいろ試したことをサルベージして記載しています。
ニッチ過ぎて使い道がほぼない。。。

#2.DBから属性を取得する
- SQL Serverから取得する場合は、要求規則にSQL文を設定すれば取得可能
- SQL Server以外はC#で作成した自作のDLLが必要

#3.C#のDLLソース
- SQLはpostgresqlを使用
- Visual Stadioのプロジェクトで下記のDLLを参照設定する（Microsoft.～のDLLはWindows Serverある）
 - Microsoft.IdentityModel
 - Microsoft.IdentityServer.ClaimsPolicy
 - Npgsql

```C#
using Npgsql;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using RemotingMessagingAsyncResult = System.Runtime.Remoting.Messaging.AsyncResult;
using Microsoft.IdentityModel.Threading;
using Microsoft.IdentityServer.ClaimsPolicy.Engine.AttributeStore;

namespace ClassLibrary1
{
    public class CustomAttributeStore : IAttributeStore
    {
        static readonly string EVENT_LOG_NAME = "CustomAttributeStore";
        static readonly string CONFIG_KEY01 = "CONNECTION_INFO";

        NpgsqlConnection _connection = null;

        string _connInfo = null;

        delegate string[][] RunQueryDelegate(string formatQuery);

        ~CustomAttributeStore()
        {
            //EventLog.WriteEntry(EVENT_LOG_NAME, "~CustomAttributeStore", EventLogEntryType.Information, 1, 100, null);
            // コネクションのクローズ
            _connection.Close();
        }

        public void Initialize(Dictionary<string, string> config)
        {
            /*
            if (!EventLog.SourceExists(EVENT_LOG_NAME))
            {
                EventLog.CreateEventSource(EVENT_LOG_NAME, "");
            }
            EventLog.WriteEntry(EVENT_LOG_NAME, "Initialize", EventLogEntryType.Information, 1, 100, null);
            */

            if (!config.TryGetValue(CONFIG_KEY01, out _connInfo))
            {
                throw new AttributeStoreInvalidConfigurationException("DB接続情報未設定");
            }

            // コネクションのオープン
            _connection = new NpgsqlConnection(_connInfo);
            _connection.Open();
        }

        public IAsyncResult BeginExecuteQuery(string query, string[] parameters, AsyncCallback callback, object state)
        {
            //EventLog.WriteEntry(EVENT_LOG_NAME, "BeginExecuteQuery", EventLogEntryType.Information, 1, 100, null);

            if (String.IsNullOrEmpty(query))
            {
                throw new AttributeStoreQueryFormatException("クエリ未設定");
            }

            RunQueryDelegate runQueryDelegate = new RunQueryDelegate(RunQuery);
            TypedAsyncResult<string[][]> asyncResult = new TypedAsyncResult<string[][]>(callback, state);
            runQueryDelegate.BeginInvoke(string.Format(query, parameters), new AsyncCallback(AsyncQueryCallback), asyncResult);
            return asyncResult;
        }

        public string[][] EndExecuteQuery(IAsyncResult result)
        {
            //EventLog.WriteEntry(EVENT_LOG_NAME, "EndExecuteQuery", EventLogEntryType.Information, 1, 100, null);
            return TypedAsyncResult<string[][]>.End(result);
        }

        private string[][] RunQuery(string formatQuery)
        {
            //EventLog.WriteEntry(EVENT_LOG_NAME, "RunQuery", EventLogEntryType.Information, 1, 100, null);
            //EventLog.WriteEntry(EVENT_LOG_NAME, formatQuery, EventLogEntryType.Information, 1, 100, null);

            // SQL実行&結果取得
            NpgsqlCommand command = new NpgsqlCommand(formatQuery, _connection);
            String data = null;
            using (NpgsqlDataReader reader = command.ExecuteReader())
            {
                if (reader.Read())
                {
                    data = reader[0].ToString();
                }
            }

            string[][] result = { new[] { data } };
            return result;
        }

        private void AsyncQueryCallback(IAsyncResult result)
        {
            //EventLog.WriteEntry(EVENT_LOG_NAME, "AsyncQueryCallback", EventLogEntryType.Information, 1, 100, null);

            TypedAsyncResult<string[][]> asyncResult = (TypedAsyncResult<string[][]>)result.AsyncState;
            RunQueryDelegate runQueryDelegate = (RunQueryDelegate)((RemotingMessagingAsyncResult)result).AsyncDelegate;

            string[][] values = null;
            Exception originalException = null;
            try
            {
                values = runQueryDelegate.EndInvoke(result);
            }
            catch (Exception e)
            {
                originalException = e;
            }
            asyncResult.Complete(values, false, originalException);
        }
    }
}
```

#4.設定
- 作成したDLLをWindows\ADFS直下に配置する。
- 下記を参考に、要求記述を追加する。
- 
|名称|設定値|
|-------------------------|-------------------------|
|表示名|返却ID|
|要求識別子|https://test.saml.local/returnid|

- 下記を参考に、カスタム属性ストアを追加する。

|名称|設定値|
|-------------------------|-------------------------|
|表示名|test_Custom|
|カスタム属性ストアのクラス名|DLL名.クラス名,DLL名|
|オプションの初期化パラメータ 名前|CONNECTION_INFO|
|オプションの初期化パラメータ 値|Server=localhost;Port=5432;User Id=postgres;Password=password;Database=testdb|

- 下記を参考に、要求規則を追加する。

|名称|設定値|
|-------------------------|-------------------------|
|要求規則名|test|
|カスタム規則|c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"] => issue(store = "test_Custom", types = ("https://test.saml.local/returnid"), query = "select returnid1 from t_user where id = '{0}'", param = c.Value);|

- カスタム規則の設定値は下記のようになっています。

|設定値|説明|
|-------------------------|-------------------------|
|c:[Type～]|指定したURIに対応する値がcに設定される<br>上記のURLはログイン名を指す|
|store|「属性ストア」の表示名を設定する|
|types|「要求記述」で追加したURIを設定する<br>このURIがsamlの属性名として返却される|
|query|実行するSQLを設定する<br>この取得値がsamlの属性値として返却される|
|param|SQLのパラメータ<br>[c.Value]でcに設定された値を条件値として設定される|