Writing a UPS Policy Evaluator

Introduction

Policy Evaluators are ECOM plug-ins. They allow security decisions to be specific to the data on which the service acts instead of simply granting full access to the service, for example "Allow application X to send SMS messages to 01234567". This is supported through the fingerprint functionality. They allow a security decision to be specific to an individual script executing within a scripting host. This is supported through the client entity field. And they allow a prompt to be displayed even if the user selected "Always" or "Never". This could be based on a usage threshold or simply as additional confirmation if the user selected "Never". This is supported through the force prompt functionality.

Note: when a user selects “Never” or “Always”, the UPS creates a record in a decision database. This record identifies the system server UID, service UID, client SID, client entity name (name of an entity running within a client process, for example, a script identifier) and a "fingerprint" for a given decision.

The purpose of a Policy Evaluator is to allow device creators to optionally customise the behaviour of the UPS at run-time. Policy Evaluators may:

  • Allow security decisions to be specific to the data on which the service acts instead of simply granting full access to the service, for example "Allow application X to send SMS messages to 01234567". This is supported via the fingerprint functionality.

  • Allow a security decision to be specific to an individual script executing within a scripting host. This is supported via the client entity field.

  • Allow a prompt to be displayed even if the user selected "Always" or "Never". This could be based on a usage threshold or simply as additional confirmation if the user selected "Never". This is supported via the force prompt functionality.

Procedure

Writing policy evaluator includes the following:

  1. Generating fingerprints

  2. Forcing prompts

  3. Defining default policy evaluator

Policy evaluators implement the CPolicyEvaluator interface.

Generating fingerprints

The key task of the policy evaluator is to generate fingerprints. A fingerprint is a collection of data describing the service being requested. The fingerprint usually includes the destination string, for example a telephone number, supplied by the service that is matched to the destination string of a policy in the policy file.

The policy evaluator must implement the GenerateFingerprints() function, which is a pure virtual function, declared in the CPolicyEvaluator base class. The function is asynchronous to allow the implementation to call other server processes.

The following table describes the parameters for the GenerateFingerprints() function:

Parameter Description

aRequest

Data that the system server provides to the UPS, including the ClientSid, ServerSid and ServiceId.

aPolicy

Details of the policy for the service.

aFingerprints

Array of fingerprints.

aClientEntity

Details of client entity (will be null if the client process is not an execution host or the policy evaluator does not support client entities).

aDialogCreatorParams

Opaque data, which can be any other information for use in the fingerprint or to be displayed in the dialog.

aStatus

The request status used to contain completion information for the function.

The following code fragment is a sample implementation of the GenerateFingerprints() function.

void CRefPolicyEvaluator::GenerateFingerprints(
 const CPromptRequest& aRequest, const CPolicy& aPolicy, 
    RPointerArray<CFingerprint>& aFingerprints, const CClientEntity*& aClientEntity, 
    const TAny*& aDialogCreatorParams, TRequestStatus& aStatus)
    {
    iRequest = &aRequest;
    iPolicy = &aPolicy;
    iFingerprints = &aFingerprints;        

    // This OUT parameter *could* be set to point to data structure
    // owned by this policy evaluator that is read by the dialog-creator.
    aDialogCreatorParams = 0;
    
    // The client entity field only needs to be set if the policy evaluator
    // is designed to identify scripts/non-native applications being executed
    // by the client application.
    aClientEntity = 0;                
    
    iClientStatus = &aStatus;
    aStatus = KRequestPending;
    
    // Kick off policy evaluator state machine
    iStatus = KRequestPending;
    TRequestStatus* status = &iStatus;
    SetActive();
    User::RequestComplete(status, KErrNone);
    }

RunL() is called when the request completes. The fingerprint is created and appended to the fingerprint array in RunL(). The following example shows a hash of a destination being created for a fingerprint as well as an empty fingerprint for decisions that apply to all destinations.

void CRefPolicyEvaluator::RunL()
    {    
    // Create most specific hash first i.e. HASH(destination)
    // Information from the opaque data supplied by the system server 
    // may also be used.
    // N.B. The UPS does not require the fingerprint to be a hash. However,
    // the fingerprint is limited to 32 bytes.    
    iDigest->Reset();
    const TDesC& d = iRequest->Destination();
    TPtrC8 p(reinterpret_cast<const TUint8*>(d.Ptr()), d.Length() * 2);
    TPtrC8 h(iDigest->Hash(p));
    
    CFingerprint* f = CFingerprint::NewLC(h, d);
    iFingerprints->AppendL(f);
    CleanupStack::Pop(f);

    // An empty fingerprint may be used for decisions that apply to
    // all destinations.
    f = CFingerprint::NewLC(KNullDesC8, KNullDesC);
    iFingerprints->AppendL(f);
    CleanupStack::Pop(f);
        
    User::RequestComplete(iClientStatus, KErrNone);
    }

Forcing prompts

You can implement the ForcePromptL() function to force a prompt, even if the UPS has found a persistent decision for the request in the decision database. It may be desirable to force a prompt in some cases such as:

  • if a threshold has been reached, for example the number of MMS messages sent on a particular day

  • if a phone user has selected "Never", because denying a request to a non-UPS-aware application may cause it to fail — the user may not realise that the previous selection of "Never" has caused the failure or may not know how to find the management application to revert the decision

The UPS invokes the ForcePromptL() function after a matching record is found in the database. The ForcePromptL() function is defined in the CPolicyEvaluator base class. The implementation in the base class always returns EFalse. To force a prompt, device creators need to override this function and return ETrue. A sample implementation is as follows. The aNewEvaluatorInfo field is a policy evaluator defined 32-bit field. It could be used to store usage thresholds or as a flag that indicates that the prompt has already been forced.

TBool CRefPolicyEvaluator::ForcePromptL(const CDecisionRecord& aDecision, TUint& aNewEvaluatorInfo)
    {
    // In this example, if the user selects Never, then they are prompted
    // once more in-case they did not intend to select Never. 
    // The "Evaluator Info" field is used to ensure the prompt is only forced once.
    //
    // The base class implementation (CPolicyEvaluator::ForcePromptL) 
    // always returns EFalse.
    aNewEvaluatorInfo = 1;
    return (aDecision.iResult == 0 && aDecision.iEvaluatorInfo == 0);
    }

Defining default policy evaluator

If device creators do not define a policy evaluator, a default (internal) policy evaluator is returned. The default policy evaluator returns a single, null fingerprint. The default policy evaluator does not override the ForcePromptL() API.

Upgrading policy evaluators

A policy evaluator can be overwritten or eclipsed without restarting the UPS, if it is delivered through an appropriately signed upgrade.

  • The ECOM plug-in will be reloaded only when there are no active RUpsSubsession::Authorise() requests.

  • The decision records are not deleted if the policy evaluator is changed. If the policy evaluator is not backwards compatible then new policy files with a new majorversion number should be delivered.

  • SWI Observer informs the UPS that the plug-ins may have changed whenever Software Install modifies sys\bin on the system drive. There is no need to explicitly register changes to plug-ins.

Policy evaluator example

The following code shows an example of a full implementation of the policy evaluator file:

#include "refpolicyevaluator.h"
#include <ecom/implementationproxy.h>
#include <ups/cliententity.h>
#include <ups/fingerprint.h>
#include <ups/upsdb.h>

using namespace UserPromptService;

static const TUint KRefPolicyEvaluatorImplementationId = 0x10285814;

CPolicyEvaluator* CRefPolicyEvaluator::CreatePolicyEvaluatorL()
/**
Factory method that instantiates a new policy evaluator ECOM plug-in.

@return A pointer to the new reference policy evaluator object.
*/
    {
    CRefPolicyEvaluator* self = new (ELeave)CRefPolicyEvaluator();
    CleanupStack::PushL(self);
    self->ConstructL();
    CleanupStack::Pop(self);
    return self;
    }

static const TImplementationProxy ImplementationTable[] = 
    {
    IMPLEMENTATION_PROXY_ENTRY(KRefPolicyEvaluatorImplementationId, CRefPolicyEvaluator::CreatePolicyEvaluatorL)
    };

EXPORT_C const TImplementationProxy* ImplementationGroupProxy(TInt& aTableCount)
/**
Standard ECOM factory
*/
    {
    aTableCount = sizeof(ImplementationTable) / sizeof(TImplementationProxy);
    return ImplementationTable;
    }    
    

CRefPolicyEvaluator::CRefPolicyEvaluator()
/**
Constructor
*/
    : CPolicyEvaluator()
    {
    CActiveScheduler::Add(this);
    }
    
CRefPolicyEvaluator::~CRefPolicyEvaluator()
/**
Destructor
*/
    {
    Deque();
    delete iDigest;
    }

void CRefPolicyEvaluator::ConstructL()
/**
Second phase constructor, creates the message digest
*/
    {    
    iDigest = CMessageDigestFactory::NewDigestL(CMessageDigest::EMD5);
    }

// From CActive
void CRefPolicyEvaluator::DoCancel()
    {    
    // Logically should Cancel the internal outstanding requst, but
    // currently GenerateFingerprints has already completed it.

    // And need to complete the clients request
    if (iClientStatus)
        {
        User::RequestComplete(iClientStatus, KErrCancel);
        }
    }
    
TInt CRefPolicyEvaluator::RunError(TInt aError)
    {
    if (iClientStatus)
        {
        User::RequestComplete(iClientStatus, aError);
        }
    return KErrNone;
    }
    
void CRefPolicyEvaluator::RunL()
    {    
    // Create most specific hash first i.e. HASH(destination)
    // Information from the opaque data supplied by the system server 
    // may also be used.
    // N.B. The UPS does not require the fingerprint to be a hash. However,
    // the fingerprint is limited to 32 bytes.    
    iDigest->Reset();
    const TDesC& d = iRequest->Destination();
    TPtrC8 p(reinterpret_cast<const TUint8*>(d.Ptr()), d.Length() * 2);
    TPtrC8 h(iDigest->Hash(p));
    
    CFingerprint* f = CFingerprint::NewLC(h, d);
    iFingerprints->AppendL(f);
    CleanupStack::Pop(f);

    // An empty fingerprint may be used for decisions that apply to
    // all destinations.
    f = CFingerprint::NewLC(KNullDesC8, KNullDesC);
    iFingerprints->AppendL(f);
    CleanupStack::Pop(f);
        
    User::RequestComplete(iClientStatus, KErrNone);
    }

void CRefPolicyEvaluator::GenerateFingerprints(
    const CPromptRequest& aRequest, const CPolicy& aPolicy, 
    RPointerArray<CFingerprint>& aFingerprints, const CClientEntity*& aClientEntity, 
    const TAny*& aDialogCreatorParams, TRequestStatus& aStatus)
    {
    iRequest = &aRequest;
    iPolicy = &aPolicy;
    iFingerprints = &aFingerprints;        

    // This OUT parameter *could* be set to point to data structure
    // owned by this policy evaluator that is read by the dialog-creator.
    aDialogCreatorParams = 0;
    
    // The client entity field only needs to be set if the policy evaluator
    // is designed to identify scripts/non-native applications being executed
    // by the client application.
    aClientEntity = 0;                
    
    iClientStatus = &aStatus;
    aStatus = KRequestPending;
    
    // Kick off policy evaluator state machine
    iStatus = KRequestPending;
    TRequestStatus* status = &iStatus;
    SetActive();
    User::RequestComplete(status, KErrNone);
    }

TBool CRefPolicyEvaluator::ForcePromptL(const CDecisionRecord& aDecision, TUint& aNewEvaluatorInfo)
    {
    // In this example, if the user selects Never, then they are prompted
    // once more in-case they did not intend to select Never. 
    // The "Evaluator Info" field is used to ensure the prompt is only forced once.
    //
    // The base class implementation (CPolicyEvaluator::ForcePromptL) 
    // always returns EFalse.
    aNewEvaluatorInfo = 1;
    return (aDecision.iResult == 0 && aDecision.iEvaluatorInfo == 0);
    }