Получается, будучи единожды назначенным, Thread.CurrentPrincipal далее уже не зависит от имперсонированного пользователя?! Если к нему обратиться до первой имперсонации, то там окажется User0 и более уже не изменится. Попытки выполнить в любом месте Thread.CurrentPrincipal = null ничего, разумеется, не изменили — значение остаётся тем-же.
Это баг или так задумано? И если последнее, то в чём идея?
Здравствуйте, Jolly Roger, Вы писали:
JR>Псевдокод:
JR>Получается, будучи единожды назначенным, Thread.CurrentPrincipal далее уже не зависит от имперсонированного пользователя?! Если к нему обратиться до первой имперсонации, то там окажется User0 и более уже не изменится. Попытки выполнить в любом месте Thread.CurrentPrincipal = null ничего, разумеется, не изменили — значение остаётся тем-же.
JR>Это баг или так задумано? И если последнее, то в чём идея?
ИМХО, всё так и задумано. Thread.CurrentPrincipal это просто дополнительный, привязанный к потоку, контекст, вообще говоря, не имеющий, ничего общего с тем, под каким пользователем мы работаем. Например, в веб приложении, код будет выполняться от имени одного и того же ASP.NET аккаунта, при этом мы можем использовать Thread.CurrentPrincipal для того, чтобы наше приложение вело себя по разному в зависимости от роли пользователя. Пожалуй, единственная неочевидность в том, что первое чтение Thread.CurrentPrincipal, нужно рассматривать как его инициализацию. Если на время забыть про дополнительные плюшки .NET для Thread.CurrentPrincipal, твой код можно переписать вот так:
...
IPrincipal myGlobalPrincipal = null;
using (var i1 = wi1.Impersonate())
{
textBox.AppendText(Environment.UserName + "\r\n"); // <- User1
// а не инициализировать-ли нам наш глобальный principal?var wi = WindowsIdentity.GetCurrent();
myGlobalPrincipal = new WindowsPrincipal(wi);
textBox.AppendText(myGlobalPrincipal.Identity.Name + "\r\n"); // <- User1
}
textBox.AppendText(Environment.UserName + "\r\n"); // <- User0
textBox.AppendText(myGlobalPrincipal.Identity.Name + "\r\n"); // <- User1 !!!!
...
И теперь ничего удивительного в его поведении нет.
Здравствуйте, k.o., Вы писали:
KO>Здравствуйте, zhech, Вы писали:
Z>>Jolly Roger, не пробовали ли Вы такого?) :
KO>А Вы пробовали? WindowsImpersonationContext.Dispose вызывает Undo, поэтому, в данном случае, никакого смысла в его явном вызове нет.
Конечно пробовал, при этом все проходило так, как необходимо
Здравствуйте, k.o., Вы писали:
Z>>Jolly Roger, не пробовали ли Вы такого?) :
KO>А Вы пробовали? WindowsImpersonationContext.Dispose вызывает Undo, поэтому, в данном случае, никакого смысла в его явном вызове нет.
Во-первых, не лишним было бы почитать документацию:
Notes to Callers
After using Impersonate, it is important to call the Undo method to end the impersonation.
Во-вторых, не лишним было бы проверить С помощью примера кода (раз), рефлектора (два) или исходников (три).
Кстати, очень может быть, что в какой-либо реализации ваши слова и могли бы оказаться правдой, но в таком случае нужно явно указать имеющуюся в виду реализацию — в общепринятых такого нет.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>Здравствуйте, k.o., Вы писали:
Z>>>Jolly Roger, не пробовали ли Вы такого?) :
KO>>А Вы пробовали? WindowsImpersonationContext.Dispose вызывает Undo, поэтому, в данном случае, никакого смысла в его явном вызове нет.
_FR>Во-первых, не лишним было бы почитать документацию: _FR>
_FR>Notes to Callers
_FR>After using Impersonate, it is important to call the Undo method to end the impersonation.
_FR>Во-вторых, не лишним было бы проверить С помощью примера кода (раз), рефлектора (два) или исходников (три).
раз:
using System;
using System.Linq;
using System.Security.Principal;
using System.Collections.Generic;
using System.Threading;
using System.Runtime.InteropServices;
namespace Test
{
class Program
{
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
void Main()
{
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
var domain = ".";
var username = "username";
var password = "password";
var tokenHandle = IntPtr.Zero;
LogonUser(
username,
domain,
password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
ref tokenHandle);
var names = new HashSet<string>();
var wi = new WindowsIdentity(tokenHandle);
using (var wic = wi.Impersonate())
{
names.Add(Thread.CurrentPrincipal.Identity.Name);
names.Undo();
}
names.Add(Thread.CurrentPrincipal.Identity.Name);
Console.WriteLine(names.Count); // выводит 1
}
}
}
_FR>Кстати, очень может быть, что в какой-либо реализации ваши слова и могли бы оказаться правдой, но в таком случае нужно явно указать имеющуюся в виду реализацию — в общепринятых такого нет.
Здравствуйте, k.o., Вы писали:
_FR>>Во-вторых, не лишним было бы проверить С помощью примера кода (раз), рефлектора (два) или исходников (три). KO>раз:
Пример, конечно, не рабочий, но я наконец-то разглядел вызов Undo() в Dispose() Смотрел до этого нескоко раз, не видел
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, zhech, Вы писали:
Z>Jolly Roger, не пробовали ли Вы такого?) :
Z> i1.Undo();
Я пробовал — от безысходности А Вы? Похоже, нет
Дело в том, что Environment.UserName работает честно и без выкрутасов. Она в лоб, что называется, вызывает WinApi функцию GetUserName, а та уж не обманет, проверено годами . Есть у текущего потока токен — она возьмёт имя из него, нет — вернёт имя из токена уровня процесса. И тот факт, что за пределами блока using (var i1 = wi1.Impersonate()) эта функция вернула User0, совершенно однозначно говорит — RevertToSelf был вызван. Ну просто не дано иного И знаете, было-бы крайне, КРАЙНЕ странно, если бы WindowsImpersonationContext.Dispose() не выполняла бы отката.
Здравствуйте, Jolly Roger, Вы писали:
Z>>Jolly Roger, не пробовали ли Вы такого?) : Z>> i1.Undo(); JR>Я пробовал — от безысходности А Вы? Похоже, нет JR>Дело в том, что Environment.UserName работает честно и без выкрутасов. Она в лоб, что называется, вызывает WinApi функцию GetUserName, а та уж не обманет, проверено годами . Есть у текущего потока токен — она возьмёт имя из него, нет — вернёт имя из токена уровня процесса. И тот факт, что за пределами блока using (var i1 = wi1.Impersonate()) JR> эта функция вернула User0, совершенно однозначно говорит — RevertToSelf был вызван. Ну просто не дано иного И знаете, было-бы крайне, КРАЙНЕ странно, если бы WindowsImpersonationContext.Dispose() не выполняла бы отката.
Посмотри в отладчике имеющийся контекст имперсонилизавобщемпонятнодолжнобытьяужеязыксломал. Наверняка у него поле m_safeTokenHandle содержит инвалидный хендл. Как и почему — тема отдельного ресёча :о))
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, k.o., Вы писали:
KO>Здравствуйте, Jolly Roger, Вы писали:
KO>ИМХО, всё так и задумано. Thread.CurrentPrincipal это просто дополнительный, привязанный к потоку, контекст, вообще говоря, не имеющий, ничего общего с тем, под каким пользователем мы работаем. Например, в веб приложении, код будет выполняться от имени одного и того же ASP.NET аккаунта, при этом мы можем использовать Thread.CurrentPrincipal для того, чтобы наше приложение вело себя по разному в зависимости от роли пользователя. Пожалуй, единственная неочевидность в том, что первое чтение Thread.CurrentPrincipal, нужно рассматривать как его инициализацию. Если на время забыть про дополнительные плюшки .NET для Thread.CurrentPrincipal, твой код можно переписать вот так:
Как переписать, я в курсе, спасибо Я вобщем-то случайно наткнулся на это поведение — ну вроде удобно, да? Не надо "гонять" Identity между методами, не нужно какие-то списки хранить с риском напутать. Ну конечно, ThreadStatic имеется, но должно-же быть что-то готовое...Да вот-же оно — CurrentPrincipal! И такой обом-с
Но пока до меня высший смысл CurrentPrincipal не дошёл Ладно, "покурю" досуге
Здравствуйте, _FRED_, Вы писали:
_FR>Посмотри в отладчике имеющийся контекст имперсонилизавобщемпонятнодолжнобытьяужеязыксломал. Наверняка у него поле m_safeTokenHandle содержит инвалидный хендл. Как и почему — тема отдельного ресёча :о))
Да была, была вызвано Undo — Environment.UserName это однозначно говорит Ну и Undo я пробовал явно вызывать — ноль эффекта, само собой. И токен нормальный — куда он денется, если его никто не закрывает. Проверка-то на его инвалидность как раз и есть контроль, была-ли имперсонация и нужно-ли реверсить. И рефлектором полазил уже — по коду вроде как так и должно быть. Осталось понять — зачем Если на систему не хотели завязывать, то нафига было возвращать текущего при PrincipalPolicy.WindowsPrincipal? А если привязались, то почему не отслеживают? В общем, или я чего-то не понимаю, или они чего-то не додумали / не доделали
Здравствуйте, Jolly Roger, Вы писали:
JR>Здравствуйте, k.o., Вы писали:
KO>>Здравствуйте, Jolly Roger, Вы писали:
KO>>ИМХО, всё так и задумано. Thread.CurrentPrincipal это просто дополнительный, привязанный к потоку, контекст, вообще говоря, не имеющий, ничего общего с тем, под каким пользователем мы работаем. Например, в веб приложении, код будет выполняться от имени одного и того же ASP.NET аккаунта, при этом мы можем использовать Thread.CurrentPrincipal для того, чтобы наше приложение вело себя по разному в зависимости от роли пользователя. Пожалуй, единственная неочевидность в том, что первое чтение Thread.CurrentPrincipal, нужно рассматривать как его инициализацию. Если на время забыть про дополнительные плюшки .NET для Thread.CurrentPrincipal, твой код можно переписать вот так:
JR>Как переписать, я в курсе, спасибо Я вобщем-то случайно наткнулся на это поведение — ну вроде удобно, да? Не надо "гонять" Identity между методами, не нужно какие-то списки хранить с риском напутать. Ну конечно, ThreadStatic имеется, но должно-же быть что-то готовое...Да вот-же оно — CurrentPrincipal! И такой обом-с
JR>Но пока до меня высший смысл CurrentPrincipal не дошёл Ладно, "покурю" досуге
Хм... Может дело в том, что я с ASP.NET дело не имел, но как-то я не улавливаю связи PrincipalPermissionAttribute говорит: "Чтобы данный код выполнить, у юзера должны быть такие-то права". Это понятно. Но как это коррелирует с CurrentPrincipal? Код не может сам себе назначить произвольные права, его права равны или меньше прав запустившего его пользователя — это постулат системы безопасности. А CurrentPrincipal может назначаться самим кодом. Но даже не это вызывает непонимание — в конце концов Private Object Security в Windows работает на тех-же принципах. Но совершенно не понятно остаётся смысл PrincipalPolicy.WindowsPrincipal с его какой-то недоделанной привязкой к CurrentUser. Ну если Вы взялись отслеживать Windows Principal, так надо-же до конца идти — или вообще его не трогать. По-моему так
Здравствуйте, Аноним, Вы писали:
_FR>>Пример, конечно, не рабочий, но я наконец-то разглядел вызов Undo() в Dispose() Смотрел до этого нескоко раз, не видел
А>О, эксперт облажался. А как чувствуют себя эксперты, когда садятся в лужу?
Попе мокро.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, Jolly Roger, Вы писали:
JR>Здравствуйте, k.o., Вы писали:
KO>>Ну, так высший смысл в наличии таких штук как, например, http://msdn.microsoft.com/en-us/library/system.security.permissions.principalpermissionattribute.aspx, т.е. в "понимании" рантаймом семантики этого ThreadStatic объекта.
JR>Хм... Может дело в том, что я с ASP.NET дело не имел, но как-то я не улавливаю связи PrincipalPermissionAttribute говорит: "Чтобы данный код выполнить, у юзера должны быть такие-то права". Это понятно. Но как это коррелирует с CurrentPrincipal? Код не может сам себе назначить произвольные права, его права равны или меньше прав запустившего его пользователя — это постулат системы безопасности. А CurrentPrincipal может назначаться самим кодом.
Ну, во-первых, CurrentPrincipal может назначаться самим кодом, только если у того есть соответсвующее разрешение.
А во-вторых, это, по-идее, можно использовать для отслеживания прав клиента в серверном приложении. Например, пусть мы хотим разрешить некоторые операции только пользователям, в определённой роли:
public void ProcessFrobnicationRequest()
{
...
AuthenticateUser();
...
Frobnicate(); // allow frobnication only for users in "Gizmo" role.
...
}
Можно проверять это самостоятельно:
private void Frobnicate()
{
var user = Context.CurrentUser;
if (user.IsInRole("Gizmo"))
{
throw new SecurityException();
}
...
}
можно прикрутить AOP (или унаследоваться от CodeAccessSecurityAttribute) и добавлять такие проверки декларативно, с помощью аттрибутов:
в том числе, получая возможность делать это для всех методов класса:
[CheckPermission(RequiredRole = "Gizmo")]
class Frobnicator
{
...
}
а можно взять средства предоставляемые FCL:
[PrincipalPermission(SecurityAction.Demand, Role = "Gizmo")]
class Frobnicator
{
...
}
Правда, некторые странности в документации PrincipalPermissionAttribute заставляют усомниться в корректности такого использования этого аттрибута с точки зрения разработчиков FCL.
JR>Но даже не это вызывает непонимание — в конце концов Private Object Security в Windows работает на тех-же принципах. Но совершенно не понятно остаётся смысл PrincipalPolicy.WindowsPrincipal с его какой-то недоделанной привязкой к CurrentUser. Ну если Вы взялись отслеживать Windows Principal, так надо-же до конца идти — или вообще его не трогать. По-моему так
Возможно, было бы логичней, сделать явную инициализацию для CurrentPrincipal, но, видимо, текущая реализация происходит из желания упростить его использование для кода с ограниченными правами. Подозреваю, что идея была примерно такая: если у кода есть права на создание WindowsIdentity и, соответсвенно, работу от имени другого пользователя, то у него будут права и на установку CurrentPrincipal и он сможет сам его менять как нужно. В противном случае, не будет прав ни на установку CurrentPrincipal ни на вызов Impersonate, и, соответсвенно, не будет потенциальных проблем с этим связанных.
Здравствуйте, k.o., Вы писали:
KO>Ну, во-первых, CurrentPrincipal может назначаться самим кодом, только если у того есть соответсвующее разрешение.
KO>А во-вторых, это, по-идее, можно использовать для отслеживания прав клиента в серверном приложении. Например, пусть мы хотим разрешить некоторые операции только пользователям, в определённой роли:
Это понятно, это по сути та-же идея, что и Private Object Security, как я уже упоминал. Возможно, в NET немного проще сделано, хотя это как посмотреть.
KO>Возможно, было бы логичней, сделать явную инициализацию для CurrentPrincipal, но, видимо, текущая реализация происходит из желания упростить его использование для кода с ограниченными правами. Подозреваю, что идея была примерно такая: если у кода есть права на создание WindowsIdentity и, соответсвенно, работу от имени другого пользователя, то у него будут права и на установку CurrentPrincipal и он сможет сам его менять как нужно. В противном случае, не будет прав ни на установку CurrentPrincipal ни на вызов Impersonate, и, соответсвенно, не будет потенциальных проблем с этим связанных.
Вот и я про то-же. Либо уж следим за текущим пользователем, либо совсем не трогаем. В принципе, я нашёл, как добиться такого поведения. Надо после каждого вызова Impersonate и Undo выполнять Thread.CurrentPrincipal = null, тогда при следующем обращении он будет создан заново. Конечно, лишний вызов, а с другой стороны — более гибко. Не знаю только, нужна-ли эта гибкость