Skip to content

Commit

Permalink
CFODEV-472 throw an exception if not authorize attribute exists (or w…
Browse files Browse the repository at this point in the history
…e do not explicitly allow anonymous).

Tests added to break the build if these are detected at run time.

(breaks the build as we have not yet added the authorise test to any command)
  • Loading branch information
carlsixsmith-moj committed Jun 3, 2024
1 parent bb4196a commit 02c0e5b
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 47 deletions.
6 changes: 6 additions & 0 deletions src/Application/Common/Security/AllowAnonymousAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Cfo.Cats.Application.Common.Security;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class AllowAnonymousAttribute : Attribute
{
}
3 changes: 2 additions & 1 deletion src/Application/Common/Security/RequestAuthorizeAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Cfo.Cats.Application.Common.Security;


public class RequestAuthorizeAttribute : Attribute
{
/// <summary>
Expand All @@ -18,4 +19,4 @@ public RequestAuthorizeAttribute() { }
/// Gets or sets the policy name that determines access to the resource.
/// </summary>
public string Policy { get; set; } = string.Empty;
}
}
103 changes: 57 additions & 46 deletions src/Application/Pipeline/AuthorizationBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,68 +29,79 @@ CancellationToken cancellationToken
.GetType()
.GetCustomAttributes<RequestAuthorizeAttribute>()
.ToArray();
if (authorizeAttributes.Any())

if (authorizeAttributes.Any() == false)
{
// Must be authenticated user
var userId = currentUserService.UserId;
if (userId is null or 0)
// if we have no authorization attribute, then we must explicitly allow all or error
var anyUserAttributes = request.GetType()
.GetCustomAttributes<AllowAnonymousAttribute>()
.SingleOrDefault();

if (anyUserAttributes == null)
{
throw new UnauthorizedAccessException();
throw new UnauthorizedAccessException("Invalid authorization configuration.");
}
}

// DefaultRole-based authorization
var authorizeAttributesWithRoles = authorizeAttributes
.Where(a => !string.IsNullOrWhiteSpace(a.Roles))
.ToArray();
// Must be authenticated user
var userId = currentUserService.UserId;
if (userId is null or 0)
{
throw new UnauthorizedAccessException();
}

if (authorizeAttributesWithRoles.Any())
{
var authorized = false;
// DefaultRole-based authorization
var authorizeAttributesWithRoles = authorizeAttributes
.Where(a => !string.IsNullOrWhiteSpace(a.Roles))
.ToArray();

if (authorizeAttributesWithRoles.Any())
{
var authorized = false;

foreach (var roles in authorizeAttributesWithRoles.Select(a => a.Roles.Split(',')))
foreach (var roles in authorizeAttributesWithRoles.Select(a => a.Roles.Split(',')))
{
foreach (var role in roles)
{
foreach (var role in roles)
var isInRole = await identityService.IsInRoleAsync(
userId.Value,
role.Trim(),
cancellationToken
);
if (isInRole)
{
var isInRole = await identityService.IsInRoleAsync(
userId.Value,
role.Trim(),
cancellationToken
);
if (isInRole)
{
authorized = true;
break;
}
authorized = true;
break;
}
}
}

// Must be a member of at least one role in roles
if (!authorized)
{
throw new ForbiddenException("You are not authorized to access this resource.");
}
// Must be a member of at least one role in roles
if (!authorized)
{
throw new ForbiddenException("You are not authorized to access this resource.");
}
}

// Policy-based authorization
var authorizeAttributesWithPolicies = authorizeAttributes
.Where(a => !string.IsNullOrWhiteSpace(a.Policy))
.ToArray();
if (authorizeAttributesWithPolicies.Any())
// Policy-based authorization
var authorizeAttributesWithPolicies = authorizeAttributes
.Where(a => !string.IsNullOrWhiteSpace(a.Policy))
.ToArray();
if (authorizeAttributesWithPolicies.Any())
{
foreach (var policy in authorizeAttributesWithPolicies.Select(a => a.Policy))
{
foreach (var policy in authorizeAttributesWithPolicies.Select(a => a.Policy))
var authorized = await identityService.AuthorizeAsync(
userId.Value,
policy,
cancellationToken
);

if (!authorized)
{
var authorized = await identityService.AuthorizeAsync(
userId.Value,
policy,
cancellationToken
throw new ForbiddenException(
"You are not authorized to access this resource."
);

if (!authorized)
{
throw new ForbiddenException(
"You are not authorized to access this resource."
);
}
}
}
}
Expand Down
47 changes: 47 additions & 0 deletions test/ArchitectureTests/ApplicationTests/RequestTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Cfo.Cats.Application.Common.Interfaces.Caching;
using Cfo.Cats.Application.Common.Security;
using Cfo.Cats.Domain.Common;
using FluentAssertions;
using MediatR;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client;
using NetArchTest.Rules;
using NUnit.Framework;

namespace Cfo.Cats.Domain.ArchitectureTests.ApplicationTests;

public class RequestTests
{
private static readonly Assembly ApplicationAssembly = typeof(Application.DependencyInjection).Assembly;

[Test]
public void Commands_Should_HaveAuthorizeAttribute()
{
var result = Types.InAssembly(ApplicationAssembly)
.That()
.ImplementInterface(typeof(IRequest<>))
.Or()
.ImplementInterface(typeof(ICacheableRequest<>))
.Or()
.ImplementInterface(typeof(ICacheInvalidatorRequest<>))
.Should()
.HaveCustomAttribute(typeof(AuthorAttribute))
.Or()
.HaveCustomAttribute(typeof(AllowAnonymousAttribute))
.GetResult();

var failedTypes = result.FailingTypes?.Select(t => t.FullName).ToList();

var formattedFailedTypes = failedTypes == null ? "None" : string.Join("\n", failedTypes);

result.IsSuccessful
.Should()
.BeTrue($"The following types failed the test:\n {formattedFailedTypes}");

}


}

0 comments on commit 02c0e5b

Please sign in to comment.