Files
glauth/v2/pkg/plugins/basesqlhandler.go
T
Chris F Ravenscroft 42ff05f141 Feature/dbsshkeys (#262)
* Dev -> Master after v1/v2 cleanup (#261)

* Reverse pull request from master to dev.

Reverse pull request.

* Fix/docker latest tag (#260)

* Dev (#254)

* use functional options pattern to inject logr (#124)

* use functional options pattern to inject logr

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* cleanup log formatting

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* allow clean shutdown (#126)

* fix owncloud posix query, log message and provisioning api results (#128)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* don't hardcode graphapi endpoint

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* add support for write handlers (#135)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* config: match shadowaccount objectlcass (#136)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* Makefile compatiblility (#134)

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* update travis.yml (#154)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* check owncloud status code is ok (#153)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* Bulid and push multiarch docker images (#142)

Fix #141.

With this change, a github action is added that builds a multiarch docker image on every commit, supporting x86_64, aarch64 and arm/v7.

When the action is triggered on a release or tag, it also uploads the versioned image to github container image registry.

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* don't mix graph and provisioning api (#157)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* Reuse http client as much as possible and allow insecure transport (#160)

* Allow using configmaps when deploying in kubernetes (#161)

* Ensure config watcher also works in kubernetes

When using configmaps in kubernetes, the file is a symlink, and then
file-watcher is not opdated with a write event. Instead it receives a
CHMOD and a REMOVE event.

This change adds two things
1) Removal of the current wacther and adding of a new watcher for the
same path
2) The do-reload conditional is updated to also include the remove
event.

* Align write and removed conditionals

* Fix API server json formating (#163) (#164)

* Update README.md (#167)

Fixing bad port in quickstart

* Use port 3893 with ldapsearch in example (#150)

* feat: add flags for ldap listen addresses (#169)

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>

* Assets build fix. (#171)

* Docker build fix.

* Makefile fix.

* update readme, config and deps (#170)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* Remove byanke's donation fields

* Database plugins (#133)

This commit actually covers a few items. In future commits, I will keep features distinct. This is only happening this time around due to how long it took to merge this branch.

Covered:

- Database plugins (at this time: SQLite, MySQL, Postgres)
- Backends acting as middleware: added the [[Backends]] configuration directive while retaining backward compatibility with [Backend]
- Schema introspection (root DSE query with base scope)
- When proxying, insert queried attribute back in upstream response, if absent, so that the LDAP library does not filter out all entries
- When chaining backends, any backend can be used to inject OTP value in password, before reaching a non-OTP-aware backend (currently guarded by a True statement in case we find an issue (I did thoroughly test but you never know))
- Handling of special "1.1" attributes filter meaning "I do not want attributes" (RFC 4511, 4.5.1.8)
- Support for "want types only" queries, even when proxying

* Mac M1 Support and LDAP Req Attributes (#192)

* Mac M1 Support

* Augmented root DSS and schema discovery based on content of schema directory

* LDAP workaround where req. attribute gets injected in response now works with combined filters

* SubSchema query can return a minimal set, freeipa or openldap's schemas

* Feature/upgrade ldap library version (#194)

* Stronger, salted paswords using bcrypt. (#195)

* Fixed badges in README file and added a couple improvements (#196)

* fix lock for ownCloud / graph backend (#198)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* the config struct is only needed by the config backend (#199)

* Variable "unixid" is now respectively "UIDNumber" and "GIDNumber" for… (#201)

* fix: keep watch config file when changed,renamed,removed (#189)

I will need to run `glauth.go` through `gofmt` myself but that is fine.

* Returning when unable to start config watcher (#203)

* Introducing goconvey testing and refactoring of config and ... (#204)

* Introducing goconvey testing and refactoring of config and plugin backends.

* gofmt, oops

* Refactored re-insertion of requested attributes

* Allow bind operations with no group provided (#205)

* Support for userPrincipalName binding and browsing. (#206)

* Support for userPrincipalName binding and browsing.

* Fixed travis CI test for userprincipalname

* fixing travis tests for more users

* Rate limit after failed binds (#207)

* Capabilities -- part 2 (#214) + do not dump config at startup

* Attempting migration from Travis to Github Actions

* Migration Step#2

* Migration Step#3

* Migration Step#4

* Migration Step#5

* Migration Step#6

* Migration Step#7

* Migration Step#8

* Migration Step#9

* Migration Step#9

* Migration dev...

* Migration dev...

* Migration dev...

* Migration dev...

* Docker with plugins, first step (#215)

* Docker with plugins, first step

* Build better docker images, including plugins

* Merge back from dev to feature branch step #2

* Added capabilities to CI

* - Now creating two docker images, including one with plugins
- Moving from Travis CI to GitHub Actions

* Plugins now run on distroless (#217)

* Getting rid of Travis CI now that it doesn't support FOSS anymore.

* Internal Stats -- performance view (#221)

* Local merge

* Update CI to forget about bindata

* Forgot to cleanup the cleanup...

* assets: use stdlib "embed" package (#200)

This should make it easier to include GLAuth in other projects using `go get`

* 'Airgapping' web assets for security and preventing breakage (#227)

* v2 -- V2 hierarchy (#228)

* Search refactoring, tree traversal and scope correctness (#229)

Search refactoring, tree traversal and scope correctness.

* Feature/multi cfg (#233)

* Feature/custom attributes (#240)

* Makefiles: platform releases (#241)

* Feature/release script (#242)

* Bug/fix docker build in v2 (#244)

* Fix #246 and #252

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Co-authored-by: Zhou Wenzong <wenzong@users.noreply.github.com>
Co-authored-by: Jairo Llopis <Yajo@users.noreply.github.com>
Co-authored-by: Benedikt Kulmann <benedikt@kulmann.biz>
Co-authored-by: Nicolai Willems <172633+nwillems@users.noreply.github.com>
Co-authored-by: Thibault Soubiran <soubi.thibault@gmail.com>
Co-authored-by: Clement JACOB <clems71@gmail.com>
Co-authored-by: Lutz Horn <code@lhorn.de>
Co-authored-by: Márk Sági-Kazár <sagikazarmark@users.noreply.github.com>
Co-authored-by: Peter Heise <pheise@koprolalie.com>
Co-authored-by: Ben Yanke <ben@benyanke.com>
Co-authored-by: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>

* Cleanup

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Co-authored-by: Zhou Wenzong <wenzong@users.noreply.github.com>
Co-authored-by: Jairo Llopis <Yajo@users.noreply.github.com>
Co-authored-by: Benedikt Kulmann <benedikt@kulmann.biz>
Co-authored-by: Nicolai Willems <172633+nwillems@users.noreply.github.com>
Co-authored-by: Thibault Soubiran <soubi.thibault@gmail.com>
Co-authored-by: Clement JACOB <clems71@gmail.com>
Co-authored-by: Lutz Horn <code@lhorn.de>
Co-authored-by: Márk Sági-Kazár <sagikazarmark@users.noreply.github.com>
Co-authored-by: Peter Heise <pheise@koprolalie.com>
Co-authored-by: Ben Yanke <ben@benyanke.com>
Co-authored-by: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Co-authored-by: Zhou Wenzong <wenzong@users.noreply.github.com>
Co-authored-by: Jairo Llopis <Yajo@users.noreply.github.com>
Co-authored-by: Benedikt Kulmann <benedikt@kulmann.biz>
Co-authored-by: Nicolai Willems <172633+nwillems@users.noreply.github.com>
Co-authored-by: Thibault Soubiran <soubi.thibault@gmail.com>
Co-authored-by: Clement JACOB <clems71@gmail.com>
Co-authored-by: Lutz Horn <code@lhorn.de>
Co-authored-by: Márk Sági-Kazár <sagikazarmark@users.noreply.github.com>
Co-authored-by: Peter Heise <pheise@koprolalie.com>
Co-authored-by: Ben Yanke <ben@benyanke.com>
Co-authored-by: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>

* SSH Keys support in database plugins

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Co-authored-by: Zhou Wenzong <wenzong@users.noreply.github.com>
Co-authored-by: Jairo Llopis <Yajo@users.noreply.github.com>
Co-authored-by: Benedikt Kulmann <benedikt@kulmann.biz>
Co-authored-by: Nicolai Willems <172633+nwillems@users.noreply.github.com>
Co-authored-by: Thibault Soubiran <soubi.thibault@gmail.com>
Co-authored-by: Clement JACOB <clems71@gmail.com>
Co-authored-by: Lutz Horn <code@lhorn.de>
Co-authored-by: Márk Sági-Kazár <sagikazarmark@users.noreply.github.com>
Co-authored-by: Peter Heise <pheise@koprolalie.com>
Co-authored-by: Ben Yanke <ben@benyanke.com>
Co-authored-by: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>
2022-05-08 19:33:27 -07:00

586 lines
17 KiB
Go

package main
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"net"
"os"
"regexp"
"sort"
"strconv"
"strings"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/v2/pkg/config"
"github.com/glauth/glauth/v2/pkg/handler"
"github.com/glauth/glauth/v2/pkg/stats"
"github.com/go-logr/logr"
"github.com/nmcclain/ldap"
)
var configattributematcher = regexp.MustCompile(`(?i)\((?P<attribute>[a-zA-Z0-9]+)\s*=\s*(?P<value>.*)\)`)
type SqlBackend interface {
// Name used by database/sql when loading the driver
GetDriverName() string
// Create db/schema if necessary
CreateSchema(db *sql.DB)
// Migrate schema if necessary
MigrateSchema(db *sql.DB, checker func(*sql.DB, string) bool)
//
GetPrepareSymbol() string
}
type database struct {
path string
cnx *sql.DB
}
type databaseHandler struct {
backend config.Backend
log logr.Logger
cfg *config.Config
yubikeyAuth *yubigo.YubiAuth
sqlBackend SqlBackend
database database
MemGroups []config.Group
ldohelper handler.LDAPOpsHelper
attmatcher *regexp.Regexp
}
// func NewDatabaseHandler_deprecated(log *logging.Logger, cfg *config.Config, yubikeyAuth *yubigo.YubiAuth, sqlBackend SqlBackend) handler.Handler {
func NewDatabaseHandler(sqlBackend SqlBackend, opts ...handler.Option) handler.Handler {
options := handler.NewOptions(opts...)
// Note: we will never terminate this connection pool.
db, err := sql.Open(sqlBackend.GetDriverName(), options.Backend.Database)
if err != nil {
options.Logger.Error(err, "Unable to open SQL database named '%s' error: %s", options.Backend.Database)
os.Exit(1)
}
err = db.Ping()
if err != nil {
options.Logger.Error(err, "Unable to communicate with SQL database error: %s", options.Backend.Database)
os.Exit(1)
}
dbInfo := database{
path: options.Backend.Database,
cnx: db,
}
handler := databaseHandler{
backend: options.Backend,
log: options.Logger,
cfg: options.Config,
yubikeyAuth: options.YubiAuth,
sqlBackend: sqlBackend,
database: dbInfo,
ldohelper: options.LDAPHelper,
attmatcher: configattributematcher}
sqlBackend.CreateSchema(db)
sqlBackend.MigrateSchema(db, ColumnExists)
options.Logger.V(3).Info("Database (" + sqlBackend.GetDriverName() + "::" + options.Backend.Database + ") Plugin: Ready")
return handler
}
func ColumnExists(db *sql.DB, columnName string) bool {
var found string
err := db.QueryRow(fmt.Sprintf(`SELECT COUNT(%s) FROM users`, columnName)).Scan(
&found)
if err != nil {
return false
}
return true
}
func (h databaseHandler) GetBackend() config.Backend {
return h.backend
}
func (h databaseHandler) GetLog() logr.Logger {
return h.log
}
func (h databaseHandler) GetCfg() *config.Config {
return h.cfg
}
func (h databaseHandler) GetYubikeyAuth() *yubigo.YubiAuth {
return h.yubikeyAuth
}
func (h databaseHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
return h.ldohelper.Bind(h, bindDN, bindSimplePw, conn)
}
func (h databaseHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
return h.ldohelper.Search(h, bindDN, searchReq, conn)
}
// Add is not yet supported for the sql backend
func (h databaseHandler) Add(boundDN string, req ldap.AddRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Modify is not yet supported for the sql backend
func (h databaseHandler) Modify(boundDN string, req ldap.ModifyRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Delete is not yet supported for the sql backend
func (h databaseHandler) Delete(boundDN string, deleteDN string, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
func (h databaseHandler) FindUser(userName string, searchByUPN bool) (f bool, u config.User, err error) {
var criterion string
if searchByUPN {
criterion = "lower(u.mail)"
} else {
criterion = "lower(u.name)"
}
user := config.User{}
found := false
var disabled int
err = h.database.cnx.QueryRow(fmt.Sprintf(`
SELECT u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.disabled
FROM users u WHERE %s=%s`,
criterion,
h.sqlBackend.GetPrepareSymbol()), userName).Scan(
&user.UIDNumber, &user.PrimaryGroup, &user.PassBcrypt, &user.PassSHA256, &user.OTPSecret, &user.Yubikey, &disabled)
if err == nil {
user.Disabled = h.intToBool(disabled)
if !user.Disabled {
found = true
if !h.cfg.Behaviors.IgnoreCapabilities {
capability := config.Capability{}
rows, err := h.database.cnx.Query(fmt.Sprintf(`
SELECT c.action,c.object
FROM capabilities c WHERE userid=%s`,
h.sqlBackend.GetPrepareSymbol()), user.UIDNumber)
if err == nil {
for rows.Next() {
err := rows.Scan(&capability.Action, &capability.Object)
if err == nil {
user.Capabilities = append(user.Capabilities, capability)
}
}
}
defer rows.Close()
}
}
}
return found, user, err
}
func (h databaseHandler) FindGroup(groupName string) (f bool, g config.Group, err error) {
group := config.Group{}
found := false
err = h.database.cnx.QueryRow(fmt.Sprintf(`
SELECT g.gidnumber FROM groups g WHERE lower(name)=%s`, h.sqlBackend.GetPrepareSymbol()), groupName).Scan(
&group.GIDNumber)
if err == nil {
found = true
}
return found, group, err
}
func (h databaseHandler) FindPosixAccounts(hierarchy string) (entrylist []*ldap.Entry, err error) {
entries := []*ldap.Entry{}
h.MemGroups, err = h.memoizeGroups()
if err != nil {
return entries, err
}
rows, err := h.database.cnx.Query(`
SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups,u.givenname,u.sn,u.mail,u.loginshell,u.homedirectory,u.disabled,u.sshkeys,u.custattr
FROM users u`)
if err != nil {
return entries, err
}
defer rows.Close()
var otherGroups string
var disabled int
var sshKeys string
var custattrstr string
u := config.User{}
for rows.Next() {
err := rows.Scan(&u.Name, &u.UIDNumber, &u.PrimaryGroup, &u.PassBcrypt, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups, &u.GivenName, &u.SN, &u.Mail, &u.LoginShell, &u.Homedir, &disabled, &sshKeys, &custattrstr)
if err != nil {
return entries, err
}
u.OtherGroups = h.commaListToIntTable(otherGroups)
u.Disabled = h.intToBool(disabled)
u.SSHKeys = h.commaListToStringTable(sshKeys)
entry := h.getAccount(hierarchy, u)
if custattrstr != "{}" {
var r map[string]interface{}
err := json.Unmarshal([]byte(custattrstr), &r)
if err != nil {
return entries, err
}
for key, attr := range r {
switch typedattr := attr.(type) {
case []interface{}:
var values []string
for _, v := range typedattr {
switch typedvalue := v.(type) {
case string:
values = append(values, handler.MaybeDecode(typedvalue))
default:
values = append(values, handler.MaybeDecode(fmt.Sprintf("%v", typedvalue)))
}
}
entry.Attributes = append(entry.Attributes, &ldap.EntryAttribute{Name: key, Values: values})
default:
h.log.V(2).Info("Unable to map custom attribute", "key", key, "value", attr)
}
}
}
entries = append(entries, entry)
}
return entries, nil
}
func (h databaseHandler) FindPosixGroups(hierarchy string) (entrylist []*ldap.Entry, err error) {
entries := []*ldap.Entry{}
h.MemGroups, err = h.memoizeGroups()
if err != nil {
return entries, err
}
for _, g := range h.MemGroups {
info := h.getGroup(hierarchy, g)
if hierarchy != "groups" {
info.DN = strings.Replace(info.DN, ",ou=groups,", fmt.Sprintf(",%s,", hierarchy), 1)
}
entries = append(entries, info)
}
return entries, nil
}
func (h databaseHandler) Close(boundDn string, conn net.Conn) error {
stats.Frontend.Add("closes", 1)
return nil
}
func (h databaseHandler) intToBool(value int) bool {
if value == 0 {
return false
}
return true
}
func (h databaseHandler) commaListToIntTable(commaList string) []int {
if len(commaList) == 0 {
return make([]int, 0)
}
rowsAsStrings := strings.Split(commaList, ",")
rowsAsInts := make([]int, len(rowsAsStrings))
for i, v := range rowsAsStrings {
iv, err := strconv.Atoi(v)
if err != nil {
return rowsAsInts
}
rowsAsInts[i] = iv
}
return rowsAsInts
}
func (h databaseHandler) commaListToStringTable(commaList string) []string {
if len(commaList) == 0 {
return make([]string, 0)
}
return strings.Split(commaList, ",")
}
func (h databaseHandler) memoizeGroups() ([]config.Group, error) {
workMemGroups := make([]*config.Group, 0)
rows, err := h.database.cnx.Query(`
SELECT g1.name,g1.gidnumber,ig.includegroupid
FROM groups g1
LEFT JOIN includegroups ig ON g1.gidnumber=ig.parentgroupid
LEFT JOIN groups g2 ON ig.includegroupid=g2.gidnumber`)
if err != nil {
return nil, errors.New("Unable to memoize groups list")
}
defer rows.Close()
var groupName string
var groupId int
var includeId sql.NullInt64 // Store includeid from left join
var pg *config.Group
recentId := -1 // id of recently updated group
for rows.Next() {
err := rows.Scan(&groupName, &groupId, &includeId)
if err != nil {
return nil, errors.New("Unable to memoize groups list")
}
if recentId != groupId {
recentId = groupId
g := config.Group{Name: groupName, GIDNumber: groupId}
pg = &g // To manipulate end of slice
workMemGroups = append(workMemGroups, &g)
}
if includeId.Valid {
pg.IncludeGroups = append(pg.IncludeGroups, int(includeId.Int64))
}
}
memGroups := make([]config.Group, len(workMemGroups))
for i, v := range workMemGroups {
memGroups[i] = config.Group{Name: v.Name, GIDNumber: v.GIDNumber, IncludeGroups: v.IncludeGroups}
}
return memGroups, nil
}
func (h databaseHandler) getGroupMemberDNs(gid int) []string {
members := make(map[string]bool)
rows, err := h.database.cnx.Query(`
SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
FROM users u`)
if err != nil {
// Silent fail... for now
return []string{}
}
defer rows.Close()
var otherGroups string
u := config.User{}
for rows.Next() {
err := rows.Scan(&u.Name, &u.UIDNumber, &u.PrimaryGroup, &u.PassBcrypt, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups)
if err != nil {
return []string{}
}
if u.PrimaryGroup == gid {
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
members[dn] = true
} else {
u.OtherGroups = h.commaListToIntTable(otherGroups)
for _, othergid := range u.OtherGroups {
if othergid == gid {
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
members[dn] = true
}
}
}
}
for _, g := range h.MemGroups {
if gid == g.GIDNumber {
for _, includegroupid := range g.IncludeGroups {
if includegroupid != gid {
includegroupmembers := h.getGroupMemberDNs(includegroupid)
for _, includegroupmember := range includegroupmembers {
members[includegroupmember] = true
}
}
}
}
}
m := []string{}
for k := range members {
m = append(m, k)
}
sort.Strings(m)
return m
}
// Used exclusively when looking up Posix Groups
func (h databaseHandler) getGroupMemberIDs(gid int) []string {
members := make(map[string]bool)
rows, err := h.database.cnx.Query(`
SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
FROM users u`)
if err != nil {
// Silent fail... for now
return []string{}
}
defer rows.Close()
var otherGroups string
u := config.User{}
for rows.Next() {
err := rows.Scan(&u.Name, &u.UIDNumber, &u.PrimaryGroup, &u.PassBcrypt, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups)
if err != nil {
return []string{}
}
if u.PrimaryGroup == gid {
members[u.Name] = true
} else {
u.OtherGroups = h.commaListToIntTable(otherGroups)
for _, othergid := range u.OtherGroups {
if othergid == gid {
members[u.Name] = true
}
}
}
}
for _, g := range h.MemGroups {
if gid == g.GIDNumber {
for _, includegroupid := range g.IncludeGroups {
if includegroupid == gid {
h.log.V(3).Info(fmt.Sprintf("Group: %d - Ignoring myself as included group", includegroupid))
} else {
includegroupmemberids := h.getGroupMemberIDs(includegroupid)
for _, includegroupmemberid := range includegroupmemberids {
members[includegroupmemberid] = true
}
}
}
}
}
m := []string{}
for k := range members {
m = append(m, k)
}
sort.Strings(m)
return m
}
// Invoked for every user being returned from our database
func (h databaseHandler) getGroupDNs(gids []int) []string {
groups := make(map[string]bool)
for _, gid := range gids {
for _, g := range h.MemGroups {
if g.GIDNumber == gid {
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormat, g.Name, h.backend.BaseDN)
groups[dn] = true
}
for _, includegroupid := range g.IncludeGroups {
if includegroupid == gid && g.GIDNumber != gid {
includegroupdns := h.getGroupDNs([]int{g.GIDNumber})
for _, includegroupdn := range includegroupdns {
groups[includegroupdn] = true
}
}
}
}
}
g := []string{}
for k := range groups {
g = append(g, k)
}
sort.Strings(g)
return g
}
// Invoked for every user being returned from our database
func (h databaseHandler) getGroupName(gid int) string {
for _, g := range h.MemGroups {
if g.GIDNumber == gid {
return g.Name
}
}
return ""
}
// Toolbox
func (h databaseHandler) getGroup(hierarchy string, g config.Group) *ldap.Entry {
asGroupOfUniqueNames := hierarchy == "ou=groups"
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{"cn", []string{g.Name}})
attrs = append(attrs, &ldap.EntryAttribute{"description", []string{fmt.Sprintf("%s via LDAP", g.Name)}})
attrs = append(attrs, &ldap.EntryAttribute{"gidNumber", []string{fmt.Sprintf("%d", g.GIDNumber)}})
attrs = append(attrs, &ldap.EntryAttribute{"uniqueMember", h.getGroupMemberDNs(g.GIDNumber)})
if asGroupOfUniqueNames {
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"groupOfUniqueNames", "top"}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{"memberUid", h.getGroupMemberIDs(g.GIDNumber)})
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"posixGroup", "top"}})
}
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormat, g.Name, h.backend.BaseDN)
return &ldap.Entry{dn, attrs}
}
func (h databaseHandler) getAccount(hierarchy string, u config.User) *ldap.Entry {
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{"cn", []string{u.Name}})
attrs = append(attrs, &ldap.EntryAttribute{"uid", []string{u.Name}})
if len(u.GivenName) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{"givenName", []string{u.GivenName}})
}
if len(u.SN) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{"sn", []string{u.SN}})
}
attrs = append(attrs, &ldap.EntryAttribute{"ou", []string{h.getGroupName(u.PrimaryGroup)}})
attrs = append(attrs, &ldap.EntryAttribute{"uidNumber", []string{fmt.Sprintf("%d", u.UIDNumber)}})
if u.Disabled {
attrs = append(attrs, &ldap.EntryAttribute{"accountStatus", []string{"inactive"}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{"accountStatus", []string{"active"}})
}
if len(u.Mail) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{"mail", []string{u.Mail}})
attrs = append(attrs, &ldap.EntryAttribute{"userPrincipalName", []string{u.Mail}})
}
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"posixAccount"}})
if len(u.LoginShell) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{"loginShell", []string{u.LoginShell}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{"loginShell", []string{"/bin/bash"}})
}
if len(u.Homedir) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{"homeDirectory", []string{u.Homedir}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{"homeDirectory", []string{"/home/" + u.Name}})
}
attrs = append(attrs, &ldap.EntryAttribute{"description", []string{fmt.Sprintf("%s via LDAP", u.Name)}})
attrs = append(attrs, &ldap.EntryAttribute{"gecos", []string{fmt.Sprintf("%s via LDAP", u.Name)}})
attrs = append(attrs, &ldap.EntryAttribute{"gidNumber", []string{fmt.Sprintf("%d", u.PrimaryGroup)}})
attrs = append(attrs, &ldap.EntryAttribute{"memberOf", h.getGroupDNs(append(u.OtherGroups, u.PrimaryGroup))})
if len(u.SSHKeys) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{"sshPublicKey", u.SSHKeys})
}
var dn string
if hierarchy == "" {
dn = fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
} else {
dn = fmt.Sprintf("%s=%s,%s=%s,%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), hierarchy, h.backend.BaseDN)
}
return &ldap.Entry{dn, attrs}
}
func main() {}