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}}