OAuthアクセストークンを使ってMicrosoft Azureにアクセスする

Posted: , Modified:   Go Azure OAuth Swagger Qiita

本稿は Qiita 投稿記事 のバックアップです.

概要

English

Microsoft Azureを利用するデスクトップアプリのデバイス認証にて取得したアクセストークンを, Go から Microsoft Azure を利用するにて紹介した Swagger で生成したクライアントから利用する方法. API によって微妙に関数の形が変わるため,生成されたソースコードを見ながらどこでトークンを渡せるのか調べる必要があるが,おおよそ次の二種類に分かれると思われる.

メソッドが認証情報を受け取る場合

最も簡単なのは,認証情報を受け取るタイプのメソッドが生成された場合である. 例えば,次のようにして生成できる 2016-09-01 版の Resource API

$ swagger generate client \
  -f https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master/arm-resources/resources/2016-09-01/swagger/resources.json \
  -t resource

では,リソースグループの作成または更新を行う関数が次のように定義されている.

func (a *Client) ResourceGroupsCreateOrUpdate(
  params *ResourceGroupsCreateOrUpdateParams,
  authInfo runtime.ClientAuthInfoWriter
) (*ResourceGroupsCreateOrUpdateOK, *ResourceGroupsCreateOrUpdateCreated, error)

この runtime.ClientAuthInfoWriter型 は,go-openapi/runtime ライブラリにて

import 	"github.com/go-openapi/strfmt"

type ClientAuthInfoWriter interface {
  AuthenticateRequest(ClientRequest, strfmt.Registry) error
}

と定義されているのだが,go-openapi/runtime/client に OAuth アクセストークン (BearerToken) から ClientAuthInfoWriter を生成する BearerToken 関数が用意されている.

token にアクセストークンが入っているのであれば,

authInfo = httptransport.BearerToken(token)

で良い.

なお,client という名のパッケージがたくさん出てくるため,go-openapi/runtime/client には httptransport という別名を与えることが多いらしい.

実際に,ResourceGroupsCreateOrUpdate を呼び出すコードは次のようになる.

import (
  // Swagger で生成した resource の中にあるパッケージ群
  // (ディレクトリは生成した環境によって異なる)
  "github.com/jkawamoto/roadie/cloud/azure/resource/client"
  "github.com/jkawamoto/roadie/cloud/azure/resource/client/resource_groups"
  "github.com/jkawamoto/roadie/cloud/azure/resource/models"

  httptransport "github.com/go-openapi/runtime/client"
  "github.com/go-openapi/strfmt"
)

const (
  // 生成した API のバージョン
  ResourceAPIVersion = "2016-09-01"
)

// name は作成するリソースグループの名前
func CreateOrUpdate(ctx context.Context, subscriptionID, name, token string) error{

  // Resource API にアクセスするためのクライアントを作成
  cli := client.NewHTTPClient(strfmt.NewFormats())

  // リソースグループを作成するリージョン
  location := "westus2"

  // cli は API グループごとのクライアントを持っている.
  // 今回は ResourceGroups に関係するクライアントを使用.
  created, creating, err := cli.ResourceGroups.ResourceGroupsCreateOrUpdate(
    resource_groups.NewResourceGroupsCreateOrUpdateParamsWithContext(ctx).
      WithAPIVersion(ResourceAPIVersion).
      WithSubscriptionID(subscriptionID).
      WithResourceGroupName(name).
      WithParameters(&models.ResourceGroup{
        Location: &location,
      }), httptransport.BearerToken(token))

  if err != nil {
    return err
  }

  // リソースグルプが作成されて関数から返ってきた場合,created が non nil に,
  // 非同期で作成中の状態で返ってきた場合は creating が non nil になる.

  // 以降略

}

ResourceGroupsCreateOrUpdateParams の構築部分がごちゃごちゃしているが,大したことはしていない.

メソッドが認証情報を受け取らない場合

先ほどとは異なり,各メソッドが認証情報を受け取らない場合,もっと低レベルなところをいじる必要がある. 具体的には,client.NewHTTPClient(strfmt.NewFormats()) が返す API のクライアントが持っている, Transport.DefaultAuthentication 要素に BearerToken 関数が返す runtime.ClientAuthInfoWriter を設定する.

// API にアクセスするためのクライアントを作成
cli := client.NewHTTPClient(strfmt.NewFormats())

// Transport を *httptransport.Runtime にキャストして ClientAuthInfoWriter を設定
switch transport := cli.Transport.(type) {
case *httptransport.Runtime:

  transport.DefaultAuthentication = httptransport.BearerToken(token)

  // 子クライアントからも認証情報にアクセスできるよう設定
  cli.ResourceGroups.SetTransport(transport)

}

注意点は client.NewHTTPClient が返すクライアントの Transport だけではなく, このクライアントが持っている子クライアントの SetTranport を呼ぶ必要があることである.

ちなみに,Resource API の場合,このクライアントは,

type ResourceManagementClient struct {
  DeploymentOperations *deployment_operations.Client

  Deployments *deployments.Client

  Providers *providers.Client

  ResourceGroups *resource_groups.Client

  Resources *resources.Client

  Tags *tags.Client

  Transport runtime.ClientTransport
}

と定義されている.今回は,ResourceGroup の Transport を更新したが,他のクライアントを使用する場合は同様に更新する必要がある.