Skip to content

Add support for role based security in ATC generator. #26

Closed
@MrAndersPedersen

Description

@MrAndersPedersen

Tasks:

  • Define custom OpenApi x-roles #137
    • Check if $ref can be used
  • Implement generator
    • Read schema
    • Validate security schema
    • Validate path schema
    • Validate role usage
    • Generate Authorize attribute
    • Demo project sample

Design

Setup in yaml file will be done using security schemes and the first type to support is OAuth2 with client id + client secret as authetication. Custom roles will be used to support access based on rights.

This can be done like shown below:

Definition of the security schema:

components:
  securitySchemes:

    oAuth2Sample:
      x-atc-azure-ad-application-roles:
          api.execute.all: 
            description:Access to all operations
            name: everything
          api.execute.read: 
            description: Access to smart charging operations.
            name: read

      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: 'https://login.microsoftonline.com/7b8ae8aa-f7d3-4bfe-a333-eb138ce54b98/oauth2/v2.0/token'
          refreshUrl: ''
          scopes:
            .default 
      description: OAuth2 using client id + client secret

Usage in a operation:

paths:
  '/items/{id}':
    put:
      summary: 'Updates an item'
      description: 'Updates an item'
      operationId: updateItem
      responses:
        '200':
          description: OK
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateItemRequest'
      parameters:
        - name: id
          in: path
          description: The id of the order
          required: true
          schema:
            type: string
            format: uuid
      security:
        - oAuth2Sample:
            - .default
        - x-atc-azure-ad-application-roles:
            - api.execute.read (can this be a $ref?)
            - api.execute.write

The generated code will affect the controller class(es) and will add an Authorize attribute to each method that is setup with security in its operation definition. The Roles property in the Authorize attribute maps to OAuth2 scopes and these are defined in the securiy scheme.

As the Roles property of the Authorize attribute treats multiple attributes on the same method as AND requirements it will be needed to have only 1 attribute per method to support OR and the roles will have to be comma separated. To make the code a bit nicer a static class with scope definitions are generated for each defined security schema.

Make sure Swagger UI shows the required roles per operation.

Validation:
ATC generator will validate the setup:

  • Unused role(s) (warning)
  • Undefined role(s) (error)
  • Roles not defined in OAuth2 schema (error)

The generated code could look something like this:

namespace Demo.Api.Generated.Security
{
    public static class OAuth2ClientCredentials
    {
        // Single roles
        public const string ApiExecuteAll = "api.execute.all";
        public const string ApiExecuteRead = "api.execute.read";

        // Combined roles
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Better readability for combined scopes.")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Better readability for combined scopes.")]
        public const string ApiExecuteAll_ApiExecuteRead = ApiExecuteAll + "," + ApiExecuteRead;
    }
}
namespace Demo.Api.Generated.Endpoints
{
    /// <summary>
    /// Endpoint definitions.
    /// Area: Items.
    /// </summary>
    [ApiController]
    [Authorize]
    [Route("api/v1/items")]
    [GeneratedCode("ApiGenerator", "1.0.216.0")]
    public class ItemsController : ControllerBase
    {
        /// <summary>
        /// Description: Updates an item.
        /// Operation: UpdateItem.
        /// Area: Items.
        /// </summary>
        [HttpPut("{id}")]
        [Authorize(Roles = OAuth2ClientCredentials.ApiExecuteAll_ApiExecuteRead)]
        [ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
        public Task<ActionResult> UpdateItemAsync(UpdateItemParameters parameters, [FromServices] IUpdateItemHandler handler, CancellationToken cancellationToken)
        {
            if (handler is null)
            {
                throw new ArgumentNullException(nameof(handler));
            }

            return InvokeUpdateItemAsync(parameters, handler, cancellationToken);
        }

        private static async Task<ActionResult> InvokeUpdateItemAsync(UpdateItemParameters parameters, IUpdateItemHandler handler, CancellationToken cancellationToken)
        {
            return await handler.ExecuteAsync(parameters, cancellationToken);
        }
    }
}

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions