Compartilhar via


Detectar 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. Essa é uma etapa de implementação opcional projetada para dar suporte a cenários como ações que consomem recursos que têm 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 com o sistema, inclui um campo allowedAppInvokers para cada definição de ação. O valor desse campo é uma lista de IDs do Modelo de Usuário do Aplicativo (AUMID) que podem descobrir a ação por meio de uma chamada para GetActionsForInputs ou GetAllActions. Os caracteres curinga têm suporte. "*" 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 IDs do modelo de usuário do aplicativo. Para obter detalhes do arquivo JSON de definição de ação, consulte O 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 para uma ação. Devido às maneiras pelas quais os provedores de ações são registrados com o sistema, ainda é possível que os aplicativos invoquem uma ação, mesmo que sejam impedidos de consultar por allowedAppInvokers. O restante deste artigo discute diferentes maneiras de restringir os chamadores para uma ação em runtime.

Detectar o chamador ao usar a ativação de lançamento do URI

A seção a seguir descreve diferentes maneiras de detectar o chamador de uma ação durante a execução para aplicativos do provedor de ações que usam ativação por lançamento de URI.

Obter o nome da família do pacote do 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 provedor na inicialização. Se o valor terminar com a cadeia de caracteres "_cw5n1h2txyewy", a ação será 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 caracteres de consulta URI do $.Token

Os provedores de ação que usam a ativação de URI declaram o URI que inicia 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 de cadeia de caracteres de consulta do URI de ativação. Passe esse token em uma chamada de 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 esse pacote de um provedor de ações, consulte Implementar o lançamento de URI para Ações para Aplicativos no Windows.

Detectar o chamador em runtime da ativação COM

A seção a seguir explica o processo de obtençã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 pelo 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 Win32 para obter o nome da família de pacotes do chamador. Para chamar mais facilmente as APIs Win32 de um aplicativo C#, este exemplo usará o pacote NuGet CsWin32, que gera wrappers C# para APIs Win32 no momento do build. 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, pesquise por "Microsoft.Windows.CsWin32".
  3. Selecione este pacote e clique em Instalar, seguindo todos os prompts.

Ao instalar este pacote, ele mostrará um README explicando como especificar as APIs Win32 para as quais os wrappers C# serão gerados. Para fazer isso, você precisará criar um arquivo de texto que liste 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 como "NativeMethods.txt" e clique em Adicionar
  4. Clique duas vezes NativeMethods.txt para abrir o arquivo e adicionar as linhas a seguir.
CoRegisterClassObject
CoRevokeClassObject
CoImpersonateClient
OpenThreadToken
GetCurrentThread
CoRevertToSelf
GetPackageFamilyNameFromToken
GetLastError

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

O método auxiliar a seguir usa as APIs Win32 especificadas em NativeMethods.txt para recuperar o AUMID para o processo de chamada e retorna true se corresponder ao AUMID que é passado. Para obter uma explicação detalhada sobre como esse código funciona, consulte Como 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 aplicativo.

O exemplo a seguir ilustra uma verificação do AUMID do Action Runtime 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)
    };
}