Compartir a través de


Detección y filtrado de autores de llamadas para acciones de aplicación en Windows

En este artículo se describen varios mecanismos que las aplicaciones de proveedor para acciones de aplicación en Windows pueden implementar para limitar el conjunto de aplicaciones que pueden consultar o invocar una acción. Se trata de un paso de implementación opcional diseñado para admitir escenarios como acciones que consumen recursos que tienen costos por uso, como los servicios LLM basados en suscripciones.

Establece allowedAppInvokers en el archivo JSON de definición de acción

El archivo JSON de definición de acción, que se usa para registrar una acción con el sistema, incluye un campo allowedAppInvokers para cada definición de acción. El valor de este campo es una lista de identificadores de modelo de usuario de aplicación (AUMID) que pueden detectar la acción a través de una llamada a GetActionsForInputs o GetAllActions. Se admite caracteres comodín. "*" coincidirá con todos los AppUserModelIDs. Si allowedAppInvokers se omite o es una lista vacía, ninguna aplicación podrá detectar la acción. Para obtener más información sobre AppUserModelIDs, consulte Identificadores de modelo de usuario de aplicación. Para obtener más información sobre el archivo JSON de definición de acción, consulte Esquema JSON de definición de acción para Acciones de aplicación en Windows.

Tenga en cuenta que el campo allowedAppInvokers solo limita qué aplicaciones pueden consultar para una acción. Debido a las formas en que los proveedores de acciones están registrados en el sistema, todavía es posible que las aplicaciones invoquen una acción, incluso si están restringidas a realizar consultas mediante allowedAppInvokers. En el resto de este artículo se describen diferentes formas de restringir los autores de llamadas para una acción en tiempo de ejecución.

Detección del llamador al usar la activación de lanzamiento de URI

En la sección siguiente se describen diferentes formas de detectar al llamador de una acción en tiempo de ejecución para aplicaciones proveedoras de acciones que usan activación mediante URI.

Obtener el nombre de familia del paquete de llamada (solo activación enriquecida)

Las aplicaciones de proveedor que usan la activación moderna y enriquecida pueden comprobar el valor de la propiedad CallerPackageFamilyName de la clase ProtocolForResultsActivatedEventArgs que se pasa a la aplicación de proveedor al iniciarse. Si el valor termina con la cadena "_cw5n1h2txyewy", Windows invocó la acción. Para más información sobre la activación enriquecida, consulte Activación enriquecida con la API del ciclo de vida de la aplicación.

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();
}

Utilice el parámetro de cadena de consulta URI $.Token

Los proveedores de acciones que usan la activación de URI declaran el URI que inicia su acción en el archivo de manifiesto JSON de definición de acción. Hay un parámetro especial de cadena de consulta $.Token que indica al Runtime de Acción que pase un token de identificador único en la cadena de consulta del URI de inicio cuando se invoque tu aplicación. En el ejemplo siguiente se muestra la sintaxis para declarar un URI con el $.Token parámetro .

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

Cuando se inicie la acción, recupere el parámetro de la cadena de consulta Token del URI de activación. Pase este token en una llamada a GetActionInvocationContextFromToken. El tiempo de ejecución de la acción validará el token con el token que proporcionó al iniciar el proveedor de acciones. Si los valores coinciden, se devuelve un objeto ActionInvocationContext válido, lo que valida que el runtime de acción invocó la acción.

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;
                }
            }

Tenga en cuenta que en este ejemplo se usan métodos auxiliares proporcionados por el paquete NuGet Microsoft.AI.Actions. Para obtener información sobre cómo hacer referencia a este paquete desde un proveedor de acciones, consulte Implementar el lanzamiento de URI para acciones de aplicaciones en Windows.

Detectar al llamante en tiempo de ejecución desde la activación COM

En la siguiente sección se explica el proceso de recuperación del AUMID de la aplicación que invocó una acción en tiempo de ejecución. Los ejemplos de esta sección comprobarán el AUMID del Action Runtime. Las aplicaciones podrían optar por filtrar por el AUMID de otras aplicaciones o usar el Nombre de familia de paquetes (PFN) para filtrar en todas las aplicaciones que se encuentran en un paquete determinado.

Agregar una referencia al paquete Nuget CsWin32

El código de ejemplo que se muestra en esta sección usa las API de Win32 para obtener el nombre de familia del paquete del autor de la llamada. Para llamar más fácilmente a las API de Win32 desde una aplicación de C#, en este ejemplo se usará el paquete NuGet CsWin32, que genera contenedores de C# para las API de Win32 en tiempo de compilación. Para obtener más información, consulte el repositorio de GitHub CsWin32.

  1. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto y seleccione Administrar paquetes NuGet....
  2. En la pestaña Examinar , busque "Microsoft.Windows.CsWin32".
  3. Seleccione este paquete y haga clic en Instalar, siguiendo todas las indicaciones.

Al instalar este paquete, se mostrará un archivo léame que detalla el procedimiento para especificar las API de Win32 para las que se generarán los envoltorios de C#. Para ello, deberá crear un archivo de texto que muestre los métodos que va a usar.

  1. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto y seleccione Agregar nuevo> elemento....
  2. Seleccione la plantilla TextFile .
  3. Asigne al archivo el nombre "NativeMethods.txt" y haga clic en Agregar.
  4. Haga doble clic en NativeMethods.txt para abrir el archivo y agregar las líneas siguientes.
CoRegisterClassObject
CoRevokeClassObject
CoImpersonateClient
OpenThreadToken
GetCurrentThread
CoRevertToSelf
GetPackageFamilyNameFromToken
GetLastError

Actualice la clase ActionProvider para comprobar el PFN del invocador de acciones.

El siguiente método auxiliar usa las API de Win32 especificadas en NativeMethods.txt para recuperar el AUMID para el proceso de llamada y devuelve true si coincide con el AUMID que se pasa. Para obtener una explicación detallada sobre cómo funciona este código, consulte Suplantación de un 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;
    }
}

Debido a la forma en que se implementa CoImpersonateClient, es importante llamar al método auxiliar desde dentro de la invocación de la acción y no desde el procedimiento de inicio de la aplicación.

En el ejemplo siguiente se muestra una comprobación de Action Runtime AUMID en una acción implementada mediante el marco de generación 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)
    };
}