PowerEventReceivers в Sharepoint 2010

Ранее я писал о замечательном решении PowerEventReceivers. Если же вы захотите использовать его в Sharepoint 2010, вы с толкнетесь с проблемой – при открытии формы редактирования текста скрипта вы получите сообщение “Возникла неожиданная ошибка”, в логе другое сообщение: “Access Denied! Current user is not a farm administrator.”

Дело в том, что данное решение проверяет принадлежность пользователя к группе администраторов фермы с помощью системного вызова “SPFarm.Local.CurrentUserIsAdministrator()”. Но в Sharepoint 2010 этот вызов изменился, в функцию добавилась переменная allowContentApplicationAccess типа bool. Именно её надо выставить в true, чтобы правильно определялась принадлежность к группе из веб-страниц.

Перекомпилированную версию для Sharepoint 2010 вы можете скачать здесь.

Замечание: данная версия пока что не позволяет использовать новые командлеты Sharepoint 2010.

Реклама

Sharepoint и FBA: создание пользователей из PowerShell. Часть 2

Этап второй. Включение пользователя в Sharepoint.

На этом “грабли” FBA не заканчиваются, а только начинаются. Для того. чтобы включить пользователя в Sharepoint, необходимо выполнить два действия:

  1. Вызвать метод класса SPWeb EnsureUser(string login)
  2. Добавить пользователя в какую-нибудь группу.

Проблемы у вас начнутся, как только вы попытаетесь подключиться к сайту Sharepoint с включенным FBA — API не обладает средствами авторизации для работы с Sharepoint с помощью FBA. И если вы просто подключитесь к сайту и вызовите метод EnsureUser, вы получите ошибку “Exception calling "EnsureUser" with "1" argument(s): "Attempted to perform an unauthorized operation."”. И вот тут начинается магия .NET и API Sharepoint. Решение с HttpContext я честно подсмотрел в инете. Подключение к сайту Sharepoint с включенным FBA будет работать вот так и только так:

   1: [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Sharepoint") | Out-Null
   2:  
   3: [Microsoft.Sharepoint.SPSecurity]::RunWithElevatedPrivileges({ 
   4:  
   5: [Microsoft.Sharepoint.SPSite]$site = new-object Microsoft.Sharepoint.SPSite("http://fbasite")
   6: [Microsoft.Sharepoint.SPWeb]$web = $site.RootWeb
   7: if($web)
   8: {
   9:     $web.AllowUnsafeUpdates = $True
  10:     if (![System.Web.HttpContext]::Current)
  11:     {
  12:         $request = new-object System.Web.HttpRequest("", $web.Url, "");
  13:         $writer = new-object System.IO.StringWriter
  14:         $response = new-object System.Web.HttpResponse($writer)
  15:         [System.Web.HttpContext]::Current = new-object System.Web.HttpContext($request, $response);
  16:         [System.Web.HttpContext]::Current.Items["HttpHandlerSPWeb"] = $web;
  17:     }
  18:  
  19:     [string]$fullName = "Иванов иван Иванович"
  20:     [string]$username = "CustomSQLMembershipProvider:TestLogin"
  21:         
  22:     [Microsoft.Sharepoint.SPUser]$curUser = $web.EnsureUser($username);
  23:  
  24:     if ($curUser)
  25:     {
  26:        $curUser.Name = $fullName;
  27:        $curUser.Update();
  28:        $web.SiteGroups["Посетители"].AddUser($curUser);
  29:     }
  30:     
  31:     $web.AllowUnsafeUpdates = $False
  32:     
  33:     $web.Dispose()
  34:     $site.Dispose()
  35: }
  36: })

Обратите внимание на строки 10-17 – тут  мы создаем текущий контекст для работы с FBA, иначе вызов метода UnsureUser вызовет ошибку. Кроме этого, весь код работы с API Sharepoint выполнен внутри метода  RunWithElevatedPrivileges – это необходимо даже в том случае, если вы работаете из под учетной записи администратора фермы Sharepoint. Кроме того, обратите внимание в каком виде необходимо передавать логин методу UnsureUser – с обязательным указанием имени провайдера.

Замечание: Еще одним способом является создание альтернативного узла IIS, который затем будет включен в SharePoint и настроен на тот же сайт, что и узел с FBA. В этом случае вы можете подключаться к этому альтернативному узлу по средствам Windows-аутентификации, но такая настройка мягко скажем не тривиальна.

В итоге у меня получился вот такой скриптик, который выполняет все необходимые действия по добавлению пользователей в Sharepoint с включенным FBA:

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Sharepoint") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null
#====================================================================================
function Get-MembershipProvider([string]$ProviderName)
{
if($ProviderName)
{
#Находим провайдер по имени
return [System.Web.Security.Membership]::Providers[$ProviderName]
}
else
{
#Если имя не было указано, возвращаем провайдер по умолчанию
return [System.Web.Security.Membership]::Provider
}
}
#====================================================================================
function Create-ProviderUser($login, $password, $mail)
{
#проверяем, "подан ли на вход" провайдер
$provider = $input | select -First 1
if($provider -isnot [System.Web.Security.MembershipProvider])
{
$provider = Get-MembershipProvider
}

$status = 0
$question = "q"
$answer = "a"
$approved = $True
$retStatus = 0
$newUser = $provider.CreateUser($login, $password, $mail, $question, $answer, $approved, $null, [ref]$retStatus)
write-host "Статус создания пользвоателя а MembershipProvider: $retStatus"

return [System.Web.Security.MembershipUser]$newUser
}
#====================================================================================
function Create-SharepointUser([string]$SiteUrl,[System.Web.Security.MembershipUser]$User, [string]$FullName, [string]$GroupName)
{
[Microsoft.Sharepoint.SPSecurity]::RunWithElevatedPrivileges({
[Microsoft.Sharepoint.SPSite]$site = new-object Microsoft.Sharepoint.SPSite($SiteUrl)
[Microsoft.Sharepoint.SPWeb]$web = $site.RootWeb
if($web)
{
$web.AllowUnsafeUpdates = $True
if (![System.Web.HttpContext]::Current)
{
$request = new-object System.Web.HttpRequest("", $web.Url, "");
$writer = new-object System.IO.StringWriter
$response = new-object System.Web.HttpResponse($writer)
[System.Web.HttpContext]::Current = new-object System.Web.HttpContext($request, $response);
[System.Web.HttpContext]::Current.Items["HttpHandlerSPWeb"] = $web;
}

[Microsoft.Sharepoint.SPUser]$curUser = $web.EnsureUser($User.UserName);
if ($curUser -is [Microsoft.Sharepoint.SPUser])
{
$curUser.Name = $FullName
#После вызова метода Update может выскочить ошибка "Unable to cast object of type 'System.Management.Automation.PSObject' to type 'Microsoft.SharePoint.SPWeb'."
#При этом всё работает. Причину и решение я не нашел.
try{ $curUser.Update();}catch{}
[Microsoft.Sharepoint.SPGroup]$group = $web.SiteGroups[$GroupName]
$group.AddUser($curUser)
}

$web.AllowUnsafeUpdates = $False

$web.Dispose()
$site.Dispose()
}
})
}
#====================================================================================

#собственно код создания
$provider = Get-MembershipProvider "AtlantisSQLMembershipProvider"
$login = "TestLogin2"
$password = "!@#_s123QWE"
$email = "my@mail.local"
$user = $provider | Create-ProviderUser $login $password $email

if($user)
{
Create-SharepointUser http://fbasite $user "Иванов Иван Иванович" "Посетители"
}

Sharepoint и FBA: создание пользователей из PowerShell. Часть 1

Я уже давно работаю с Sharepoint, приличное время с PowerShell. И вот недавно мне поставили задачу – перевести функционал сайта, сделанного в 1С:Битрикс на WSS. Всё было просто, пока мы не дошли до стадии создания логинов на сайте.

Т.к. сайт предназначался для доступа из вне, естественным было использовать FBA в Sharepoint – Form Based Authentication. Встала необходимость создать порядка 50 пользователей, список которых был выгружен из 1С:Битрикс. Создание пользователей для Sharepoint  в обычной среде (с доменом и авторизацией в AD)  проблемой не является. Но, как оказалось, с FBA всё сильно сложнее.

Я не буду останавливаться на процессе собственно включения FBA в Sharepoint, благо про это написано не одна статья. В итоге у нас должен появиться свой собственный провайдер аутентификации и RoleManagement-а. Итак, процесс создания пользователя в общих чертах выглядит следующих образом:

  1. Создать пользователя в нашем провайдере аутентификации. Для этого нам надо указать логин, пароль, и электронную почту (в зависимости от настроек провайдера может потребоваться также подстановка контрольного вопроса и ответа). После этого пользователь будет создан в БД.
  2. Включить пользователя в Sharepoint, и добавить его в необходимую группу.

Этап первый.

Для создания пользователей в провайдере используется пространство имен [System.Web.Security]. В данном пространстве есть класс [System.Web.Security.MembershipProvider], экземпляры которого содержит в себе несколько методов для работы с пользователями, в том числе метод CreateUser, который нам и нужен.

Собственно код для создания пользователя в провайдере достаточно прост:

   1: [System.Reflection.Assembly]::LoadWithPartialName("System.Web")  | Out-Null
   2: $provider = [System.Web.Security.Membership]::Providers["CustomSqlMembershipProvider"]
   3: $login = "TestLogin"
   4: $password = "Secure1_Password"
   5: $mail = "test@mail.local"
   6: $question = "q"
   7: $answer = "a"
   8: $approved = $true
   9: $provider.CreateUser($login, $password, $mail, $question, $answer, $approved, $null, [ref]$status)

На вид – ничего сложного. Но когда вы запустите этот код, ваш созданный ранее провайдер не будет найден. Дело всё в том, что пространство имен [System.Web.Security] рассчитано на то, что все его классы будут использоваться внутри веб-сервера, и как следствие иметь доступ к настройках текущего узла. Точно также работает и класс [System.Web.Security.MembershipProvider] – вызов свойства [System.Web.Security.MembershipProvider]::Providers в свою очередь вызывает внутренние методы, которые обращаются к файлу настроек веб-сервера (а точнее, что важно как вы увидите потом – к файлу настроек приложения веб-вервера). Все настройки веб-севрера IIS хранятся в файле c:\Windows\Microsoft.NET\Framework64\v2.0.50727\CONFIG\web.config. Там же, в секции <configuration><roleManager> хранится и созданный провайдер. Логично, PowerShell вообще ничего не знает об этом файле настроек и не умеет его использовать.

Вот тут проявляется вся сила и простота .NET Framework. Всё, что нам нужно сделать – создать файл настроек (.config) для PowerShell. Для этого достаточно каталоге с файлом powershell.exe (или powershell_ise.exe, в зависимости от того, чем вы пользуетесь) создать одноименный файл powershell.config и\или powershell_ise.config со следующим шаблоном:

   1: <?xml version ="1.0"?>
   2: <configuration>
   3:   <system.web>
   4:   </system.web>
   5: </configuration>

После этого внутрь секции syste.web скопировать секции <roleManager> и <membership> из web.config, а также вставить после секции </system.web> секцию <connectionStrings> из того же web.config. В итоге у вас должно получиться примерно следующее:

   1: <?xml version ="1.0"?>
   2: <configuration>
   3:  <system.web>
   4:   <roleManager>
   5:    <providers>
   6:     <add name="CustomSqlRoleprovider" type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" applicationName="/" connectionStringName="LocalSqlServer" />
   7:    </providers>
   8:   </roleManager>
   9:   <membership>
  10:     <providers>
  11:       <add name="CustomSQLMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" applicationName="/" connectionStringName="LocalSqlServer" enablePasswordReset="true" enablePasswordRetrieval="false" passwordFormat="Hashed" requiresQuestionAndAnswer="false" requiresUniqueEmail="true" minRequiredNonalphanumericCharacters="1" minRequiredPasswordLength="8" />
  12:     </providers>
  13:   </membership>
  14:  </system.web>
  15:  <connectionStrings>
  16:    <remove name="LocalSqlServer" />
  17:    <add connectionString="Server=localhost;Database=FormAuthDB;Integrated Security=true" name="LocalSqlServer" providerName="System.Data.SqlClient" />
  18:  </connectionStrings>
  19: </configuration>

После этого, если вы запустите тот же самый скрипт – он должен отработать корректно.