Activities of "maliming"

hi

What is your abp package version?

I will share some code with you. Thanks.

Hi,

I've analyzed your admin server logs. Your AdministrationService has 4,233 permissions registered from multiple microservices. This is expected in the microservice template with Dynamic Permission Store enabled.

From the logs:

  • Cache hit: ~650ms (e.g., request at 05:48:03)
  • Cache miss: 10+ seconds (e.g., request at 05:44:45)

We suspect the bottleneck is the Redis cache operations with this large number of permissions. To confirm, could you temporarily disable Redis caching and use in-memory cache instead to test the same API call?

You can do this by removing the AbpCachingStackExchangeRedisModule dependency and the related Redis configuration from your AdministrationService, then compare the response time.

This will help us confirm the root cause before providing a solution.

Thanks

hi

The recommended approach is to extend the built-in OpenLdapManager and LdapExternalLoginProvider to add tenant validation after LDAP authentication succeeds.

Step 1: Extend OpenLdapManager to query the user's organizational unit from LDAP

Following the same pattern as the existing GetUserEmailAsync method:

using System.Linq;
using System.Threading.Tasks;
using LdapForNet;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity.ExternalLoginProviders.Ldap;
using Volo.Abp.Ldap;

namespace YourProject;

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(OpenLdapManager), typeof(ILdapManager), typeof(LdapManager))]
public class CustomOpenLdapManager : OpenLdapManager
{
    public CustomOpenLdapManager(ILdapSettingProvider ldapSettingProvider)
        : base(ldapSettingProvider)
    {
    }

    public virtual async Task<string> GetUserOrganizationalUnitAsync(string userName)
    {
        using (var conn = await CreateLdapConnectionAsync())
        {
            await AuthenticateLdapConnectionAsync(
                conn,
                await NormalizeUserNameAsync(await LdapSettingProvider.GetUserNameAsync()),
                await LdapSettingProvider.GetPasswordAsync());

            var searchResults = await conn.SearchAsync(
                await GetBaseDnAsync(),
                await GetUserFilterAsync(userName));

            try
            {
                var userEntry = searchResults.First();
                return GetUserOrganizationalUnit(userEntry);
            }
            catch (LdapException e)
            {
                Logger.LogException(e);
            }

            return null;
        }
    }

    protected virtual string GetUserOrganizationalUnit(LdapEntry ldapEntry)
    {
        // Option 1: Read "ou" or "department" attribute directly from the LDAP entry
        var ou = ldapEntry.ToDirectoryEntry().GetAttribute("ou")?.GetValue<string>();
        if (!ou.IsNullOrWhiteSpace())
        {
            return ou;
        }

        // Option 2: Parse OU from the user's DN
        // e.g. "uid=john,ou=Unit1,dc=company,dc=com" -> "Unit1"
        var dn = ldapEntry.Dn;
        var ouPart = dn?.Split(',')
            .FirstOrDefault(p => p.Trim().StartsWith("ou=", System.StringComparison.OrdinalIgnoreCase));

        return ouPart?.Split('=').LastOrDefault()?.Trim();
    }
}

Step 2: Create a tenant-restricted LDAP login provider

using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Volo.Abp.Features;
using Volo.Abp.Guids;
using Volo.Abp.Identity;
using Volo.Abp.Identity.ExternalLoginProviders.Ldap;
using Volo.Abp.Ldap;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Settings;

namespace YourProject;

public class TenantRestrictedLdapExternalLoginProvider : LdapExternalLoginProvider
{
    protected ITenantStore TenantStore { get; }

    public TenantRestrictedLdapExternalLoginProvider(
        IGuidGenerator guidGenerator,
        ICurrentTenant currentTenant,
        IdentityUserManager userManager,
        IIdentityUserRepository identityUserRepository,
        OpenLdapManager ldapManager,
        ILdapSettingProvider ldapSettingProvider,
        IFeatureChecker featureChecker,
        ISettingProvider settingProvider,
        IOptions<IdentityOptions> identityOptions,
        ITenantStore tenantStore)
        : base(guidGenerator, currentTenant, userManager, identityUserRepository,
            ldapManager, ldapSettingProvider, featureChecker, settingProvider, identityOptions)
    {
        TenantStore = tenantStore;
    }

    public override async Task<bool> TryAuthenticateAsync(string userName, string plainPassword)
    {
        // First, perform standard LDAP authentication
        var isAuthenticated = await base.TryAuthenticateAsync(userName, plainPassword);
        if (!isAuthenticated)
        {
            return false;
        }

        // Skip tenant validation for host users
        if (CurrentTenant.Id == null)
        {
            return true;
        }

        // Query LDAP for user's organizational unit
        var customLdapManager = (CustomOpenLdapManager)LdapManager;
        var userOu = await customLdapManager.GetUserOrganizationalUnitAsync(userName);
        if (userOu.IsNullOrWhiteSpace())
        {
            Logger.LogWarning($"Could not determine organizational unit for LDAP user: {userName}");
            return false;
        }

        // Get current tenant info
        var tenant = await TenantStore.FindAsync(CurrentTenant.Id.Value);
        if (tenant == null)
        {
            return false;
        }

        // Compare user's OU with the current tenant
        if (!IsUserAllowedForTenant(userOu, tenant.Name))
        {
            Logger.LogWarning(
                $"LDAP user '{userName}' belongs to OU '{userOu}' and is not allowed for tenant '{tenant.Name}'");
            return false;
        }

        return true;
    }

    protected virtual bool IsUserAllowedForTenant(string userOu, string tenantName)
    {
        // Simple approach: tenant name matches OU name (case-insensitive)
        // Customize this mapping based on your business requirements
        return string.Equals(userOu, tenantName, System.StringComparison.OrdinalIgnoreCase);
    }
}

Step 3: Register the custom provider in your module

public override void ConfigureServices(ServiceConfigurationContext context)
{
    // Replace the default LdapExternalLoginProvider with our tenant-restricted version
    Configure<AbpIdentityOptions>(options =>
    {
        options.ExternalLoginProviders.Remove(LdapExternalLoginProvider.Name);
        options.ExternalLoginProviders.Add<TenantRestrictedLdapExternalLoginProvider>(LdapExternalLoginProvider.Name);
    });
}

How it works:

  1. User submits credentials on the login page (with a tenant selected).
  2. AbpSignInManager.PasswordSignInAsync iterates external login providers and calls TryAuthenticateAsync.
  3. Our custom provider first validates credentials against LDAP (via base.TryAuthenticateAsync).
  4. If the current context is host (CurrentTenant.Id == null), it allows login directly.
  5. Otherwise, it queries LDAP for the user's OU using CustomOpenLdapManager.GetUserOrganizationalUnitAsync.
  6. It compares the OU with the current tenant's name via IsUserAllowedForTenant.
  7. Returns false if there's a mismatch, which blocks the sign-in.

Notes:

  • The CustomOpenLdapManager is automatically injected into TenantRestrictedLdapExternalLoginProvider because OpenLdapManager is replaced in the DI container via the [Dependency(ReplaceServices = true)] attribute.
  • The GetUserOrganizationalUnit method provides two strategies for determining the user's OU: reading a direct LDAP attribute (ou) or parsing the OU from the user's DN. You should adjust this based on your LDAP directory structure.
  • The IsUserAllowedForTenant method uses a simple name comparison. If your tenant names don't match OU names directly, you can implement a custom mapping (e.g., from a configuration file or database table).
  • LDAP settings in ABP are tenant-aware, but since you use a single LDAP service, make sure the LDAP connection settings (host, port, base DN, etc.) are configured at the host level so all tenants share the same configuration.

Thanks

hi

The UserInvitation is a new entity, so add it to your DbContext is correct.

We will check your angular issues.

Thanks.

: )

ok.

Hi,

Yes, you can reuse Blazor WebAssembly pages in a MAUI Blazor Hybrid application with ABP. ABP provides a MAUI Blazor application template specifically designed for this purpose.

How reuse works at different levels:

  1. ABP built-in module pages (Identity, Settings, Audit Logging, Language Management, OpenIddict, etc.) — These are already shared. Both MAUI Blazor and Blazor WebAssembly reference the same base Blazor UI packages (e.g., Volo.Abp.Identity.Pro.Blazor). The WebAssembly variant just adds a .WebAssembly suffix package for WASM-specific service registration, but the actual Razor pages/components come from the same base package. So you get all the built-in module pages working in MAUI Blazor without any extra work.

  2. Template-generated pages (like Index.razor, HostDashboard.razor) — In our current templates, these pages are duplicated between MAUI Blazor and Blazor WebApp Client projects. The markup is almost identical, but there are small differences. For example, the Login button on Index.razor uses <a href="/Account/Login"> in MAUI Blazor vs <a @onclick="Login"> with NavigationManager in WebApp Client. The HostDashboard.razor is essentially the same in both templates.

  3. Your custom pages — If you want to share your own custom pages between both apps, you can create a Razor Class Library (RCL) project and reference it from both the MAUI Blazor and Blazor WebAssembly projects. Keep in mind that you may need to handle some platform-specific differences (like navigation and authentication) through abstractions or conditional logic.

  4. HttpApi.Client — Both MAUI Blazor and Blazor WebAssembly reference the same HttpApi.Client project, so all your API client proxy code is fully shared.

What requires separate implementation:

  • Authentication: MAUI Blazor uses OIDC via Duende.IdentityModel.OidcClient with a platform-specific MauiAuthenticationBrowser (supporting both authorization code flow and password flow). Blazor WebAssembly uses standard ASP.NET Core authentication (AddBlazorWebAppServices()). ABP provides Volo.Abp.Account.Pro.Public.MauiBlazor package that handles MAUI authentication for you.

  • Module configuration: MAUI Blazor module (MyProjectNameMauiBlazorModule) has its own setup for Blazorise, routing, bundling, and authentication that differs from the WebAssembly module.

  • Navigation menus: Each template has its own MenuContributor. For example, the MAUI Blazor user menu links to external URLs (Account management pages on the auth server) with target: "_blank", while the WebAssembly version uses OperatingSystem.IsBrowser() to differentiate behavior.

  • Platform-specific code: MAUI Blazor includes platform handlers for Android, iOS, MacCatalyst, and Windows (e.g., WebAuthenticator callbacks, SSL certificate handling in debug mode).

Getting started:

You can create a new ABP solution with the MAUI Blazor option using ABP Studio. The generated template comes pre-configured with everything needed. Then, to maximize code reuse with your existing Blazor WebAssembly app, extract shared custom components into a Razor Class Library.

Thanks

hi

Can you try to share full /api/abp/application-configuration HTTP request debug logs?

liming.ma@volosoft.com

https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problems

Have you used Redis? If so. Can you disable it and try again?

Thanks.

hi

Is your problem solved?

Thanks.

Get the LeptonXTheme source code.

dotnet tool list -g
Package Id                       Version         Commands         
------------------------------------------------------------------
volo.abp.studio.cli              2.2.4           abp      

abp get-source  Volo.Abp.LeptonXTheme 
[09:46:59 INF] Downloading source code of Volo.Abp.LeptonXTheme (Latest)
[09:46:59 INF] Output folder: /Downloads/test
[09:47:14 INF] Volo.Abp.LeptonXTheme.SourceCode (v: 5.1.1) package downloaded from https://nuget.abp.io/xxx/v3/index.json and saved to (/Users/maliming/.nuget/packages/)
[09:47:15 INF] 'Volo.Abp.LeptonXTheme' has been successfully downloaded to '/Downloads/test'
Showing 701 to 710 of 12928 entries
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on November 02, 2026, 06:56
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.