Exchange Web Serviceでのフォルダ権限の設定

リソースメールボックスの共有アクセス件の設定方法を探していたところ、ぴったりの記事があった。
Exchange 2007 SP1から、Exchange Web Serviceを利用してフォルダへのアクセス権が設定できるようになっているようだ。
Outlookからは簡単に設定できるのだが、リソースメールボックスの権限を設定するためだけにOutlookを起動するのはいまいちだし、Exhangeの管理シェルにもちょうどよいコマンドがなくて困っているところだった。


「Setting and Understanding Folder Permissions in Exchange Web Services」
http://gsexdev.blogspot.com/2008/01/setting-and-understanding-folder.html



Let’s start by looking at the permissions that can be set on a folder via EWS
CanCreateItems
ReadItems
DeleteItems
EditItems
CanCreateSubFolders
IsFolderContact
IsFolderOwner
IsFolderVisible

EWS(Exchange Web Service)でフォルダに設定できる権限は以下の通り
CanCreateItems
ReadItems
DeleteItems
EditItems
CanCreateSubFolders
IsFolderContact
IsFolderOwner
IsFolderVisible



With Exchange 2007/Outlook2007 there where Permissions added to support the new freebusy detail features in Outlook and Exchange. There’s some good information about these on Stephen Griffin's Blog . These new permissions are only valid for a calendar folder. Overlaying these based folder permissions are the Roles that a user would generally assign in Outlook. Now these Roles are just certain combinations of the above Permissions. The following are the roles you would normally see set on a folder if you’re looking in Outlook

Exchange 2007とOutlook 2007では、空き時間情報の機能をサポートするための権限が追加されています。Stephen Griffinのブログに詳しい情報がのっています。これらの新しい権限は予定表フォルダでのみ有効です。これらの権限をもとにして、Outloolでユーザが設定可能なロールが厚生されています。ロールはこれらの権限の組み合わせです。以下に、Outlookで通常のフォルダに設定可能なロールを示します。


Author
Contributor
Custom
Editor
None
NoneditingAuthor
Owner
PublishingAuthor
PublishingEditor
Reviewer

On a calendar folder you have the following additional roles to support freebusy detail

予定表フォルダには、空き時間情報をサポートするために以下の権限が追加されています。


FreeBusyTimeAndSubjectAndLocation
FreeBusyTimeOnly



Now let’s look at an example of setting the calendar folder permissions using EWS. Generally when you are setting permissions you’ll be modifying the permissions on an already existing Exchange Store object. So you will be either adding an additional Access Control Entry to the permissions list or modifying the rights or an existing ACE.

それでは、EWSを使用した予定表フォルダへの権限の設定例を見てみましょう。通常、権限を設定する場合はすでにExchange Storeオブジェクトに設定されている権限を修正することになります。権限リストにアクセスコントロールエントリ(ACE)を追加するか、既存のACEの権限を修正することになります。



In EWS calendar folder permissions are represented by the CalendarPermissionSetType object so to modify the permissions on an existing folder you need to modify the Permission Set property using an updateitem operation. Sounds easy right well this is where the complications begin.

EWSの予定表オブジェクトでは、権限はCalendarPermissionSetTypeのオブジェクトとして表現されています。つまり、既存のフォルダの権限を変更するには、updateitemを使用してPermission Set プロパティを変更する必要があります。簡単なことのように見えますが、本当の困難はここから始まります。



When you use an UpdateItem operation to update a property on a folder that property you update will overwrite the existing store property. Because the whole PermissionSet is represented as one property you can’t just write the changes with an UpdateItem operation. If you do this you will end up deleting all your existing ACE’s and end up only with the changes you’re trying to make. So to cater for this you first need to get the existing CalendarPermissionSet using a GetFolder operation, you then need to create a new CalendarPermissionSet that contains all the ACE’s from the existing CalendarPermissionSet with whatever modifications you want and then post this new CalendarPermissionSet to overwrite the existing set. Now here’s a precautionary tale don’t do what I tried to which was to try and just make changes to the existing CalendarPermissionSet object you retrieve with GetFolder operation and then post this back.

フォルダのプロパティに対してUpdateItemを実行すると、既存のプロパティが上書きされます。すべてのPermissionSetは1つのプロパティとなっているので、単純に変更をUpdateItemで適用することはできません。もしもそのような操作をしてしまった場合、既存のACEはすべて消去され、適用しようとしていた変更のみが残ってしまいます。そのため権限変更を実行するためには、まずGetFolderを実行し既存のCalendarPermissionSetを取得します。次に、新しいCalenderPermissionSetを作成し、既存のACEをすべて含め、、変更を適用したCalendarPermissionSetを送信して既存のCalendarPermissionSetを上書きします。注意点としては、GetFolderで取得したCalendarPermissionSetを直接修正して送信してはいけないということです。



The reason this could fail is also a little complicated but there are a few important points to understand.

この失敗の理由は少々込み入っていますが、理解しておくべき重要な点がいくつかあります。



When you include a calendarPermission in a CalendarPermissionSet you have to set the CalendarPermissionLevel. These Levels relate to the Outlook Roles I mentioned previously if these roles are set to anything other than Custom you must make sure you don’t include setting any of the base rights. So basically you can set something like the CalendarPermissionLevel to PublishingAuthor or you can set the CalendarPermissionLevel to custom and then set each of the base rights like CanCreateItems,ReadItems etc. If you try to set CalendarPermissionLevel to PublishingAuthor and also set the base rights (including the foldercontact and folderowner setting) EWS will reject the changes you’re trying to make as invalid.

CalendarPermissionSetにcalendarPermissionを含めるときには、CalendarPermissionLevelを設定する必要があります。このレベルは前に述べてOutlookのロールに関連しています。カスタム以外のロールを設定する場合は、元となっている権限を含めることはできません。つまり、基本的には、CalendarPermissionLevelをPublishingAuthorなどに設定するか、CalendarPermissionLevelをカスタムに設定し、CanCreateItemsやReadItemsなどの権限を設定することになります。CalendarPermissionLevelにPublishingAuthorを設定し、かつ、権限(foldercontactやfolderownerも含む)を同時に設定するとEWSによって変更が無効であるとして拒否されます。



Now when you get the permissionset using a GetFolder operation EWS will return a fully populated CalendarPermission object for each ACE in the Set even if the CalendarPermissionLevel isn’t set to custom. So if you try to use one of these calendarPermissions that had a CalendarPermissionLevel set to something other than custom it will cause your code to error out because it breaks the rule I mentioned in the last paragraph.

GetFolderで権限を取得すると、EWSはCalendarPermissionLevelがカスタムではない場合もすべてのACEを含んだCaledarPermissionオブジェクトを返します。そのため、これらのCalendarPermissionのうちCalendarPermissionLevelがカスタムではないものを利用すると、前の段落で述べたルールに違反するため、エラーが発生します。



So from a coding perspective to make this work you should build a new CalendarPermissionLevel object based on the existing object with some logic to verify the CalendarPermission your including in the set is going to be valid. The logic I came up with was you can copy any of the existing custom CalendarPermissionLevel objects okay into the new CalendarPermissionSet object but for any other role you need to create a new CalendarPermission object and just copy the userid and CalendarPermissionLevel from the existing CalendarPermission.

そのため、コーディングを行う際には、既存のオブジェクトにロジックを適用して、有効な新しいCalendarPermissionLevelオブジェクトを生成する必要があります。私が使用しているロジックは、カスタムのCalendarPermissionLevelオブジェクトは新しいCalendarPermissionSetにコピーしますが、それ以外のロールの場合には、新しいCalendarPermissionオブジェクトを作成し、useridとCalendarPermisssionLevelのみを既存のCalendarPermissionからコピーするというものです。



The one thing to be careful of if you are setting Public Folder permission with EWS and you have set custom folder contacts is you may in some situations lose your custom contact folder setting if you set permissions using EWS and you don’t use the Custom Level.

カスタムの連絡先を設定したパブリックフォルダの権限をEWSで設定する際に気をつけなければならないのは、権限のレベルをカスタム以外にEWSで設定すると、カスタムの連絡先フォルダの設定内容が失われてしまうことがあるという点です。



So to put this all together in a code sample the following piece of code will change the default permissions on a user’s calendar from none to editor. I’ve put a download of this code here the code itself looks like.

これらの方法を使用して、ユーザの予定表の既定の権限を「なし」から「編集者」に変更するのが以下に示すコードです。

static void Main(string[] args)
{
// Create the binding and set the credentials
ExchangeServiceBinding esb = new ExchangeServiceBinding();
esb.RequestServerVersionValue = new RequestServerVersion();
esb.RequestServerVersionValue.Version = ExchangeVersionType.Exchange2007_SP1;
ServicePointManager.ServerCertificateValidationCallback =
delegate(Object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
// Replace this line with code to validate server certificate.
return true;
};

esb.Credentials = new NetworkCredential("username", "password", "domain");
esb.Url = @"https://servername/EWS/Exchange.asmx";
setcalperm(esb);

}
static void setcalperm(ExchangeServiceBinding esb)
{

DistinguishedFolderIdType cfCurrentCalendar = new DistinguishedFolderIdType();
cfCurrentCalendar.Id = DistinguishedFolderIdNameType.calendar;

FolderResponseShapeType frFolderRShape = new FolderResponseShapeType();
frFolderRShape.BaseShape = DefaultShapeNamesType.AllProperties;

GetFolderType gfRequest = new GetFolderType();
gfRequest.FolderIds = new BaseFolderIdType[1] { cfCurrentCalendar };
gfRequest.FolderShape = frFolderRShape;


GetFolderResponseType gfGetFolderResponse = esb.GetFolder(gfRequest);
CalendarFolderType cfCurrentFolder = null;
if (gfGetFolderResponse.ResponseMessages.Items[0].ResponseClass == ResponseClassType.Success)
{

cfCurrentFolder = (CalendarFolderType)((FolderInfoResponseMessageType)gfGetFolderResponse.ResponseMessages.Items[0]).Folders[0];

}
else
{//handle error
}

UserIdType auAceUser = new UserIdType();
auAceUser.DistinguishedUserSpecified = true;
auAceUser.DistinguishedUser = DistinguishedUserType.Default;

CalendarPermissionSetType cfCurrentCalPermsionsSet = cfCurrentFolder.PermissionSet;
CalendarPermissionSetType cfNewCalPermsionsSet = new CalendarPermissionSetType();
cfNewCalPermsionsSet.CalendarPermissions = new CalendarPermissionType[cfCurrentCalPermsionsSet.CalendarPermissions.Length] ;
for(int cpint=0;cpint < cfCurrentCalPermsionsSet.CalendarPermissions.Length;cpint++){
if (cfCurrentCalPermsionsSet.CalendarPermissions[cpint].UserId.SID == auAceUser.SID)
{
cfNewCalPermsionsSet.CalendarPermissions[cpint] = new CalendarPermissionType();
cfNewCalPermsionsSet.CalendarPermissions[cpint].UserId = cfCurrentCalPermsionsSet.CalendarPermissions[cpint].UserId;
cfNewCalPermsionsSet.CalendarPermissions[cpint].CalendarPermissionLevel = CalendarPermissionLevelType.Reviewer;
}
else
{
//Copy old ACE
if (cfCurrentCalPermsionsSet.CalendarPermissions[cpint].CalendarPermissionLevel == CalendarPermissionLevelType.Custom)
{
cfNewCalPermsionsSet.CalendarPermissions[cpint] = cfCurrentCalPermsionsSet.CalendarPermissions[cpint];
}
else
{
cfNewCalPermsionsSet.CalendarPermissions[cpint] = new CalendarPermissionType();
{
cfNewCalPermsionsSet.CalendarPermissions[cpint].UserId = cfCurrentCalPermsionsSet.CalendarPermissions[cpint].UserId;
cfNewCalPermsionsSet.CalendarPermissions[cpint].CalendarPermissionLevel = cfCurrentCalPermsionsSet.CalendarPermissions[cpint].CalendarPermissionLevel;
}
}
}

}


CalendarFolderType cfUpdateCalFolder = new CalendarFolderType();
cfUpdateCalFolder.PermissionSet = cfNewCalPermsionsSet;

UpdateFolderType upUpdateFolderRequest = new UpdateFolderType();

FolderChangeType fcFolderchanges = new FolderChangeType();

FolderIdType cfFolderid = new FolderIdType();
cfFolderid.Id = cfCurrentFolder.FolderId.Id;
cfFolderid.ChangeKey = cfCurrentFolder.FolderId.ChangeKey;

fcFolderchanges.Item = cfFolderid;

SetFolderFieldType cpCalPerms = new SetFolderFieldType();
PathToUnindexedFieldType cpFieldURI = new PathToUnindexedFieldType();
cpFieldURI.FieldURI = UnindexedFieldURIType.folderPermissionSet;
cpCalPerms.Item = cpFieldURI;
cpCalPerms.Item1 = cfUpdateCalFolder;

fcFolderchanges.Updates = new FolderChangeDescriptionType[1] { cpCalPerms };
upUpdateFolderRequest.FolderChanges = new FolderChangeType[1] { fcFolderchanges };

UpdateFolderResponseType ufUpdateFolderResponse = esb.UpdateFolder(upUpdateFolderRequest);
if (ufUpdateFolderResponse.ResponseMessages.Items[0].ResponseClass == ResponseClassType.Success)
{
Console.WriteLine("Permissions Updated sucessfully");
}
else
{
// Handle Error

}

}