Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ public static class UserFriendlyExceptionCodes
public const string INVALID_EMAIL_CAPTCHA = "InvalidEmailCaptcha";
public const string INVALID_SMS_CAPTCHA = "InvalidSMSCaptcha";
public const string CONNECT_ERROR = "ConnectError";
public const string LDAP_SERVER_UNREACHABLE = "LdapServerUnreachable";
public const string LDAP_SSL_MISMATCH = "LdapSslMismatch";
public const string LDAP_CREDENTIALS_INVALID = "LdapCredentialsInvalid";
public const string LDAP_BIND_FAILED = "LdapBindFailed";
public const string INVALID_SEND_EMAIL_TYPE = "InvalidSendEmailType";
public const string INVALID_SEND_MSG_CODE_TYPE = "InvalidSendMsgCodeType";
public const string INVALID_RESET_PASSWORD_TYPE = "InvalidResetPasswordType";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Masa.Auth.Service.Admin.Application.Subjects.Commands;

public class LdapConnectTestCommandValidator : AbstractValidator<LdapUpsertCommand>
public class LdapConnectTestCommandValidator : AbstractValidator<LdapConnectTestCommand>
{
public LdapConnectTestCommandValidator()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,18 @@ public LdapCommandHandler(
[EventHandler]
public async Task LdapConnectTestAsync(LdapConnectTestCommand ldapConnectTestCommand)
{
var ldapOptions = ldapConnectTestCommand.LdapDetailDto.Adapt<LdapOptions>();
var ldapProvider = _ldapFactory.CreateProvider(ldapOptions);
if (!await ldapProvider.AuthenticateAsync(ldapOptions.RootUserDn, ldapOptions.RootUserPassword))
{
throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.CONNECT_ERROR);
}
await LdapAuthenticateHelper.AuthenticateOrThrowAsync(ldapConnectTestCommand.LdapDetailDto);
}

[EventHandler]
public async Task LdapUpsertAsync(LdapUpsertCommand ldapUpsertCommand)
{
var ldapIdpDto = ldapUpsertCommand.LdapDetailDto;

await LdapAuthenticateHelper.AuthenticateOrThrowAsync(ldapIdpDto);

var ldapOptions = ldapIdpDto.Adapt<LdapOptions>();
var ldapProvider = _ldapFactory.CreateProvider(ldapOptions);
if (!await ldapProvider.AuthenticateAsync(ldapOptions.RootUserDn, ldapOptions.RootUserPassword))
{
throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.CONNECT_ERROR);
}

var ldapIdp = new LdapIdp(
ldapIdpDto.ServerAddress,
Expand Down
4 changes: 4 additions & 0 deletions src/Services/Masa.Auth.Service.Admin/Assets/I18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
"InvalidSMSCaptcha": "The sms captcha is invalid",
"InvalidCaptcha": "Invalid Captcha",
"ConnectError": "connect error",
"LdapServerUnreachable": "[Address/Port] Unable to reach LDAP server {0}:{1}. Please verify the server address and port",
"LdapSslMismatch": "[LDAP/LDAPS or TLS error] The {0} handshake failed for {1}:{2}. Verify the protocol, port, and server certificate",
"LdapCredentialsInvalid": "[Credentials] Invalid RootUserDn or password. Please check and retry",
"LdapBindFailed": "[LDAP authentication] The server rejected the bind or the connection was interrupted. Check the account permissions and LDAP server policy",
"InvalidSendEmailType": "Invalid SendEmailType",
"InvalidSendMsgCodeType": "Invalid SendMsgCodeType",
"InvalidResetPasswordType": "Invalid ResetPasswordType",
Expand Down
4 changes: 4 additions & 0 deletions src/Services/Masa.Auth.Service.Admin/Assets/I18n/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
"InvalidSMSCaptcha": "SMS認証コードが無効です",
"InvalidCaptcha": "認証コードが無効です",
"ConnectError": "接続エラー",
"LdapServerUnreachable": "【アドレス/ポートエラー】LDAPサーバー {0}:{1} に接続できません。アドレスとポートを確認してください",
"LdapSslMismatch": "【LDAP/LDAPSまたはTLSエラー】{0} で {1}:{2} に接続する際にハンドシェイクに失敗しました。プロトコル、ポート、サーバー証明書を確認してください",
"LdapCredentialsInvalid": "【アカウント/パスワードエラー】RootUserDnまたはパスワードが正しくありません",
"LdapBindFailed": "【LDAP認証失敗】LDAPサーバーがBind要求を拒否したか、接続が中断されました。アカウント権限とLDAPサーバーの認証ポリシーを確認してください",
"InvalidSendEmailType": "SendEmailTypeが無効です",
"InvalidSendMsgCodeType": "SendMsgCodeTypeが無効です",
"InvalidResetPasswordType": "パスワードリセットタイプが無効です",
Expand Down
4 changes: 4 additions & 0 deletions src/Services/Masa.Auth.Service.Admin/Assets/I18n/ru-RU.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
"InvalidSMSCaptcha": "Неверный SMS-код",
"InvalidCaptcha": "Неверный код подтверждения",
"ConnectError": "Ошибка соединения",
"LdapServerUnreachable": "[Адрес/порт] Не удалось подключиться к LDAP-серверу {0}:{1}. Проверьте адрес и порт",
"LdapSslMismatch": "[LDAP/LDAPS или TLS] Не удалось выполнить рукопожатие {0} с {1}:{2}. Проверьте протокол, порт и сертификат сервера",
"LdapCredentialsInvalid": "[Учетные данные] Неверный RootUserDn или пароль",
"LdapBindFailed": "[Аутентификация LDAP] Сервер отклонил запрос Bind или соединение было прервано. Проверьте права учетной записи и политику LDAP-сервера",
"InvalidSendEmailType": "Неверный тип SendEmailType",
"InvalidSendMsgCodeType": "Неверный тип SendMsgCodeType",
"InvalidResetPasswordType": "Неверный тип ResetPasswordType",
Expand Down
4 changes: 4 additions & 0 deletions src/Services/Masa.Auth.Service.Admin/Assets/I18n/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
"InvalidSMSCaptcha": "短信验证码无效",
"InvalidCaptcha": "验证码无效",
"ConnectError": "连接错误",
"LdapServerUnreachable": "【地址/端口错误】无法连接到 LDAP 服务器 {0}:{1},请检查服务器地址和端口",
"LdapSslMismatch": "【LDAP/LDAPS 或 TLS 错误】使用 {0} 连接 {1}:{2} 时握手失败,请检查协议、端口和服务器证书",
"LdapCredentialsInvalid": "【域账号/密码错误】RootUserDn 或密码不正确,请检查后重试",
"LdapBindFailed": "【LDAP 认证失败】LDAP 服务器拒绝了 Bind 请求或连接被中断,请检查账号权限及服务器认证策略",
"InvalidSendEmailType": "SendEmailType无效",
"InvalidSendMsgCodeType": "SendMsgCodeType无效",
"InvalidResetPasswordType": "ResetPasswordType无效",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the Apache License. See LICENSE.txt in the project root for license information.

using System.Net.Sockets;

namespace Masa.Auth.Service.Admin.Infrastructure.Ldap;

/// <summary>
/// LDAP 连接校验,按失败阶段返回明确提示:
/// 1. TCP 地址/端口不可达 → <see cref="UserFriendlyExceptionCodes.LDAP_SERVER_UNREACHABLE"/>
/// 2. LDAP/LDAPS 握手失败 → <see cref="UserFriendlyExceptionCodes.LDAP_SSL_MISMATCH"/>
/// 3. LDAP result code 49 → <see cref="UserFriendlyExceptionCodes.LDAP_CREDENTIALS_INVALID"/>
/// 4. 其它 Bind 失败 → <see cref="UserFriendlyExceptionCodes.LDAP_BIND_FAILED"/>
/// </summary>
public static class LdapAuthenticateHelper
{
/// <summary>
/// LDAP result code: invalidCredentials (RFC 4511).
/// </summary>
const int LdapInvalidCredentials = 49;

/// <summary>
/// Connect timeout for LDAP connect-test / save validation (milliseconds).
/// </summary>
const int ConnectionTimeoutMs = 10_000;

public static async Task AuthenticateOrThrowAsync(LdapDetailDto ldapDetailDto)
{
await EnsureTcpConnectionAsync(ldapDetailDto);

using var connection = new LdapConnection
{
SecureSocketLayer = ldapDetailDto.IsLdaps,
ConnectionTimeout = ConnectionTimeoutMs
};

// Stage 1: TCP / TLS connect — address、port、LDAP vs LDAPS
try
{
await connection.ConnectAsync(ldapDetailDto.ServerAddress, ldapDetailDto.ServerPort);
}
catch (Exception ex)

Check warning on line 42 in src/Services/Masa.Auth.Service.Admin/Infrastructure/Ldap/LdapAuthenticateHelper.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unused local variable 'ex'.

See more on https://sonarcloud.io/project/issues?id=masastack_MASA.Auth&issues=AZ-CgFcgCMIQUNYb5-wu&open=AZ-CgFcgCMIQUNYb5-wu&pullRequest=1477
{
// TCP 已经连通,此时失败发生在 LDAP 明文协议或 TLS 握手阶段。
// 不再把它误报为“地址/端口不可达”。
throw CreateProtocolException(ldapDetailDto);
}

// Stage 2: Bind — credentials
try
{
await connection.BindAsync(ldapDetailDto.RootUserDn, ldapDetailDto.RootUserPassword);
}
catch (LdapException ex) when (ex.ResultCode == LdapInvalidCredentials)
{
throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.LDAP_CREDENTIALS_INVALID);
}
catch (LdapException)
{
// 只有 result code 49 才能确定是账号或密码错误。
throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.LDAP_BIND_FAILED);
}
catch (Exception)
{
// Bind 阶段连接中断、服务端策略异常等,不能归类为密码错误。
throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.LDAP_BIND_FAILED);
}
}

static async Task EnsureTcpConnectionAsync(LdapDetailDto ldapDetailDto)
{
using var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(ConnectionTimeoutMs));
using var tcpClient = new TcpClient();

try
{
await tcpClient.ConnectAsync(
ldapDetailDto.ServerAddress,
ldapDetailDto.ServerPort,
timeout.Token);
}
catch (Exception ex) when (ex is SocketException or OperationCanceledException)
{
throw new UserFriendlyException(
UserFriendlyExceptionCodes.LDAP_SERVER_UNREACHABLE,
ldapDetailDto.ServerAddress,
ldapDetailDto.ServerPort.ToString());
}
}

static UserFriendlyException CreateProtocolException(LdapDetailDto ldapDetailDto)
{
var mode = ldapDetailDto.IsLdaps ? "LDAPS" : "LDAP";
return new UserFriendlyException(
UserFriendlyExceptionCodes.LDAP_SSL_MISMATCH,
mode,
ldapDetailDto.ServerAddress,
ldapDetailDto.ServerPort.ToString());
}
}
1 change: 1 addition & 0 deletions src/Services/Masa.Auth.Service.Admin/_Imports.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
global using Masa.Auth.Service.Admin.Infrastructure.CacheModels;
global using Masa.Auth.Service.Admin.Infrastructure.Email;
global using Masa.Auth.Service.Admin.Infrastructure.Extensions;
global using Masa.Auth.Service.Admin.Infrastructure.Ldap;
global using Masa.Auth.Service.Admin.Infrastructure.Message;
global using Masa.Auth.Service.Admin.Infrastructure.Middleware;
global using Masa.Auth.Service.Admin.Infrastructure.Models;
Expand Down
Loading