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);
    }
  }
}

0 件のコメント: