using EntitlementDAL.DTO.User; using EntitlementDAL.Interfaces; using GB5Shared.Connection; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.User; namespace EntitlementBLL.Implementations; /// /// Adapter behind GB5Shared.User.IClientUserProvisioner — inserts the first admin MUSER row /// directly via EntitlementDAL rather than delegating to FrameworkBLL.User.IUserBLL. /// /// PlatformHost (which hosts Entitlement) has zero reference to FrameworkBLL/FrameworkDAL/ /// FrameworkSL, so the original design (an adapter in FrameworkBLL wrapping IUserBLL.SaveUser) /// could never actually resolve there. This duplicates the narrow "new user, UserId==0" slice of /// IUserBLL.SaveUser's logic (FrameworkBLL/User/UserBLL.cs) — auto-number generation, temp /// password generation/encryption, MUSER insert — using EntitlementUserQB.SAVE_USER/SAVE_USER_PG, /// copied verbatim from FrameworkDAL.Query.User.UserQB (SQL Server + Postgres only; Entitlement /// never runs against Oracle/MySQL, unlike Framework's UserDAL). /// public class ClientUserProvisioner : IClientUserProvisioner { private readonly IEntitlementUserDAL _UserDAL; private readonly AutoNumber _AutoNumber; private readonly IApplicationConnection _ApplicationConnection; public ClientUserProvisioner( IEntitlementUserDAL userDAL, AutoNumber autoNumber, IApplicationConnection applicationConnection) { _UserDAL = userDAL; _AutoNumber = autoNumber; _ApplicationConnection = applicationConnection; } public async Task ProvisionAsync(ProvisionClientUserRequest request, LoginDTO login) { var userDto = new EntitlementUserDTO { UserCode = request.UserCode, UserName = request.UserName, // MUSER.USERLOGINNAME is NOT NULL with no default (confirmed live, tracker §31.13) — // never set here before, meaning no user could ever have been created through this // pipeline. Real live rows use either the email or the user code as login name; user // code is used here since it's guaranteed present (email is required too, but the // code is this DTO's own natural unique identifier for a login name). UserLoginName = request.UserCode, UserPrimaryMail = request.UserPrimaryMail, UserPrimaryMobile = request.UserPrimaryMobile, RoleId = request.RoleId, // LOGINMACHINE/LOGINIP/LANGUAGEID are NOT NULL with no default and no backing field // initializer (confirmed live, tracker §31.13) — real rows use empty string for // LOGINMACHINE/LOGINIP (a system-initiated user, no interactive login session yet) // and "EN" for LANGUAGEID (the only value seen on any real row checked). UserLoginMachine = "", UserLoginIp = "", UserLanguageId = "EN", // CKMUSER_SOURCETYPE only allows 1-5, confirmed live (tracker §31.13) — 0 fails // outright. 1 = "Framework" per this DTO's own documented taxonomy comment, matching // this row's real origin (a system/framework-driven onboarding action). UserSourceType = 1, }; // A 12-char plaintext here (matching FrameworkBLL.User.UserBLL.SaveUser's own choice) // AES-GCM-encrypts to IV(12)+ciphertext(12)+tag(16) = 40 raw bytes = 56 Base64 chars — // confirmed live (tracker §31.13) that this overflows MUSER.PASSWORD's real NVARCHAR(50) // outright. This is a pre-existing bug shared with FrameworkBLL.User.UserBLL.SaveUser and // PayRoll.EmployeeBLL (both call RandomGenerateAlphaNumeric(12) into the same column) — // flagged, not fixed there (out of scope: shared framework code affecting other modules). // 8 chars here encrypts to 36 raw bytes = 48 Base64 chars, safely under 50. var password = _ApplicationConnection.RandomGenerateAlphaNumeric(8); userDto.UserGeneratedPassword = password; userDto.UserPassword = _ApplicationConnection.Encrypt(password, userDto.UserCode); var autoNumberDto = await _AutoNumber.GetAutoNumber(1, "USER", login).ConfigureAwait(false); userDto.UserId = autoNumberDto.StartNumber; userDto.UserCreatedById = login.UserId; userDto.UserCreatedOn = DateTime.UtcNow; userDto.UserModifiedById = login.UserId; userDto.UserModifiedOn = DateTime.UtcNow; userDto.TenantId = login.ClientId; await _UserDAL.SaveUserAsync(userDto, login, CancellationToken.None).ConfigureAwait(false); return new ProvisionClientUserResult { UserId = userDto.UserId, UserGeneratedPassword = userDto.UserGeneratedPassword, }; } }