2010年9月24日金曜日

FIM2010 Google Appsへのプロビジョニング その2

前回に引き続きGoogle Appsへのプロビジョニングについてです。
今回は実際の動作およびソースコードを紹介します。

■試す
では、早速動作を確認してみます。
今回は以下のような構成です。

















CSVファイルから名前、姓、名を読み取り、Active DirectoryおよびGoogle Apps上にアカウントを作成します。
ポイントは、Google AppsのログオンID+ドメイン名をActive Directoryのメールアドレス属性にセットすることです。もちろんAD FS2.0の属性マッピングで同様の設定を行うことも可能ですが、ここではFIM2010側で実施しました。

この設定を行うことにより、AD FS2.0は属性ストアであるActive Directoryのメールアドレス属性から取得した値をSAMLのNameIdentifier属性としてGoogle Appsへ渡してシングル・サインオンが成立します。














実際にCSVにユーザ情報を記載して、以下の手順で実行プロファイルを実行します。
CSV MA
 Import
 Synchronization
AD MA
 Synchronization
 Export
GoogleApps MA
 Synchronization
 Export

実行後、Google Appsの管理画面を見ると確かにユーザが作成されていることがわかります。



















尚、Google Appsへのプロビジョニングを行う上で何点か注意点があります。(Google Appsの仕様)
・購入ユーザ数を超えてユーザを作成することはできない
 当たり前ですが、サービスを契約しているユーザ数以上はプロビジョニングできません。無理やりプロビジョニングしようとするとDomainUserLimitExceededという例外が発生します。
・削除したユーザと同じユーザ名のユーザは削除後5日間は作成できない
 ユーザの再作成を5日以内に実行するとUserDeletedRecentlyという例外が発生します。今回作成したMAには入れていませんが、削除ではなくサスペンドという対応にした方が良いかもしれません。(ユーザ数の増加になるので課金が増えますが)


また、プロビジョニングとは直接関係ありませんが実環境で使うためには以下の点にも留意が必要です。
Google Appsなどのクラウド上のサービスは性質上インターネットに接続できる環境であれば直接ログオンしようと思えばログオンできてしまいます。
ただ、実際に企業から使おうと思うと自宅PCから制限なくアクセスさせてしまうのは避けたいところなので、一般的に以下のような手段をとります。
・特定のネットワークからしかサービスに接続できなくする(サービス側が対応している必要あり)
・認証システムを社内に配置し、そのシステムにアクセスできるネットワークに接続できないと認証が出来ずサービスが利用できなくする


今回の場合、認証システムを外だしするという形をとりますが、Google Appsの場合例えばGmailに直接接続する場合は外部認証システムに強制リダイレクトする、という形が取れますが、管理コンソール(http://www.google.com/a/yourdomain.com/)経由のアクセスだとGoogle Apps自身が持っている認証システムでの認証となるので、パスワードさえ合致すればインターネットからでも接続できてしまいます。
これを防ぐために非常にアナログな手段ですがGoogle Appsへプロビジョニングを行う際に設定するユーザのパスワードはランダム文字列にし、且つ定期的に自動変更されるような仕組みを作ります。


■今回のソースコード

最後に今回作成したMAのソースコードを張り付けておきます。(sourceforge.netからダウンロードできるものと同じです)

メインモジュール

using System;
using System.Xml;
using System.Text;
using System.Collections.Specialized;
using Microsoft.MetadirectoryServices;
using Google.GData.Apps;
using Google.GData.Client;
using Google.GData.Extensions;
namespace Miis_CallExport
{
  public class MACallExport : 
    IMAExtensibleFileImport,
    IMAExtensibleCallExport
  {
    AppsService service;
    lib lb;
    Boolean m_isInitialized;
    // Constructor
    public MACallExport()
    {
      lb = new lib();
      m_isInitialized = true;
    }
    // Deconstructor
    ~MACallExport()
    {
      lb.Cleanup(false);
      m_isInitialized = false;
    }
    public void GenerateImportFile(
      string           filename,
      string           connectTo,
      string           user,
      string           password,
      ConfigParameterCollection  configParameters,
      bool            fullImport,
      TypeDescriptionCollection  types,
      ref string         customData
      )
    {
      //
      // TODO: Remove this throw statement if you implement this method
      //
      throw new EntryPointNotImplementedException();
    }
    public void BeginExport(
      string           connectTo,
      string           user,
      string           password,
      ConfigParameterCollection  configParameters,
      TypeDescriptionCollection  types
      )
    {
      service = new AppsService(connectTo, user, password);
#if DEBUG
      lb.Logging("BeginExport", "000");
      lb.Logging("connectTo : " + connectTo, "000");
      lb.Logging("user : " + user, "000");
      lb.Logging("password : " + password, "000");
#endif
    }
    public void ExportEntry(
      ModificationType  modificationType,
      string[]      changedAttributes,
      CSEntry       csentry
      )
    {
#if DEBUG
      lb.Logging("ExportEntry", "000");
      lb.Logging("modificationType : " + modificationType, "000");
      for (int i = 0; i < user =" service.RetrieveUser(csentry[" familyname =" csentry[" givenname =" csentry[" password =" csentry[">





ライブラリモジュール(エラー処理など)

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.MetadirectoryServices;
namespace Miis_CallExport
{
  class lib
  {
    // Google Apps API ErrorCode
    const int UnknownError = 1000;
    const int UserDeletedRecently = 1100;
    const int UserSuspended = 1101;
    const int DomainUserLimitExceeded = 1200;
    const int DomainAliasLimitExceeded = 1201;
    const int DomainSuspended = 1202;
    const int DomainFeatureUnavailable = 1203;
    const int EntityExists = 1300;
    const int EntityDoesNotExist = 1301;
    const int EntityNameIsReserved = 1302;
    const int EntityNameNotValid = 1303;
    const int InvalidGivenName = 1400;
    const int InvalidFamilyName = 1401;
    const int InvalidPassword = 1402;
    const int InvalidUsername = 1403;
    const int InvalidHashFunctionName = 1404;
    const int InvalidHashDigestLength = 1405;
    const int InvalidEmailAddress = 1406;
    const int InvalidQueryParameterValue = 1407;
    const int TooManyRecipientsOnEmailList = 1500;
    //
    const string LogfileName = "\\GoogleApps.log";
    // private variables
    Boolean m_isInitialized;
    StreamWriter m_writer;
    // Constructor
    public lib()
    {
      string logfile = MAUtils.MAFolder.ToString() + LogfileName;
      m_writer = new StreamWriter(logfile, true);
      m_isInitialized = true;
    }
    // Deconstructor
    ~lib()
    {
      if (m_isInitialized)
      {
        Cleanup(true);
      }
    }
    // Cleanup
    public void Cleanup(Boolean isFromDeconstructor)
    {
      if (isFromDeconstructor)
      {
        m_writer.Close();
        m_isInitialized = false;
      }
      else
      {
        m_writer.Close();
      }
    }
    // Notify Google Apps Errors to FIM2010
    public void handleException(string errorMessage, string errorCode)
    {
      Logging(errorMessage, errorCode);
      switch (int.Parse(errorCode))
      {
        case EntityExists:
          throw new Microsoft.MetadirectoryServices.ObjectAlreadyExistsException(errorMessage);
        case EntityDoesNotExist:
          throw new Microsoft.MetadirectoryServices.NoSuchObjectException(errorMessage);
        case UnknownError:
        case UserDeletedRecently:
        case UserSuspended:
        case DomainUserLimitExceeded:
        case DomainAliasLimitExceeded:
        case DomainSuspended:
        case DomainFeatureUnavailable:
        case EntityNameIsReserved:
        case EntityNameNotValid:
        case InvalidGivenName:
        case InvalidFamilyName:
        case InvalidPassword:
        case InvalidUsername:
        case InvalidHashFunctionName:
        case InvalidHashDigestLength:
        case InvalidEmailAddress:
        case InvalidQueryParameterValue:
        case TooManyRecipientsOnEmailList:
        default:
          throw new Microsoft.MetadirectoryServices.ExtensibleExtensionException(errorMessage);
      }
    }
    // Logging
    public void Logging(string errMessage, string errCode)
    {
      m_writer.WriteLine("[{0}] {1} {2}", DateTime.Now.ToString(), errCode, errMessage);
    }
  }
}

2010年9月23日木曜日

FIMの本、WIFの本

洋書ですが要チェックな本を紹介します。

まずはFIMの本です。先日も紹介した本ですが、MVPのDavid Lundellさんの作です。

FIM Best Practices Volume 1: Introduction, Architecture And Installation Of Forefront Identity Manager 2010




















分散でのFIMの構成方法など非常に詳しく記載されているので、実際の業務環境にFIMを導入する際には非常に役に立つ本だと思います。
オンデマンド印刷の形で販売されているので一般書店やAmazonには流通しないと思いますので、ここで購入する形になります。
オーダーしてから2週間程度で届きました。

もちろん日本語なら青本も必携ですが。


次にWIF本です。
こちらはMicrosoftのVittorio Bertocciさんの作でAmazon.co.jpでも購入できます。

目次を見るとWIFそのものの動きに関する章からASP.NETでのプログラマ向けの章、Windows AzureやWCFへの組み込みの話まで非常に有用な内容が目白押しです。
ちなみに9月初旬に発売されているのですが、まだ手元には届いていません。。。














2010年9月11日土曜日

FIM2010 Google Appsへのプロビジョニング その1

ILMやFIM Synchronization ServiceはManagement Agent(MA)というアダプタを使って各種レポジトリ(Active Directoryやデータベースなど)との同期やプロビジョニングを行っています。

もちろん元々用意されているMAでもある程度のシステムはカバーしているのですが、カスタムのMAを作成して対応していないシステムとの同期をさせることもできます。
その時に使うのが、
 Extensible Connectivity(XMA)
です。













このタイプのMAを使うとコーディングは必要ですが同期先システムに合わせてカスタムMAを作成することができます。

今回はこの仕組みを使ってGoogle Appsへのユーザの作成・更新・削除を行うMAを作ってみたいと思います。
別のエントリでも述べているようにAD FS2.0を使ってGoogle AppsへのログインをActive Directoryの認証で行う、という方法もありますので、その方法と合わせるとGoogle Apps上のユーザのライフサイクル管理と認証を完全に管理することができます。


















■準備
まず、必要となる環境ですがGoogle Apps上のユーザを外部から管理するためにはGoogle AppsのProvisioning APIを使う必要があります。
その場合、
 Google Apps Premium Edition
 Google Apps Education Editionon
のどちらかが必要になりますので予め契約をしておく必要があります。(Premium Editionには30日間の試用期間あり)
※ちなみに1ユーザから契約ができますが、1ユーザだけではユーザの追加ができないので、最低2ユーザは契約しておく必要があります。

購入(もしくは試用)が出来たらGoogle Appsの管理コンソールより、ユーザーとグループ -> 設定 メニューで[Provisioning APIを有効にする]にチェックを入れます。
これでGoogle Apps側の準備は完了です。











次に開発を行うための準備をします。
私はVisual Studio 2010 Ultimate Editionを使っていますが、FIMのExtension ProjectはVisual Studio 2008がデフォルトなので当然2008でも構いません。

.NET環境からGoogleのProvisioning APIを利用するためのクラスライブラリがGoogleから提供されているので、以下のURLからダウンロードしセットアップしておきます。
URL
 http://code.google.com/p/google-gdata/downloads/list
モジュール
 Google Data API Setup (1.6.0.0).msi

まとめ(準備)
Google Apps Premium or Education Editionの契約(2ユーザ以上)
Visual Studio 2008 / 2010
Google Data APIライブラリのインストール
※もちろんFIMも必要です(笑)


■カスタムMAの作成
早速カスタムMAを作成するのですが、まずはFIM Synchronization ServiceのコンソールでMAを作成します。先にMAを作成することでFIMがあらかじめ用意しているカスタムMAのプロジェクトテンプレートを使うことができるようになります。

以下の通り、MAを作成します。(デフォルトから変更する部分のみ)
ページ項目
Create Management AgentManagement agent forExtensible Connectivity
NameGoogle Apps
Configure Connection InformationSpecify the interfaces supported by this management agentExport
Spscify the export mode supported by this management agentCall-based
Connected data source extension nameGoogleApps__Extension.dll
Connec ToGoogle Appsの契約ドメイン名
UserGoogle Appsの管理者ユーザ名(emailアドレス)
PasswordGoogle Appsの管理者パスワード
Select Template Input FileTemplate Input File任意※
File formatDelimited
Delimited Text FormatUse first row for header namesチェック
Text qualifier<none>
Configure AttributesSet Anchorname
Configure Join and Projection RuleJoin Rule for personname <direct> accountName
Projection / Metaverse object typeperson
Configure Attibute FlowDatasource <- MetaversegivenName <export> givenName
sn <export> sn
userPassword <export/Advanced> Constant[P@ssw0rd]

※テンプレートファイルは以下の文字列を保存したテキストファイルをあらかじめ用意しておきます。(MAの作成以後は使用しません)
 Name,givenName,sn,userPassword
※Join / Projection / Attribute FlowはCodeless Provisioningを使う場合は不要です。


MAが作成できたらMAを右クリックし[Create Extension Projects]をクリックします。
































ここではProject Typeに[Connected Data Source Extension]を選択し、プロジェクトを作成します。
プログラム言語は任意ですがここではC#を選んでいます。また、私は開発環境を別のPCに作っていたのでLaunch in VS.Net IDEのチェックを外しています。

いよいよカスタムMAの中身のロジックを実装していきます。


■カスタムMAの実装
出来上がったプロジェクトファイルをVisual Studioで開くとあらかじめ定義されたテンプレートが出来ているので、各メソッドを実装していきます。

その前に今回はGoogle AppsのProvisioning APIを使うので先にダウンロードしたライブラリへの参照設定を行います。
必要なのは、
・Google.GData.Apps.dll
・Google.GData.Client.dll
・Google.GData.Extensions.dll
への参照です。
















同様にusingでそれらライブラリへの参照を宣言します。

using Google.GData.Apps;
using Google.GData.Client;
using Google.GData.Extensions;


いよいよロジックの実装ですが、あらかじめ用意されているメソッドの中で今回必要となるのは以下のメソッドです。
メソッド名役割
BeginExportExport開始時に呼び出される
ExportEntryExport時に呼び出される(実際のExportロジック)
EndExportExport終了時に呼び出される


また、Google Appsに接続するためにAppsServiceというオブジェクトを使うので変数の宣言をしておきます。

namespace Miis_CallExport
{
 public class MACallExport :
  IMAExtensibleFileImport,
  IMAExtensibleCallExport
 {
  AppsService service; ← ここ


次にBeginExportの実装です。ここでは先ほど宣言したAppsServiceのオブジェクトを生成します。
BeginExportメソッドの引数の中のconnectTo,user,passwordは先ほどMAを作成したときに設定したものがわたってきますので、それらをそのまま使ってGoogel Appsへ接続します。

service = new AppsService(connectTo, user, password);


次に実際のExport時に呼び出されるExportEntryメソッドです。
ここでは引数としてわたってくるmodificationTypeを見て処理が分かれます。
modificationType処理
Addエントリの追加
Replaceエントリの更新
Deleteエントリの削除


まずはエントリの追加(modificationType=Add)の時ですが、Google Apps Provisioning APIでCreateUserというメソッドを利用します。
引数には同じくExportEntryメソッドの引数としてわたってくるcsentryの各属性値を渡します。
service.CreateUser(
  csentry["Name"].Value.ToString(),     ← 名前(ログインID)
  csentry["givenName"].Value.ToString(),   ← 名(First Name)
  csentry["sn"].Value.ToString(),       ← 姓(Family Name)
  csentry["userPassword"].Value.ToString()  ← パスワード
);



次はエントリの更新(modificationType=Replace)です。ここではUpdateUserメソッドを使います。
// ユーザの属性をセットする
UserEntry user = service.RetrieveUser(csentry["Name"].Value.ToString());
user.Name.FamilyName = csentry["sn"].Value.ToString();
user.Name.GivenName = csentry["givenName"].Value.ToString();
user.Login.Password = csentry["userPassword"].Value.ToString();
// ユーザを更新する
service.UpdateUser(user);



最後にエントリの削除(modificationType=Delete)です。ここではDeleteUserメソッドを使います。
service.DeleteUser(csentry["Name"].Value.ToString());



これらをswitch分で振り分けて各処理を実行させます。
switch (modificationType)
{
  case ModificationType.Add:
  // エントリの作成
   break;
  case ModificationType.Replace:
  // エントリの更新
   break;
  case ModificationType.Delete:
  // エントリの削除
   break;
}


尚、EndExportメソッドでは他にオブジェクトを生成した場合はオブジェクトの廃棄を行います。(今回は特に何も書きませんが)


長くなってしまいましたので、詳細なコードの解説、実際の使い方は次回解説をしたいと思います。
※コードはsourceforge.netにアップロードしてあるので解説不要の方はどうぞ。
 http://sourceforge.net/projects/fim2010mas/

2010年9月1日水曜日

@IT記事:Windowsで構築する、クラウド・サービスと社内システムのSSO

そういえば@ITさんでAD FS2.0を使ったクラウド・サービスとのシングル・サインオンを
中心としてアイデンティティ管理関連の記事を連載中です。
それなりにページビューもあるそうなので、皆様興味やニーズはあるんでしょうね。

先ほど何気にGoogleで「クラウド SSO」と検索したらその記事とこのblogが上位に来ていました。













某社のプレス発表も見えてますが。。



ちなみに現段階で公開されている記事の内容とURLは以下の通りです。

第1回 クラウド・コンピューティングとアイデンティティ管理の概要
http://www.atmarkit.co.jp/fwin2k/operation/adsf2sso01/adsf2sso01_01.html
 1.クラウド・コンピューティングの基礎と課題
 2.クラウドでのセキュリティ対策とアイデンティティ管理の役割
 3.クラウドがアイデンティティ管理システムにもたらす変化

第2回 クラウド・コンピューティング時代の認証技術
http://www.atmarkit.co.jp/fwin2k/operation/adfs2sso02/adfs2sso02_01.html

 1.アイデンティティ連携(フェデレーション)の要素技術
 2.マイクロソフトのアイデンティティに関するビジョン
 3.アイデンティティ・メタシステムの実装


第3回はまだ公開されていませんが、「クラウドサービスの活用例」として実際人AD FS2.0を使って各種クラウド・サービスとのシングル・サインオンを実装する手順を紹介する予定です。
対象サービスとして、
・Google Apps( GMail )
・Windows Live
・Salesforce.com CRM
・Windows Azure上にデプロイしたWIFを使ったASP.NETアプリケーション
・force.com


良ければ見てやってください。