根据来自外部身份的角色更新 Umbraco 用户的用户类型
Update Umbraco user's user type, based on the roles from external identity
在过去的几天里,我一直致力于将 Umbraco Backoffice 与 IdentityServer v3 集成。我已经设法做到这一点,我在外部对用户进行身份验证,并让 Umbraco 在后台创建一个具有某些默认用户类型的用户,并将其 link 添加到外部帐户。
接下来我要做的是根据用户角色更新 Umbraco 用户类型。我想我找到了一种方法,可以将 Umbraco link 连接到外部帐户,但是我看不到任何方法可以在每次登录时不断更新用户类型,以防角色 removed/added一个用户。
通过分析Umbraco中的代码BackOfficeController
,似乎无法进入Umbraco端的认证和更新数据的过程。
var user = await UserManager.FindAsync(loginInfo.Login);
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
else
{
if (await AutoLinkAndSignInExternalAccount(loginInfo) == false)
{
ViewBag.ExternalSignInError = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not been linked to to an account" };
}
}
似乎如果找到了 umbraco 登录,那么用户只是在登录,没有任何暴露的事件或选项。仅当未找到用户时,才会开始创建和 linking 的整个过程,我实际上可以在其中对用户属性进行一些更改。
也就是说,有没有什么方法可以在每次登录时根据外部服务器的声明实际更新 Umbraco 用户的用户类型?
我在 Startup class 中的代码如下。
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie
});
var idAuth = new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44332",
ClientId = "id",
ClientSecret = "secret",
RedirectUri = "http://localhost:8081/Umbraco",
ResponseType = "id_token token",
Scope = "openid profile roles email",
Caption = "test",
SignInAsAuthenticationType = Umbraco.Core.Constants.Security.BackOfficeExternalAuthenticationType
};
idAuth.Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var id = n.AuthenticationTicket.Identity;
var givenName = id.FindFirst(System.Security.Claims.ClaimTypes.GivenName);
var familyName = id.FindFirst(System.Security.Claims.ClaimTypes.Surname);
var roles = id.FindAll(System.Security.Claims.ClaimTypes.Role);
var nid = new ClaimsIdentity(
id.AuthenticationType,
System.Security.Claims.ClaimTypes.GivenName,
System.Security.Claims.ClaimTypes.Role);
nid.AddClaim(givenName);
nid.AddClaim(familyName);
nid.AddClaims(roles);
nid.AddClaim(id.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier));
nid.AddClaim(id.FindFirst(System.Security.Claims.ClaimTypes.Email));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
}
};
//idAuth.AuthenticationType = "https://localhost:44332";
idAuth.ForUmbracoBackOffice("btn-google-plus", "fa-google-plus"); //temporary icon/button
idAuth.AuthenticationType = "https://localhost:44332";
var externalOptions = new ExternalSignInAutoLinkOptions(autoLinkExternalAccount: true, defaultUserType: "admin");
//externalOptions.OnAutoLinking; // TODO: set user type based on roles
idAuth.SetExternalSignInAutoLinkOptions(externalOptions);
app.UseOpenIdConnectAuthentication(idAuth);
在 Umbraco 服务 IExternalLoginService
和 IUserService
的帮助下,通过在 SecurityTokenValidated
上手动检查角色声明和 Umbraco UserType
设法解决了这个问题。如果组合不正确(例如,声明中不存在管理员角色),我使用 Umbraco IUserService
来更新该用户的 UserType
Notifications =
new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var id = n.AuthenticationTicket.Identity;
var uid = id.FindFirst(ClaimTypes.NameIdentifier);
var givenName = id.FindFirst(ClaimTypes.GivenName);
var familyName = id.FindFirst(ClaimTypes.Surname);
var roles = id.FindAll(ClaimTypes.Role);
var rolesList = roles as IList<Claim> ?? roles.ToList();
if (
!rolesList.Any(
c =>
string.Equals(c.Value, RoleNames.ContentEditor,
StringComparison.InvariantCultureIgnoreCase)))
throw new HttpException(403,
"You do not have any roles configured for the application");
// create new identity and set name and role claim type
var nid = new ClaimsIdentity(
id.AuthenticationType,
ClaimTypes.GivenName,
ClaimTypes.Role);
UpdateUserType(uid.Value, rolesList, applicationConfiguration.AuthorityUrl);
nid.AddClaim(givenName);
nid.AddClaim(familyName);
nid.AddClaims(rolesList);
nid.AddClaim(uid);
nid.AddClaim(id.FindFirst(ClaimTypes.Email));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
}
}
private static void UpdateUserType(string uid, IList<Claim> roles, string providerName)
{
var userService = ApplicationContext.Current.Services.UserService;
var oneUser = ApplicationContext.Current.Services.ExternalLoginService.Find(new UserLoginInfo(providerName, uid)).FirstOrDefault();
if (oneUser == null)
return;
var user = userService.GetUserById(oneUser.UserId);
if (user == null)
return;
if (
roles.Any(
r => string.Equals(r.Value, RoleNames.Administrator, StringComparison.InvariantCultureIgnoreCase))
&& !string.Equals(user.UserType.Alias, UmbracoRoleNames.Administrator))
{
SetUserType(user, UmbracoRoleNames.Administrator, userService);
return;
}
if (
roles.Any(
r => string.Equals(r.Value, RoleNames.ContentEditor, StringComparison.InvariantCultureIgnoreCase))
&& !string.Equals(user.UserType.Alias, UmbracoRoleNames.ContentEditor))
{
SetUserType(user, UmbracoRoleNames.ContentEditor, userService);
return;
}
}
private static void SetUserType(Umbraco.Core.Models.Membership.IUser user, string alias, IUserService userService)
{
try
{
user.UserType = userService.GetUserTypeByAlias(alias);
userService.Save(user);
}
catch (Exception e)
{
LogHelper.Error(typeof(ClassName), "Could not update the UserType of a user.", e);
}
}
在这种特定情况下,当有人缺乏其角色声明中的特权时,我不会将 UserType
改回 non-admin/non-content 编辑器,因为他们在前一步被过滤掉了正在返回 403 错误代码。
在过去的几天里,我一直致力于将 Umbraco Backoffice 与 IdentityServer v3 集成。我已经设法做到这一点,我在外部对用户进行身份验证,并让 Umbraco 在后台创建一个具有某些默认用户类型的用户,并将其 link 添加到外部帐户。
接下来我要做的是根据用户角色更新 Umbraco 用户类型。我想我找到了一种方法,可以将 Umbraco link 连接到外部帐户,但是我看不到任何方法可以在每次登录时不断更新用户类型,以防角色 removed/added一个用户。
通过分析Umbraco中的代码BackOfficeController
,似乎无法进入Umbraco端的认证和更新数据的过程。
var user = await UserManager.FindAsync(loginInfo.Login);
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
else
{
if (await AutoLinkAndSignInExternalAccount(loginInfo) == false)
{
ViewBag.ExternalSignInError = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not been linked to to an account" };
}
}
似乎如果找到了 umbraco 登录,那么用户只是在登录,没有任何暴露的事件或选项。仅当未找到用户时,才会开始创建和 linking 的整个过程,我实际上可以在其中对用户属性进行一些更改。
也就是说,有没有什么方法可以在每次登录时根据外部服务器的声明实际更新 Umbraco 用户的用户类型?
我在 Startup class 中的代码如下。
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie
});
var idAuth = new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44332",
ClientId = "id",
ClientSecret = "secret",
RedirectUri = "http://localhost:8081/Umbraco",
ResponseType = "id_token token",
Scope = "openid profile roles email",
Caption = "test",
SignInAsAuthenticationType = Umbraco.Core.Constants.Security.BackOfficeExternalAuthenticationType
};
idAuth.Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var id = n.AuthenticationTicket.Identity;
var givenName = id.FindFirst(System.Security.Claims.ClaimTypes.GivenName);
var familyName = id.FindFirst(System.Security.Claims.ClaimTypes.Surname);
var roles = id.FindAll(System.Security.Claims.ClaimTypes.Role);
var nid = new ClaimsIdentity(
id.AuthenticationType,
System.Security.Claims.ClaimTypes.GivenName,
System.Security.Claims.ClaimTypes.Role);
nid.AddClaim(givenName);
nid.AddClaim(familyName);
nid.AddClaims(roles);
nid.AddClaim(id.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier));
nid.AddClaim(id.FindFirst(System.Security.Claims.ClaimTypes.Email));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
}
};
//idAuth.AuthenticationType = "https://localhost:44332";
idAuth.ForUmbracoBackOffice("btn-google-plus", "fa-google-plus"); //temporary icon/button
idAuth.AuthenticationType = "https://localhost:44332";
var externalOptions = new ExternalSignInAutoLinkOptions(autoLinkExternalAccount: true, defaultUserType: "admin");
//externalOptions.OnAutoLinking; // TODO: set user type based on roles
idAuth.SetExternalSignInAutoLinkOptions(externalOptions);
app.UseOpenIdConnectAuthentication(idAuth);
在 Umbraco 服务 IExternalLoginService
和 IUserService
的帮助下,通过在 SecurityTokenValidated
上手动检查角色声明和 Umbraco UserType
设法解决了这个问题。如果组合不正确(例如,声明中不存在管理员角色),我使用 Umbraco IUserService
来更新该用户的 UserType
Notifications =
new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var id = n.AuthenticationTicket.Identity;
var uid = id.FindFirst(ClaimTypes.NameIdentifier);
var givenName = id.FindFirst(ClaimTypes.GivenName);
var familyName = id.FindFirst(ClaimTypes.Surname);
var roles = id.FindAll(ClaimTypes.Role);
var rolesList = roles as IList<Claim> ?? roles.ToList();
if (
!rolesList.Any(
c =>
string.Equals(c.Value, RoleNames.ContentEditor,
StringComparison.InvariantCultureIgnoreCase)))
throw new HttpException(403,
"You do not have any roles configured for the application");
// create new identity and set name and role claim type
var nid = new ClaimsIdentity(
id.AuthenticationType,
ClaimTypes.GivenName,
ClaimTypes.Role);
UpdateUserType(uid.Value, rolesList, applicationConfiguration.AuthorityUrl);
nid.AddClaim(givenName);
nid.AddClaim(familyName);
nid.AddClaims(rolesList);
nid.AddClaim(uid);
nid.AddClaim(id.FindFirst(ClaimTypes.Email));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
}
}
private static void UpdateUserType(string uid, IList<Claim> roles, string providerName)
{
var userService = ApplicationContext.Current.Services.UserService;
var oneUser = ApplicationContext.Current.Services.ExternalLoginService.Find(new UserLoginInfo(providerName, uid)).FirstOrDefault();
if (oneUser == null)
return;
var user = userService.GetUserById(oneUser.UserId);
if (user == null)
return;
if (
roles.Any(
r => string.Equals(r.Value, RoleNames.Administrator, StringComparison.InvariantCultureIgnoreCase))
&& !string.Equals(user.UserType.Alias, UmbracoRoleNames.Administrator))
{
SetUserType(user, UmbracoRoleNames.Administrator, userService);
return;
}
if (
roles.Any(
r => string.Equals(r.Value, RoleNames.ContentEditor, StringComparison.InvariantCultureIgnoreCase))
&& !string.Equals(user.UserType.Alias, UmbracoRoleNames.ContentEditor))
{
SetUserType(user, UmbracoRoleNames.ContentEditor, userService);
return;
}
}
private static void SetUserType(Umbraco.Core.Models.Membership.IUser user, string alias, IUserService userService)
{
try
{
user.UserType = userService.GetUserTypeByAlias(alias);
userService.Save(user);
}
catch (Exception e)
{
LogHelper.Error(typeof(ClassName), "Could not update the UserType of a user.", e);
}
}
在这种特定情况下,当有人缺乏其角色声明中的特权时,我不会将 UserType
改回 non-admin/non-content 编辑器,因为他们在前一步被过滤掉了正在返回 403 错误代码。