根据来自外部身份的角色更新 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 服务 IExternalLoginServiceIUserService 的帮助下,通过在 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 错误代码。