Policy Enforcement Point

There are different types of Policy Enforcement Points in the Altinn 3 platform.

These are constructed in different ways.

Standard PEP

One important principle we follow is that security should be configured when possible. This means that we have developed some standard policy enforcement points that can be configured on the different API endpoints.

Attribute-based authorization is best solved with Policy-Based Authorization in ASP.NET Core

The different standard PEP’s in the ASP.Net Web application template is created as Authorization Handlers.

See AppAccessHandler for PEP for checking app policy for an API.

See ScopeAccessHandler for PEP validating scope requirements

In the App there is defined a set of AuthorizationRequirements and for each operation of the different API endpoints needs to be configured with the correct requirement.

Example on requirements are:

  • InstanceRead (User/system needs to be authorized to perform read action on the instance in current state)
  • InstanceWrite (User/system needs to be authorized to perform write action on the instance and its data in current state)
  • InstanceInstantiate (user/system needs to be authorized to Instantiate an instance for an app)

The PEP will based on route data (like instanceId) and the authenticated Identity create a decision request and call PDP. Based on the response the PEP will deny or approve the user. (Deny = http 403)

The PEP validates any obligation from the PDP like minimum authentication level. If this is not valid, the request will be denied (HTTP 403).

Configuration

The application needs to have a startup configuration to enable the different standard PEPs

         services.AddAuthorization(options =>
            {
                options.AddPolicy(AuthzConstants.POLICY_INSTANCE_READ, policy => policy.Requirements.Add(new AppAccessRequirement("read")));
                options.AddPolicy(AuthzConstants.POLICY_INSTANCE_WRITE, policy => policy.Requirements.Add(new AppAccessRequirement("write")));
                options.AddPolicy(AuthzConstants.POLICY_INSTANCE_DELETE, policy => policy.Requirements.Add(new AppAccessRequirement("delete")));
                options.AddPolicy(AuthzConstants.POLICY_INSTANCE_COMPLETE, policy => policy.Requirements.Add(new AppAccessRequirement("complete")));
                options.AddPolicy(AuthzConstants.POLICY_SCOPE_APPDEPLOY, policy => policy.Requirements.Add(new ScopeAccessRequirement("altinn:appdeploy")));
                options.AddPolicy(AuthzConstants.POLICY_SCOPE_INSTANCE_READ, policy => policy.Requirements.Add(new ScopeAccessRequirement("altinn:instances.read")));
                options.AddPolicy(AuthzConstants.POLICY_STUDIO_DESIGNER, policy => policy.Requirements.Add(new ClaimAccessRequirement("urn:altinn:app", "studio.designer")));
            });

Example from Storage Startup

The API needs to have enabled PEP for a given API operation

     [Authorize(Policy = AuthzConstants.POLICY_INSTANCE_WRITE)]
        [HttpDelete("data/{dataGuid:guid}")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        [Produces("application/json")]
        public async Task<ActionResult<DataElement>> Delete(int instanceOwnerPartyId, Guid instanceGuid, Guid dataGuid)
        {

Example from DataController

Custom PEP

For some scenarious it is not possible to authorize the request based on API parameters.

This cases requires a custom PEP that is implemented as part of API logic.

In the example below a list of elements is retreived from database and we need to filter elements before they are returned based on what user is authorized for.

       [Authorize]
        [HttpGet("{instanceOwnerPartyId:int}/{instanceGuid:guid}")]
        public async Task<ActionResult> GetMessageBoxInstance(
            int instanceOwnerPartyId,
            Guid instanceGuid,
            [FromQuery] string language)
        {
            string[] acceptedLanguages = { "en", "nb", "nn" };
            string languageId = "nb";

            if (language != null && acceptedLanguages.Contains(language.ToLower()))
            {
                languageId = language;
            }

            string instanceId = $"{instanceOwnerPartyId}/{instanceGuid}";

            Instance instance = await _instanceRepository.GetOne(instanceId, instanceOwnerPartyId);

            if (instance == null)
            {
                return NotFound($"Could not find instance {instanceId}");
            }

            List<MessageBoxInstance> authorizedInstanceList =
                await _authorizationHelper.AuthorizeMesseageBoxInstances(
                    HttpContext.User, new List<Instance> { instance });
            if (authorizedInstanceList.Count <= 0)
            {
                return Forbid();
            }

            MessageBoxInstance authorizedInstance = authorizedInstanceList.First();

            // get app texts and exchange all text keys.
            List<TextResource> texts = await _textRepository.Get(new List<string> { instance.AppId }, languageId);
            InstanceHelper.ReplaceTextKeys(new List<MessageBoxInstance> { authorizedInstance }, texts, languageId);

            return Ok(authorizedInstance);
        }

Example from MessageboxInstancesController