Chapter 06

Compose関数で提供機能を構成する

abcb2
abcb2
2023.02.03に更新

composeパッケージの役割

composeパッケージ(compose/compose.go)はフレームワークユーザーがOAuth2Providerインターフェースを通してアクセスできる機能を決定するためのパッケージです。

なぜConfigのメンバーのハンドラーはスライスなのか

Compose関数とComposeAllEnable関数
type Factory func(config fosite.Configurator, storage interface{}, strategy interface{}) interface{}

func Compose(config *fosite.Config, storage interface{}, strategy interface{}, factories ...Factory) fosite.OAuth2Provider {
	f := fosite.NewOAuth2Provider(storage.(fosite.Storage), config)
	for _, factory := range factories {
		res := factory(config, storage, strategy)
		if ah, ok := res.(fosite.AuthorizeEndpointHandler); ok {
			config.AuthorizeEndpointHandlers.Append(ah)
		}
		if th, ok := res.(fosite.TokenEndpointHandler); ok {
			config.TokenEndpointHandlers.Append(th)
		}
		if tv, ok := res.(fosite.TokenIntrospector); ok {
			config.TokenIntrospectionHandlers.Append(tv)
		}
		if rh, ok := res.(fosite.RevocationHandler); ok {
			config.RevocationHandlers.Append(rh)
		}
		if ph, ok := res.(fosite.PushedAuthorizeEndpointHandler); ok {
			config.PushedAuthorizeEndpointHandlers.Append(ph)
		}
	}

	return f
}

func ComposeAllEnabled(config *fosite.Config, storage interface{}, key interface{}) fosite.OAuth2Provider {
	keyGetter := func(context.Context) (interface{}, error) {
		return key, nil
	}
	return Compose(
		config,
		storage,
		&CommonStrategy{
			CoreStrategy:               NewOAuth2HMACStrategy(config),
			OpenIDConnectTokenStrategy: NewOpenIDConnectStrategy(keyGetter, config),
			Signer:                     &jwt.DefaultSigner{GetPrivateKey: keyGetter},
		},
		OAuth2AuthorizeExplicitFactory,
		OAuth2AuthorizeImplicitFactory,
		OAuth2ClientCredentialsGrantFactory,
		OAuth2RefreshTokenGrantFactory,
		OAuth2ResourceOwnerPasswordCredentialsFactory,
		RFC7523AssertionGrantFactory,

		OpenIDConnectExplicitFactory,
		OpenIDConnectImplicitFactory,
		OpenIDConnectHybridFactory,
		OpenIDConnectRefreshFactory,

		OAuth2TokenIntrospectionFactory,
		OAuth2TokenRevocationFactory,

		OAuth2PKCEFactory,
		PushedAuthorizeHandlerFactory,
	)
}

Compose関数で可変長引数(variadic)のfactoriesに渡されたFactoryからhandler.gointrospect.goに定義されたハンドラーの具象型を生成し、Config型のメンバーのハンドラーに機能を搭載していきます。

なぜ各エンドポイントのハンドラーはスライスになっているのでしょうか。

/authorize/token/introspect/revokeなどといったHTTPエンドポイントは一つしか定義できないじゃないか、と思ったりもするかもしれません。

これは各HTTPエンドポイントで様々な仕様の認可リクエストや認可レスポンス、トークンリクエストやトークンレスポンスに対応する必要があるためだから、と思われます。

OAuth2だけでも/authorizeエンドポイントは様々なresponse_typeに対応する必要があり、さらにはOIDC対応するとなると条件分岐は複雑になります。

そのためStrategyパターンを用いて条件に合致するハンドラーで処理するという方式を取っています。

Strategyはどこで使われているか

Config型のメンバーとなっているハンドラーinterface(のスライス)には先述の通りCompose関数で具象型(strategy)が追加されます。

ではその具象型はどこから使われているかというと、それぞれ以下の通りです。

各ハンドラーに追加された具象型であるstrategyを使ってリクエスト・レスポンスを処理していきます。

パラメータを渡してそれぞれのstrategyは処理をし、

  • そのstrategyが処理担当で正常処理の場合はそのままloop継続します
    • パラメータは参照渡しになっており、処理結果等はそこに書かれます
  • そのstrategyが処理担当で処理エラーの場合はエラーを返します
  • そのstrategyが処理担当外の場合はnilを返します

AuthorizeEndpointHandler

authorize_response_writer.goの42行目

	for _, h := range f.Config.GetAuthorizeEndpointHandlers(ctx) {
		if err := h.HandleAuthorizeEndpointRequest(ctx, ar, resp); err != nil {
			return nil, err
		}
	}

TokenEndpointHandler

access_response_writer.goの41行目

	for _, tk = range f.Config.GetTokenEndpointHandlers(ctx) {
		if err = tk.PopulateTokenEndpointResponse(ctx, requester, response); err == nil {
			// do nothing
		} else if errors.Is(err, ErrUnknownRequest) {
			// do nothing
		} else if err != nil {
			return nil, err
		}
	}

RevocationHandler

revoke_handler.goの72行目

	for _, loader := range f.Config.GetRevocationHandlers(ctx) {
		if err := loader.RevokeToken(ctx, token, tokenTypeHint, client); err == nil {
			found = true
		} else if errors.Is(err, ErrUnknownRequest) {
			// do nothing
		} else if err != nil {
			return err
		}
	}

PushedAuthorizeEndpointHandler

pushed_authorize_response_wirter.goの29行目

	for _, h := range handlersProvider.GetPushedAuthorizeEndpointHandlers(ctx) {
		if err := h.HandlePushedAuthorizeEndpointRequest(ctx, ar, resp); err != nil {
			return nil, err
		}
	}

TokenIntrospector

introspect.goの62行目

	for _, validator := range f.Config.GetTokenIntrospectionHandlers(ctx) {
		tu, err := validator.IntrospectToken(ctx, token, tokenUse, ar, scopes)
		if err == nil {
			found = true
			foundTokenUse = tu
		} else if errors.Is(err, ErrUnknownRequest) {
			// do nothing
		} else {
			rfcerr := ErrorToRFC6749Error(err)
			return "", nil, errorsx.WithStack(rfcerr)
		}
	}

Strategyのロジックはどこにあるのか

handlerディレクトリ以下に存在します。

% tree ./handler -L 1
./handler
├── oauth2
├── openid
├── par
├── pkce
└── rfc7523