Partilhar via


Detetar e filtrar chamadores para Ações de Aplicativo no Windows

Este artigo descreve vários mecanismos que os aplicativos de provedor para Ações de Aplicativo no Windows podem implementar para limitar o conjunto de aplicativos que podem consultar ou invocar uma ação. Esta é uma etapa de implementação opcional projetada para dar suporte a cenários como ações que consomem recursos com custos por uso, como serviços LLM baseados em assinatura.

Definir allowedAppInvokers no arquivo JSON de definição de ação

O arquivo JSON de definição de ação, que é usado para registrar uma ação no sistema, inclui um campo allowedAppInvokers para cada definição de ação. O valor desse campo é uma lista de IDs de Modelo de Usuário de Aplicativo (AUMID) que podem descobrir a ação por meio de uma chamada para GetActionsForInputs ou GetAllActions. Caracteres universais são suportados. "*" corresponderá a todos os AppUserModelIDs. Se allowedAppInvokers for omitido ou for uma lista vazia, nenhum aplicativo poderá descobrir a ação. Para obter mais informações sobre AppUserModelIDs, consulte Application User Model IDs. Para obter detalhes do arquivo JSON de definição de ação, consulte Esquema JSON de definição de ação para ações de aplicativo no Windows.

Observe que o campo allowedAppInvokers limita apenas quais aplicativos podem consultar uma ação. Devido às maneiras pelas quais os provedores de ação são registrados no sistema, ainda é possível que os aplicativos invoquem uma ação, mesmo que estejam impedidos de consultar por allowedAppInvokers. O restante deste artigo discute diferentes maneiras de restringir os chamadores para uma ação em tempo de execução.

Identificar a origem da chamada ao usar a ativação de arranque através de URI

A seção a seguir descreve diferentes maneiras de detetar o chamador de uma ação em tempo de execução para aplicativos do provedor de ações que usam a ativação de inicialização de URI.

Obter nome da família do pacote chamador (somente ativação avançada)

Os aplicativos de provedor que usam ativação moderna e avançada podem verificar o valor da propriedade CallerPackageFamilyName da classe ProtocolForResultsActivatedEventArgs passada para seu aplicativo de provedor na inicialização. Se o valor terminar com a cadeia de caracteres "_cw5n1h2txyewy", a ação foi invocada pelo Windows. Para obter mais informações sobre ativação avançada, consulte Ativação avançada com a API de ciclo de vida do aplicativo.

const string windowsPFN = "_cw5n1h2txyewy";
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
    var eventargs = AppInstance.GetCurrent().GetActivatedEventArgs();

    if ((eventargs != null) && (eventargs.Kind == ExtendedActivationKind.ProtocolForResults))
    {

        ProtocolForResultsActivatedEventArgs? protocolForResultsArgs = eventargs.Data as ProtocolForResultsActivatedEventArgs;

        if (protocolForResultsArgs.CallerPackageFamilyName.EndsWith(windowsPFN))
        {
            // Perform the action if invoked by Windows
        }
        
    }

    _window = new MainWindow();
    _window.Activate();
}

Use o parâmetro de cadeia de consulta URI do $.Token

Os provedores de ação que usam a ativação de URI declaram o URI que desencadeia a sua ação no arquivo de manifesto JSON de definição de ação. Há um parâmetro $.Token de cadeia de caracteres de consulta especial que instrui o Action Runtime a passar um token de identificador exclusivo na cadeia de caracteres de consulta do URI de inicialização quando seu aplicativo é invocado. O exemplo a seguir mostra a sintaxe para declarar um URI com o $.Token parâmetro.

"invocation": {
  "type": "Uri",
  "uri": "urilaunchaction-protocol://messageaction?token=${$.Token}",
  "inputData": {
    "message": "${message.Text}"
  }
}

Quando a ação for iniciada, recupere o parâmetro Token da cadeia de caracteres de consulta do URI de ativação. Transmita esse token numa chamada para GetActionInvocationContextFromToken. O Action Runtime validará o token em relação ao token fornecido ao iniciar o provedor de ações. Se os valores corresponderem, um objeto ActionInvocationContext válido será retornado, validando que a ação foi invocada pelo Action Runtime.

protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
    var eventargs = AppInstance.GetCurrent().GetActivatedEventArgs();

    if ((eventargs != null) && (eventargs.Kind == ExtendedActivationKind.ProtocolForResults))
    {

        ProtocolForResultsActivatedEventArgs? protocolForResultsArgs = eventargs.Data as ProtocolForResultsActivatedEventArgs;

        using (ActionRuntime runtime = ActionRuntimeFactory.CreateActionRuntime())
        {

            var launchUri = protocolForResultsArgs.Uri;
            var query = HttpUtility.ParseQueryString(launchUri.Query);
            var token = query["Token"];
            if(token != null)
            {
                var context = runtime.GetActionInvocationContextFromToken(token);
                if(context == null)
                {
                    // Caller is not Action Runtime
                    return;
                }
            }

Observe que este exemplo usa métodos auxiliares fornecidos pelo pacote NuGet Microsoft.AI.Actions. Para obter informações sobre como referenciar este pacote de um provedor de ações, consulte Implementar inicialização de URI para ações de aplicativo no Windows.

Detetar o chamador em tempo de execução a partir da ativação COM

A seção a seguir percorre o processo de recuperação do AUMID do aplicativo que invocou uma ação em tempo de execução. Os exemplos nesta seção verificarão o AUMID do Action Runtime. Os aplicativos podem optar por filtrar o AUMID de outros aplicativos ou usar o PFN (Nome da Família do Pacote) para filtrar todos os aplicativos contidos em um pacote específico.

Adicionar uma referência ao pacote Nuget CsWin32

O código de exemplo mostrado nesta seção usa APIs do Win32 para obter o nome da família do pacote do chamador. Para chamar mais facilmente APIs do Win32 de um aplicativo C#, este exemplo usará o pacote NuGet CsWin32, que gera wrappers C# para APIs do Win32 em tempo de compilação. Para obter mais informações, consulte o repositório github CsWin32.

  1. No Gerenciador de Soluções, clique com o botão direito do mouse em seu projeto e selecione Gerenciar pacotes NuGet....
  2. Na guia Procurar, procure por "Microsoft.Windows.CsWin32".
  3. Selecione este pacote e clique em Instalar, seguindo todos os prompts.

Quando você instala este pacote, ele mostrará um readme detalhando como especificar as APIs do Win32 para as quais os wrappers C# serão gerados. Para fazer isso, você precisará criar um arquivo de texto que lista os métodos que você usará.

  1. No Gerenciador de Soluções, clique com o botão direito do mouse em seu projeto e selecione Adicionar> Novo Item....
  2. Selecione o modelo TextFile .
  3. Nomeie o arquivo "NativeMethods.txt" e clique em Adicionar
  4. Clique duas vezes em NativeMethods.txt para abrir o arquivo e adicionar as seguintes linhas.
CoRegisterClassObject
CoRevokeClassObject
CoImpersonateClient
OpenThreadToken
GetCurrentThread
CoRevertToSelf
GetPackageFamilyNameFromToken
GetLastError

Atualize sua classe ActionProvider para verificar o invocador de ação PFN

O método auxiliar a seguir usa as APIs do Win32 especificadas em NativeMethods.txt para recuperar o AUMID para o processo de chamada e retorna true se ele corresponder ao AUMID que é passado. Para obter explicações detalhadas sobre como esse código funciona, consulte Representar um cliente.

static bool WasInvokedByAUMID(string aumid)
{
    Windows.Win32.Foundation.HRESULT hr = PInvoke.CoImpersonateClient();

    var RPC_E_CALL_COMPLETE = unchecked((int)0x80010117);

    if (hr.Value == RPC_E_CALL_COMPLETE)
    {
        return false;
    }

    Microsoft.Win32.SafeHandles.SafeFileHandle hThreadTok;


    bool bRes = PInvoke.OpenThreadToken(
        new Microsoft.Win32.SafeHandles.SafeFileHandle(PInvoke.GetCurrentThread(), true),
        Windows.Win32.Security.TOKEN_ACCESS_MASK.TOKEN_QUERY,
        true,
        out hThreadTok
    );

    if (bRes == false)
    {
        var e = Marshal.GetLastWin32Error();
        Console.WriteLine("Unable to read thread token. " + e);
        hThreadTok.Close();
        return false;
    }


    PInvoke.CoRevertToSelf();


    var invokerAumid = new Span<char>();
    uint aumidLength = 0;

    PInvoke.GetApplicationUserModelIdFromToken(hThreadTok, ref aumidLength, invokerAumid);
    invokerAumid = new char[aumidLength].AsSpan();
    PInvoke.GetApplicationUserModelIdFromToken(hThreadTok, ref aumidLength, invokerAumid);

    hThreadTok.Close();

    if (invokerAumid.Trim('\0').EndsWith(aumid))
    {
        return true;
    }
    else
    {
        return false;
    }
}

Devido à maneira como o CoImpersonateClient é implementado, é importante que você chame o método auxiliar de dentro da invocação de sua ação e não de dentro do procedimento de inicialização do seu aplicativo.

O exemplo a seguir ilustra uma verificação para o Action Runtime AUMID em uma ação implementada usando a estrutura de geração de código Microsoft.AI.Actions.

const string actionRuntimeAUMID = "_cw5n1h2txyewy!ActionRuntime";
[WindowsActionInputCombination(
    Inputs = ["Contact", "Message"],
    Description = "Send '${Message.Text}' to '${Contact.Text}'"
)]
public async Task<SendMessageResult> SendMessage(
    [Entity(Name = "Contact")] string contact,
    [Entity(Name = "Message")] string? message,
    InvocationContext context)
{
    if (!WasInvokedByAUMID(actionRuntimeAUMID))
    {
        context.Result = ActionInvocationResult.Unavailable;
        return new SendMessageResult
        {
            Text = context.EntityFactory.CreateTextEntity("")
        };
    }
    
    // Your action logic here
    string result = await ProcessMessageAsync(contact, message);

    return new SendMessageResult
    {
        Text = context.EntityFactory.CreateTextEntity(result)
    };
}