Roger Lipscombe
Roger Lipscombe

Reputation: 91885

Can a custom UserNamePasswordValidator be used as a role provider?

Related to this question, I've got a custom UserNamePasswordValidator that logs in to our internal API. As part of this logging-in, I can discover the user's roles in our system.

I'd like to later use these in PrincipalPermissionAttribute demands on the service methods, e.g.:

[OperationContract]
[PrincipalPermission(SecurityAction.Demand, Role = "System Administrator")]
public string HelloWorld()
{ /* ... */ }

Upvotes: 3

Views: 2530

Answers (2)

Roger Lipscombe
Roger Lipscombe

Reputation: 91885

To expand on Ladislav's answer:

No. A custom UserNamePasswordValidator cannot be used as a role provider. The UserNamePasswordValidator runs in a separate context (or thread, or something) from the OperationContext that you want to mess with.

What you need to do instead is implement custom authorization. I found this page most useful for doing this. Warning: there's a lot of plumbing before you get to the interesting bits.

Essentially, you start with a ServiceCredentials-derived class, registered in App.config, as follows:

<serviceBehaviors>
  <behavior name="...">
    <serviceAuthorization principalPermissionMode="Custom" />

    <serviceCredentials type="MyNamespace.MyServiceCredentials, MyAssembly">
      <userNameAuthentication userNamePasswordValidationMode="Custom" />

      <serviceCertificate etc. />
    </serviceCredentials>

Associate the behavior with your service.

Override ServiceCredentials.CreateSecurityTokenManager to return a MySecurityTokenManager, derived from ServiceCredentialsSecurityTokenManager. On that, override CreateSecurityTokenAuthenticator, returning a MySecurityTokenAuthenticator. That should be derived from CustomUserNameSecurityTokenAuthenticator. In that, override ValidateUserNamePasswordCore. Call the base class, which will return a list of authorization policies.

To that list, add a new one: MyAuthorizationPolicy, which implements IAuthorizationPolicy. In that, you merely (hah) need to do the following:

public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
    IList<IIdentity> identities = GetIdentities(evaluationContext);

    // Find the GenericIdentity with our user-name in it.
    IIdentity currentIdentity = identities.SingleOrDefault(
        i => i is GenericIdentity &&
        StringComparer.OrdinalIgnoreCase.Equals(i.Name, UserName));
    if (currentIdentity == null)
        throw new InvalidOperationException("No Identity found");

    // Replace the GenericIdentity with a new one.
    identities.Remove(currentIdentity);
    var newIdentity =
        new GenericIdentity(_userName, currentIdentity.AuthenticationType);
    identities.Add(newIdentity);

    // This makes it available as
    // ServiceSecurityContext.Current.PrimaryIdentity later.
    evaluationContext.Properties["PrimaryIdentity"] = newIdentity;

    // This makes it available as Thread.CurrentPrincipal.
    IPrincipal newPrincipal = new GenericPrincipal(newIdentity, _roles);
    evaluationContext.Properties["Principal"] = newPrincipal;

    return true;
}

private static IList<IIdentity> GetIdentities(
    EvaluationContext evaluationContext)
{
    object identitiesProperty;
    if (!evaluationContext.Properties.TryGetValue(
        "Identities", out identitiesProperty))
    throw new InvalidOperationException("No Identity found");

    var identities = identitiesProperty as IList<IIdentity>;
    if (identities == null)
        throw new InvalidOperationException("No Identity found");
    return identities;
}

And then, having done that lot, you can mark up your service operations with PrincipalPermission:

[PrincipalPermission(SecurityAction.Demand, Role = "Editor")]

Upvotes: 6

Ladislav Mrnka
Ladislav Mrnka

Reputation: 364349

I think it can't because you need to create custom Principal. The reason why I think you can't do it in the validator is because I read somewhere that the validator runs in different thread than operation context. I have never checked it but lets assume it really does. Based on this assumption Principal set in the validator will not be used in WCF operation. You have to create custom autorization or custom role provider.

Upvotes: 2

Related Questions