ASP.NET Core – 查詢 AD 使用者密碼即將到期的 Web API

  延續之前兩個 AD 密碼相關的 API,接著寫了一個可以查詢 AD 使用者密碼即將到期的的 API,這篇就不再仔細寫出環境細節,包括 Models、appsettings.json、Program.cs 等,因為是延續之前的專案,如有需要可以參考前面的文章。


Services/PasswordManagementService.cs

using AD.Models;
using Microsoft.Extensions.Options;
using System.DirectoryServices;
using System.DirectoryServices.Protocols;
using System.Net;
using System.Text;
using System.DirectoryServices.AccountManagement;
using System.Runtime.Versioning;


namespace AD.Services
{
    public class PasswordManagementService(IOptions<LdapSettings> ldapSettings)
    {
        private readonly string _ldapServer = ldapSettings.Value.Server;
        private readonly string _domain = ldapSettings.Value.Domain;
        private readonly string _baseDn = ldapSettings.Value.BaseDn;

        // 查詢網域密碼政策
        [SupportedOSPlatform("windows")] // 宣告以下方法僅適用於 Windows,避免 DirectorySearcher 等 class 被提示要留意跨平臺問題。
        public TimeSpan GetMaxPasswordAge()
        {
            try
            {
                // 使用 DirectorySearcher 查詢網域《群組原則》的「密碼最長使用期限」。
                using DirectorySearcher searcher = new(new DirectoryEntry($"LDAP://{_baseDn}"))
                {
                    Filter = "(objectClass=domain)"
                };
                searcher.PropertiesToLoad.Add("maxPwdAge"); // 密碼最長使用期限

                SearchResult? result = searcher.FindOne();

                if (result != null && result.Properties.Contains("maxPwdAge"))
                {
                    long maxPwdAgeTicks = (long)result.Properties["maxPwdAge"][0]; // 「密碼最長使用期限」原始數值,
                    return TimeSpan.FromTicks(maxPwdAgeTicks); // 轉成 TimeSpan 物件。(TimeSpan 物件在時間處理較為方便)
                }

                return TimeSpan.Zero; // 找不到資料時,回傳 0
            }
            catch (Exception ex)
            {
                Console.WriteLine($"查詢網域密碼政策時,發生錯誤: {ex.Message}");
                return TimeSpan.Zero;
            }
        }

        // 查詢密碼即將到期的使用者
        [SupportedOSPlatform("windows")] // 宣告以下方法僅適用於 Windows,避免 GetMaxPasswordAge() 等方法被提示要留意跨平臺問題。
        public List<String> GetUsersWithExpiringPasswords(int daysUntilExpiry)
        {
            List<String> expiringUsers = []; // 「密碼即將到期的使用者」清單。

            try
            {
                TimeSpan MaxPasswordAge = GetMaxPasswordAge(); // 取得「密碼最長使用期限」。

                if (MaxPasswordAge == TimeSpan.Zero) // 未設定「密碼最長使用期限」。
                    return expiringUsers;

                DateTime today = DateTime.Now;
                DateTime thresholdDate = today.AddDays(daysUntilExpiry); // XX 天 (daysUntilExpiry) 後的「到期日期」。

                // 使用 DirectorySearcher 查詢網域《群組原則》的「密碼最長使用期限」。
                using DirectorySearcher searcher = new(new DirectoryEntry($"LDAP://{_baseDn}"))
                {
                    Filter = "(&(objectCategory=person)(objectClass=user)(pwdLastSet=*)(mail=*))" // 篩選有「上次密碼設定時間」、「Mail」的「使用者」。
                };
                searcher.PropertiesToLoad.Add("sAMAccountName"); // AD 帳號
                searcher.PropertiesToLoad.Add("pwdLastSet");     // 上次密碼設定時間
                searcher.PropertiesToLoad.Add("displayName");    // 顯示名稱
                searcher.PropertiesToLoad.Add("mail");           // 電子郵件

                foreach (SearchResult result in searcher.FindAll())
                {
                    long pwdLastSetTicks = (long)result.Properties["pwdLastSet"][0]; // 使用者帳戶的「上次密碼設定時間」。
                    DateTime pwdLastSet = DateTime.FromFileTime(pwdLastSetTicks);    // 轉成 DateTime 格式。(具體的日期)
                    DateTime passwordExpiryDate = pwdLastSet + MaxPasswordAge;       // 使用者帳戶的「密碼到期日」。

                    if (passwordExpiryDate <= thresholdDate) // 已經到期或未來 XX 天內會到期的帳戶
                    {
                        string userSAMAccountName = result.Properties["sAMAccountName"][0].ToString() ?? string.Empty; // AD 帳號
                        string userPasswordExpiryDate = passwordExpiryDate.ToString("yyyy/MM/dd") ?? string.Empty;     // 上次密碼設定時間
                        string userDisplayName = result.Properties["displayName"][0].ToString() ?? string.Empty;       // 顯示名稱
                        string userMail = result.Properties["mail"][0].ToString() ?? string.Empty;                     // 電子郵件

                        string expiringUsersInfo = $"{userSAMAccountName};{userPasswordExpiryDate};{userDisplayName};{userMail}";
                        expiringUsers.Add(expiringUsersInfo);
                    }
                }

                return expiringUsers; // 若沒結果,回傳空的清單。
            }
            catch (Exception ex)
            {
                Console.WriteLine($"查詢密碼即將到期的使用者時,發生錯誤: {ex.Message}");
                return expiringUsers;
            }
        }
    }
}

 

Controllers/PasswordManagementController.cs

using AD.Models;
using AD.Services;
using Microsoft.AspNetCore.Mvc;
using System.Runtime.Versioning;

namespace AD.Controllers
{
    [Route("password")]
    [ApiController]

    public class PasswordManagementController(PasswordManagementService passwordManagement) : ControllerBase
    {

        // 取得密碼即將到期的使用者
        [HttpGet("get-expiry-users/{days}")]
        [SupportedOSPlatform("windows")] // 宣告以下用到的方法僅適用於 Windows,避免 GetUsersWithExpiringPasswords 方法被提示要留意跨平臺問題。

        public IActionResult GetExpiringUsers(int days)
        {
            try
            {
                var users = passwordManagement.GetUsersWithExpiringPasswords(days);
                return Ok(new { expiringUsers = users }); // 回傳一個包含 ExpiringUsers 屬性,其值為 users 的 JSON 格式。
            }
            catch (Exception ex) {
                return StatusCode(500, new { Message = "發生錯誤", Error = ex.Message });
            }
        }
    }
}

 

《Swagger 測試結果》

 

《相關連結》

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.