Activities of "alexander.nikonov"

Or, I can keep using AppService - with RemoteService(IsEnabled = false)] as mentioned in my code, right? I do not expose NotificationAppService outside. In such case there's no difference between using it or converting it to DomainService, as far as I understand. I even don't inherit ApplicationService class...

Even if I use DomainService or Repository directly - how will it differ from using IApplicationService in the way I used it in terms of trust? I really need kind of pass "trust" from Received method into AppService (or other level) method where I would act on behalf of this user which usually is an authenticated ICurrentUser if used from frontend application. What I have at my disposal is just this user's Id and Tenant inside Received method.

I have a separate IdentityServer.

One of the projects has RabbitMQ receiver, where all other projects send the data too. This receiver needs to insert data into DB based on the received RabbitMQ input and it uses INotificationAppService in the same project to do that. See the code below.

When I do not use Authorization attribute - it of course works OK. But I want to make it work secure - i.e. if I do not use typical request authorization (since there is no request in question here) RabbitMQ needs to be trusted somehow inside AppService.

So what are your proposals again - even if you mean not to use AppService at all here? Could you please write a couple of code lines to describe the idea?

public override async Task<object> Received(BasicDeliverEventArgs @event)
{
    var notificationCacheRabbitMqEto = @event.ToDataObject<NotificationRabbitMqEto>();

    var hubContext = _serviceProvider.GetService<IHubContext<NotificationHub>>();

    using (var scope = _serviceProvider.CreateScope())
    {
        try
        {
            var notificationAppService = scope.ServiceProvider.GetRequiredService<INotificationAppService>();
            var newNotification = await notificationAppService.CreateAsync(new NotificationCreateDto
            {
                TenantId = notificationCacheRabbitMqEto.TenantId,
                Login = notificationCacheRabbitMqEto.Login,
                Level = (int)notificationCacheRabbitMqEto.Level,
                Title = notificationCacheRabbitMqEto.Title,
                Details = notificationCacheRabbitMqEto.Details,
                IsActive = true,
                IsImportant = notificationCacheRabbitMqEto.IsImportant,
                CreatorName = GetTrimmedCreatorName(notificationCacheRabbitMqEto.Login), //Like so?
                LastModifierName = GetTrimmedCreatorName(notificationCacheRabbitMqEto.Login) //Like so?
            });

            await hubContext.Clients.Group(notificationCacheRabbitMqEto.Login).SendAsync("notificationReceived", newNotification);
        }
        catch (Exception ex)
        {
            Log.Error(ex.Message);
        }
    }

    return Task.FromResult<object>(null);
}
    
[RemoteService(IsEnabled = false)]
//[Authorize]
public class NotificationAppService : INotificationAppService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly INotificationRepository _notificationRepository;
    private readonly IStringLocalizer<CommonUIResource> _stringLocalizer;
    private readonly IUnitOfWorkManager _unitOfWorkManager;
    private readonly IObjectMapper _objectMapper;

    public NotificationAppService
    (
        IServiceProvider serviceProvider,
        INotificationRepository notificationRepository,
        IStringLocalizer<CommonUIResource> stringLocalizer,
        IUnitOfWorkManager unitOfWorkManager,
        IObjectMapper objectMapper
    )
    {
        _serviceProvider = serviceProvider;
        _notificationRepository = notificationRepository;
        _stringLocalizer = stringLocalizer;
        _unitOfWorkManager = unitOfWorkManager;
        _objectMapper = objectMapper;
    }
    ...
}

public interface INotificationAppService : IApplicationService, ITransientDependency {
    ...
}
  • ABP Framework version: v5.1.2
  • UI type: Angular
  • DB provider: EF Core
  • Identity Server Separated (Angular)

What is correct way to authorize RabbitMQ user so that I could invoke AppService methods from RabbitMQ receiver or sender? In the meantime both CurrentTenant and CurrentUser are null. Please write the example of the code.

You should not remove Authorize, We recommend that you pass the access token to complete the authentication.

https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-7.0#built-in-jwt-authentication https://ocelot.readthedocs.io/en/latest/features/websockets.html

I've already seen and used it before. As I said, passing token in URL doesn't seem very prominent. But this confuses me at Ocelot at NOT SUPPORTED section: "Authentication - If anyone requests it we might be able to do something with basic authentication."

So what is the underline here? Does authentication for Ocelot + SignalR does not suggest another way of request authentication besides passing token via URL?

UPDATE.

It started to work after I removed [Authorize] attribute from my Hub class. Does it mean that currently SignalR is working without authentication? I know that SignalR does not work with headers, but I don't want to pass token via URL. Should I ever worry about that, if my SignalRService code works within AppComponent which is available only for authorized user?

Probably you can suggest another approach to be on safe side? In addition to using tokens, I used to create token cookie in my HttpApi.Host project middleware and added it later on to each request like this (for instance, for Hangfire dashboard page), but at some point cookie functionality got broken - probably after introducing Ocelot gateway, not sure...

  • ABP Framework version: v5.1.2
  • UI type: Angular
  • DB provider: EF Core
  • Identity Server Separated

I have installed AbpAspNetCoreSignalRModule into HttpApi.Host project and put it into Module dependencies. I also used the following setup:

    Configure<AbpSignalROptions>(options =>
    {
        options.Hubs.AddOrUpdate(
            typeof(NotificationHub),
            config =>
            {
                config.RoutePattern = "/signalr-hubs/notification";
                config.ConfigureActions.Add(hubOptions =>
                {
                    //Additional options
                    hubOptions.LongPolling.PollTimeout = TimeSpan.FromSeconds(30);
                });
            }
        );
    });

The code above is followed by ConfigureSwagger setup. Nevertheless, I don't see signalr-hubs/notification endpoint at my Swagger page. I even installed Swagger extension from Nuget, but I still can't see this endpoint. WHY? This is question #1.

Question #2. Why Angular app does not work with SignalR?

    @Injectable({
      providedIn: 'root'
    })
    export class SignalRService {
    
      private hubConnection: signalR.HubConnection
        public startConnection = () => {
          this.hubConnection = new signalR.HubConnectionBuilder()
            .withUrl
            (
              `${environment.apis['default'].url}/signalr-hubs/notification`,
              { skipNegotiation: true, transport: signalR.HttpTransportType.WebSockets }
            )
            .build();
          this.hubConnection
            .start()
            .then(() => console.log('Connection started'))
            .catch(err => console.log('Error while starting connection: ' + err))
        }
        
        public addNotificationListener = () => {
          this.hubConnection.on('notificationreceived', (data) => {
            console.log(data);
          });
        }
    }

The error is:

Probably something is not fully correct with my Ocelot gateway settings, but I am even not sure the Hub endpoint is available. How do I check this??

    {
        "DownstreamPathTemplate": "/signalr-hubs/notification",
        "DownstreamScheme": "wss",
        "DownstreamHostAndPorts": [
            {
                "Host": "localhost",
                "Port": 44328
            }
        ],
        "UpstreamPathTemplate": "/centraltool/signalr-hubs/notification",
        "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ],
        "Key": "centraltool-signalr"
    },

Anticipating your question: no, I cannot send the project. And we have no time trying to make things work in the test project: our setup is more complex and it would not help us in any way if we find out the test sample DOES work. Please help us to understand, what is wrong here. Step by step.

Thank you.

Answer

Recreating our structure having several projects using a brand new ABP project is not a trivial and time consuming task and I'm afraid I have no project time for that. For this reason I'm asking you if there's something wrong with the code above. This is all I can provide. If this code makes no sense - I'd like to know why permission cache is not properly updated across the applications sharing the same cache - no matter if I use default in-memory cache or Redis server. All the apps are using the same Identity server and now hosted on localhost.

Answer

Hi.

It's not possible to check out. Because we've already upgraded.

In my opinion there shouldn't be any special custom code if using Redis: all permissions from all applications use the same permission cache, but they have own cache prefix, so the permissions are not mixed. And once I am done editing the roles, the cache has to be automatically updated, so when a user refreshes a webpage in app B, app C - he sees only the pages which are available according to the updated permissions. Unfortunately, it doesn't work...

Answer

Hi. Could you please let me know why this does not work anymore? Maybe it has something to do with the ABP version upgrade? If I remember it right, it was done for 4.x. Now we have ABP 5.1.3 and even though RabbitMq data seems to be correct, the permission update happens RANDOMLY, i.e. sometimes the permission with "false" value are not removed in fact from cache. Sometimes permissions with "true" value are not added... The question is related to SetManyAsync. I've tried to use sync method instead, tried to use RefreshManyAsync after SetManyAsync, it's all in vain:

public class MyPermissionCacheRabbitMqReceiver : RabbitMqReceiverBase
{
    private readonly IServiceProvider _serviceProvider;

    public MyPermissionCacheRabbitMqReceiver(IServiceProvider serviceProvider, string queueName) : base(queueName)
    {
        _serviceProvider = serviceProvider;
    }

    public override async Task&lt;object&gt; Received(BasicDeliverEventArgs @event)
    {
        var permissionCacheRabbitMqEto = @event.ToDataObject&lt;PermissionCacheRabbitMqEto&gt;();

        var permissionGrantCache = _serviceProvider.GetService&lt;IDistributedCache&lt;PermissionGrantCacheItem&gt;>();

        var currentTenant = _serviceProvider.GetService&lt;ICurrentTenant&gt;();

        using (currentTenant.Change(permissionCacheRabbitMqEto.AbpTenantId))
        {
            await permissionGrantCache.SetManyAsync(
                permissionCacheRabbitMqEto.Permissions.Select(permission =>
                    new KeyValuePair&lt;string, PermissionGrantCacheItem&gt;(permission.Key, new PermissionGrantCacheItem(permission.Value))));
        }

        return Task.FromResult&lt;object&gt;(null);
    }
}            

The service which sends the message resides on the different host and looks like this:

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IPermissionAppService))]
public class ApiPermissionAppService : PermissionAppService
{
    private readonly IRabbitMqManager _rabbitMqManager;

    public ApiPermissionAppService
    (
        IPermissionManager permissionManager,
        IPermissionDefinitionManager permissionDefinitionManager,
        IOptions&lt;PermissionManagementOptions&gt; options,
        ISimpleStateCheckerManager&lt;PermissionDefinition&gt; permissionStateManager,
        IRabbitMqManager rabbitMqManager
    )
        : base(permissionManager, permissionDefinitionManager, options, permissionStateManager)
    {
        _rabbitMqManager = rabbitMqManager;
    }

    public override async Task UpdateAsync(string providerName, string providerKey, UpdatePermissionsDto input)
    {
        await base.UpdateAsync(providerName, providerKey, input);

        var permissions = input.Permissions.Select(x => x)
            .ToDictionary(x => PermissionGrantCacheItem.CalculateCacheKey(x.Name, providerName, providerKey), x => x.IsGranted);

        await _rabbitMqManager.SendPermissionCacheChangeAsync("AbxEps-Abp-Caching", CurrentTenant.Id, permissions);
    }
}

Sorry, we cannot share our code. Creating a test project would be too complex too.

UPDATE: I've tried to use Redis server cache instead of the built-in cache. And the problem remains. Do I ever need to make something special if I change role permission on separate app server A and want them to get applied on server B, server C...?

Showing 11 to 20 of 141 entries
Made with ❤️ on ABP v9.2.0-preview. Updated on January 14, 2025, 14:54