simpleidserver / SimpleIdServer

OpenID, OAuth 2.0, SCIM2.0, UMA2.0, FAPI, CIBA & OPENBANKING Framework for ASP.NET Core
https://simpleidserver.com/
Apache License 2.0
682 stars 90 forks source link

[SCIM] AmbiguousActionException: Multiple actions matched. #762

Closed johanndev closed 2 weeks ago

johanndev commented 2 weeks ago

I was trying to follow the guide from this comment.

Template version: SimpleIdServer.Templates (version 5.0.0)

Steps to reproduce

  1. Create a new project with the scim template: dotnet new scim -n SCIM.API --connectionString "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SCIM_New;Integrated Security=True;TrustServerCertificate=True" -t "SQLSERVER"
  2. Add custom resource schema:
    var customResource = SCIMSchemaBuilder.Create("urn:customresource", "CustomResources", "CustomResources")
      .AddStringAttribute("name")
      .AddStringAttribute("lastname")
      .Build();
    ...
    context.SCIMSchemaLst.Add(customResource);
  3. Add custom Controller:
    [Route("CustomResources")]
    public class CustomResourcesController : BaseApiController
    {
    public CustomResourcesController(
        IAddRepresentationCommandHandler addRepresentationCommandHandler,
        IDeleteRepresentationCommandHandler deleteRepresentationCommandHandler,
        IReplaceRepresentationCommandHandler replaceRepresentationCommandHandler,
        IPatchRepresentationCommandHandler patchRepresentationCommandHandler,
        ISearchRepresentationsQueryHandler searchRepresentationsQueryHandler,
        IGetRepresentationQueryHandler getRepresentationQueryHandler,
        IAttributeReferenceEnricher attributeReferenceEnricher,
        IOptionsMonitor<SCIMHostOptions> options,
        ILogger<CustomResourcesController> logger,
        IBusControl busControl,
        IResourceTypeResolver resourceTypeResolver,
        IUriProvider uriProvider,
        IRealmRepository realmRepository) : base(
            "CustomResources",
            addRepresentationCommandHandler,
            deleteRepresentationCommandHandler,
            replaceRepresentationCommandHandler,
            patchRepresentationCommandHandler,
            searchRepresentationsQueryHandler,
            getRepresentationQueryHandler,
            attributeReferenceEnricher,
            options,
            logger,
            busControl,
            resourceTypeResolver,
            uriProvider,
            realmRepository)
    { }
    }
  4. Test the custom resource:
    GET https://localhost:5003/CustomResources HTTP/1.1
    Authorization: Bearer ba521....

    Result:

    Request matched multiple actions resulting in ambiguity. Matching actions: 
    SCIM.API.CustomResourcesController.GetAll (SCIM.API)
    SCIM.API.CustomResourcesController.Get (SCIM.API)
Full log output ``` fail: Microsoft.AspNetCore.Mvc.Infrastructure.ActionSelector[1] Request matched multiple actions resulting in ambiguity. Matching actions: SCIM.API.CustomResourcesController.GetAll (SCIM.API) SCIM.API.CustomResourcesController.Get (SCIM.API) fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1] An unhandled exception has occurred while executing the request. Microsoft.AspNetCore.Mvc.Infrastructure.AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied: SCIM.API.CustomResourcesController.GetAll (SCIM.API) SCIM.API.CustomResourcesController.Get (SCIM.API) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionSelector.SelectBestCandidate(RouteContext context, IReadOnlyList`1 candidates) at Microsoft.AspNetCore.Mvc.Routing.MvcAttributeRouteHandler.RouteAsync(RouteContext context) at Microsoft.AspNetCore.Routing.Tree.TreeRouter.RouteAsync(RouteContext context) at Microsoft.AspNetCore.Routing.RouteCollection.RouteAsync(RouteContext context) at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context) fail: Microsoft.AspNetCore.Mvc.Infrastructure.ActionSelector[1] Request matched multiple actions resulting in ambiguity. Matching actions: SCIM.API.CustomResourcesController.GetAll (SCIM.API) SCIM.API.CustomResourcesController.Get (SCIM.API) fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1] An unhandled exception has occurred while executing the request. Microsoft.AspNetCore.Mvc.Infrastructure.AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied: SCIM.API.CustomResourcesController.GetAll (SCIM.API) SCIM.API.CustomResourcesController.Get (SCIM.API) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionSelector.SelectBestCandidate(RouteContext context, IReadOnlyList`1 candidates) at Microsoft.AspNetCore.Mvc.Routing.MvcAttributeRouteHandler.RouteAsync(RouteContext context) at Microsoft.AspNetCore.Routing.Tree.TreeRouter.RouteAsync(RouteContext context) at Microsoft.AspNetCore.Routing.RouteCollection.RouteAsync(RouteContext context) at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context) ```

What am I missing?

johanndev commented 2 weeks ago

I tried to align my sample with the acceptance tests located in tests/SimpleIdServer.Scim.Host.Acceptance.Tests/

  1. I removed the route attribute from my controller
  2. I added the call to e.UseStandardScimEdp("CustomResources", false); before the call to e.UseScim()

Now I get an 404 Not Found response when trying to call the custom resource endpoint.

simpleidserver commented 2 weeks ago

Hello :)

Your custom controller cannot be found because the function "UseStandardScimEdp" must be used before "UseScim". Otherwise, the routes "catchAllGet" and "catchAllPut" will catch all the HTTP requests.

Your code should look something like this:

o.UseStandardScimEdp("CustomResources", false);
o.UseScim(opts.EnableRealm);
johanndev commented 2 weeks ago

Thanks for your reply :)

That's what I tried, I placed the call configuring my custom resource before the UseScim() call.

I'll check it again tomorrow, maybe my build cache wasn't up to date.

johanndev commented 2 weeks ago

@simpleidserver I checked my code again, the order of the endpoints seems to be correct to me :/

I published my reproduction sample here: https://github.com/johanndev/sid-issue-762

EDIT: I included a http testing file for quickly testing the endpoints. A call to /Users works as expected, so the general setup seems to be correct.

johanndev commented 2 weeks ago

If I list all ActionDescriptors via:

[Route("ActionDescriptors")]
public class ActionDescriptorsController(IActionDescriptorCollectionProvider provider) : Controller
{
    [HttpGet]
    public IActionResult Get() {
        var res = provider.ActionDescriptors.Items.Select(i => i.DisplayName);
        return new OkObjectResult(res);
    }
}

I get the following result:

[
  "SCIM.API.ActionDescriptorsController.Get (SCIM.API)",
  "SimpleIdServer.Scim.Api.BulkController.Index (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.DefaultController.Get (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.DefaultController.Put (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.DefaultController.Post (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.DefaultController.Delete (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.DefaultController.Patch (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.GroupsController.GetAll (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.GroupsController.Search (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.GroupsController.Get (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.GroupsController.Add (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.GroupsController.Delete (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.GroupsController.Update (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.GroupsController.Patch (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.ResourceTypesController.GetAll (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.ResourceTypesController.Get (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.SchemasController.GetAll (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.SchemasController.Get (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.ServiceProviderConfigController.Get (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.UsersController.GetAll (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.UsersController.Search (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.UsersController.Get (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.UsersController.Add (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.UsersController.Delete (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.UsersController.Update (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.UsersController.Patch (SimpleIdServer.Scim)"
]

Notice that the CustomResourcesController is missing, although it's registered exactly like the Users and Groups Controllers.

EDIT: The endpoints are correctly added to the ScimEndpointStore:

[HttpGet("scimendpoints")]
public IActionResult Get()
{
    var res = scimEndpointStore.Routes.Select(r => r.RouteName);
    return new OkObjectResult(res);
}

Result:

[
  "getCustomResources",
  "searchCustomResources",
  "getUniqueCustomResources",
  "addCustomResources",
  "deleteCustomResources",
  "updateCustomResources",
  "patchCustomResources",
  "getResourceTypes",
  "getResourceType",
  "getSchemas",
  "getSchema",
  "getServiceProviderConfig",
  "bulk",
  "getUsers",
  "searchUsers",
  "getUniqueUsers",
  "addUsers",
  "deleteUsers",
  "updateUsers",
  "patchUsers",
  "getGroups",
  "searchGroups",
  "getUniqueGroups",
  "addGroups",
  "deleteGroups",
  "updateGroups",
  "patchGroups"
]
johanndev commented 2 weeks ago

I made a stupid mistake creating the CustomResourcesController: I created it directly at the bottom of the Program.cs and subsequently used the "Move to File" Refactoring in VS. Unfortunately, VS nested the controller inside a partial program class. Removing the partial class fixes the route registration/action descriptors:

[
  "SCIM.API.ActionDescriptorsController.GetActionDescriptors (SCIM.API)",
  "SCIM.API.ActionDescriptorsController.Get (SCIM.API)",
  "SCIM.API.CustomResourcesController.GetAll (SCIM.API)",       🥳
  "SCIM.API.CustomResourcesController.Search (SCIM.API)",   🥳
  "SCIM.API.CustomResourcesController.Get (SCIM.API)",      🥳
  "SCIM.API.CustomResourcesController.Add (SCIM.API)"       🥳,
  "SCIM.API.CustomResourcesController.Delete (SCIM.API)",   🥳
  "SCIM.API.CustomResourcesController.Update (SCIM.API)",   🥳
  "SCIM.API.CustomResourcesController.Patch (SCIM.API)",        🥳
  "SimpleIdServer.Scim.Api.BulkController.Index (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.DefaultController.Get (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.DefaultController.Put (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.DefaultController.Post (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.DefaultController.Delete (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.DefaultController.Patch (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.GroupsController.GetAll (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.GroupsController.Search (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.GroupsController.Get (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.GroupsController.Add (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.GroupsController.Delete (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.GroupsController.Update (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.GroupsController.Patch (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.ResourceTypesController.GetAll (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.ResourceTypesController.Get (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.SchemasController.GetAll (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.SchemasController.Get (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.ServiceProviderConfigController.Get (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.UsersController.GetAll (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.UsersController.Search (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.UsersController.Get (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.UsersController.Add (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.UsersController.Delete (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.UsersController.Update (SimpleIdServer.Scim)",
  "SimpleIdServer.Scim.Api.UsersController.Patch (SimpleIdServer.Scim)"
]

.... but I still get a 404 when trying to call /CustomResources 😢

johanndev commented 2 weeks ago

I found the error: var customResource = SCIMSchemaBuilder.Create("urn:customresource", "CustomResources", "CustomResources")

The 's' at the end of the resource type was the culprit 🙈

Removing it and reseeding the db fixed the problem.

simpleidserver commented 2 weeks ago

Hello, and sorry for my late reply :)

I'm glad you found the issue! Don't hesitate to create GitHub tickets if you have other questions.