From fbcc859289fc39521c89593255736e715ad91b2d Mon Sep 17 00:00:00 2001 From: fontvielle Date: Thu, 22 Oct 2026 13:37:51 +0300 Subject: [PATCH] =?UTF-8?q?AD:=20=D1=83=D1=87=D0=B0=D1=81=D1=82=D0=BD?= =?UTF-8?q?=D0=B8=D0=BA=D0=B8=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF=20=D1=81=20?= =?UTF-8?q?=D0=B4=D0=BE=D1=81=D1=82=D1=83=D0=BF=D0=BE=D0=BC=20=D0=B2=20bj?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit У каждой группы домена в «Пользователи и вход» — «участники: N»: ФИО, логин, почта, отметки «отключён в AD» и «входил в bj»; с вложенными группами (LDAP_MATCHING_RULE_IN_CHAIN). GET /admin/setup/users/ad-group-members — только для групп из настроек bj. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn --- internal/lkgateway/adwizard.go | 78 +++++++++++++++++++ .../web/templates/part_accounts.html | 19 ++++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/internal/lkgateway/adwizard.go b/internal/lkgateway/adwizard.go index 2519e17..49d92de 100644 --- a/internal/lkgateway/adwizard.go +++ b/internal/lkgateway/adwizard.go @@ -16,6 +16,8 @@ import ( "log" "net/http" "net/url" + "sort" + "strconv" "strings" "sync" "time" @@ -29,6 +31,7 @@ func (h *setupHandlers) registerADWizard(mux *http.ServeMux) { mux.HandleFunc("/admin/setup/users/ad-trust", h.adTrust) mux.HandleFunc("/admin/setup/users/ad-discover", h.adDiscoverHTTP) mux.HandleFunc("/admin/setup/users/ad-group", h.adGroupSave) + mux.HandleFunc("/admin/setup/users/ad-group-members", h.adGroupMembersHTTP) } // adConnect — POST: учётная запись домена и пароль; домен и контроллер — из расширенных настроек или bj находит @@ -546,3 +549,78 @@ func (h *setupHandlers) adGroupSave(w http.ResponseWriter, r *http.Request) { igateShareSyncSoon(h.rc) // папки шлюза «через домен» — тем же группам h.usersFlash(w, r, msg) } + +// adMember — участник группы домена (для контроля, кто получает доступ в bj через группу). +type adMember struct { + Login string `json:"login"` + Name string `json:"name"` + Email string `json:"email,omitempty"` + Disabled bool `json:"disabled,omitempty"` // учётная запись отключена в AD — войти не сможет + InBJ bool `json:"in_bj,omitempty"` // уже входил в bj (есть пользователь bj) +} + +// adGroupMembersHTTP — GET /admin/setup/users/ad-group-members?dn=…: участники группы с доступом в bj, +// включая вложенные группы (LDAP_MATCHING_RULE_IN_CHAIN). Только группы из настроек bj. +func (h *setupHandlers) adGroupMembersHTTP(w http.ResponseWriter, r *http.Request) { + a := h.rc.Snapshot().Auth + dn := strings.TrimSpace(r.URL.Query().Get("dn")) + if !a.ADEnabled || a.ADBindUser == "" { + jsonOut(w, map[string]any{"ok": false, "error": "домен не подключён"}) + return + } + known := false + for _, g := range a.ADGroups { + known = known || strings.EqualFold(g.Group, dn) + } + if !known { + jsonOut(w, map[string]any{"ok": false, "error": "группы нет в настройках bj"}) + return + } + list, err := adGroupMembers(a, dn, 1000) + if err != nil { + jsonOut(w, map[string]any{"ok": false, "error": err.Error()}) + return + } + jsonOut(w, map[string]any{"ok": true, "members": list}) +} + +func adGroupMembers(a AuthSettings, groupDN string, limit int) ([]adMember, error) { + conn, err := adDial(a) + if err != nil { + return nil, err + } + defer conn.Close() + conn.SetTimeout(15 * time.Second) + if err := adBindService(conn, a); err != nil { + return nil, err + } + base := a.ADBaseDN + if base == "" { + base = adDefaultBase(conn) + } + filter := "(&(objectCategory=person)(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=" + ldap.EscapeFilter(groupDN) + "))" + sr, err := conn.Search(ldap.NewSearchRequest(base, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, limit, 15, false, + filter, []string{"sAMAccountName", "displayName", "mail", "userAccountControl"}, nil)) + if err != nil && (sr == nil || len(sr.Entries) == 0) { + return nil, fmt.Errorf("участники группы: %v", err) + } + out := []adMember{} + for _, en := range sr.Entries { + m := adMember{Login: en.GetAttributeValue("sAMAccountName"), Name: en.GetAttributeValue("displayName"), Email: en.GetAttributeValue("mail")} + if m.Login == "" { + continue + } + if uac, err := strconv.Atoi(en.GetAttributeValue("userAccountControl")); err == nil && uac&2 != 0 { + m.Disabled = true + } + m.InBJ = globalUsers.Get(m.Login) != nil + out = append(out, m) + } + sort.Slice(out, func(i, j int) bool { + if out[i].Disabled != out[j].Disabled { + return !out[i].Disabled + } + return strings.ToLower(pickStr(out[i].Name, out[i].Login)) < strings.ToLower(pickStr(out[j].Name, out[j].Login)) + }) + return out, nil +} diff --git a/internal/lkgateway/web/templates/part_accounts.html b/internal/lkgateway/web/templates/part_accounts.html index f0919f6..e576b24 100644 --- a/internal/lkgateway/web/templates/part_accounts.html +++ b/internal/lkgateway/web/templates/part_accounts.html @@ -54,9 +54,24 @@ {{if $A.ADGroups}}
Группы домена с доступом в bj — их сотрудники входят без отдельной учётной записи:
- {{range $A.ADGroups}}
{{.Name}} → {{roleTitle .Role}} + {{range $A.ADGroups}}
{{.Name}} → {{roleTitle .Role}}
участники: …
{{end}} -
{{end}} +
+ {{end}}