688 lines
20 KiB
Go
688 lines
20 KiB
Go
package plugins
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/rs/zerolog"
|
|
"github.com/uptrace/opentelemetry-go-extra/otelsql"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
|
|
"go.opentelemetry.io/otel/trace"
|
|
|
|
"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/glauth/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, string) bool)
|
|
//
|
|
GetPrepareSymbol() string
|
|
}
|
|
|
|
type database struct {
|
|
path string
|
|
cnx *sql.DB
|
|
}
|
|
|
|
type databaseHandler struct {
|
|
backend config.Backend
|
|
log *zerolog.Logger
|
|
cfg *config.Config
|
|
yubikeyAuth *yubigo.YubiAuth
|
|
sqlBackend SqlBackend
|
|
database database
|
|
MemGroups []config.Group
|
|
ldohelper handler.LDAPOpsHelper
|
|
attmatcher *regexp.Regexp
|
|
|
|
tracer trace.Tracer
|
|
}
|
|
|
|
func NewDatabaseHandler(sqlBackend SqlBackend, opts ...handler.Option) handler.Handler {
|
|
options := handler.NewOptions(opts...)
|
|
|
|
// Note: we will never terminate this connection pool.
|
|
db, err := otelsql.Open(
|
|
sqlBackend.GetDriverName(),
|
|
options.Backend.Database,
|
|
otelsql.WithAttributes(otlpDriverAttribute(sqlBackend)),
|
|
otelsql.WithDBName(options.Backend.Database),
|
|
)
|
|
|
|
if err != nil {
|
|
options.Logger.Error().Err(err).Msg(fmt.Sprintf("unable to open SQL database named '%s'", options.Backend.Database))
|
|
os.Exit(1)
|
|
}
|
|
err = db.Ping()
|
|
if err != nil {
|
|
options.Logger.Error().Err(err).Msg(fmt.Sprintf("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,
|
|
tracer: options.Tracer,
|
|
}
|
|
|
|
sqlBackend.CreateSchema(db)
|
|
sqlBackend.MigrateSchema(db, ColumnExists)
|
|
|
|
options.Logger.Debug().Msg("Database (" + sqlBackend.GetDriverName() + "::" + options.Backend.Database + ") Plugin: Ready")
|
|
|
|
return handler
|
|
}
|
|
|
|
func ColumnExists(db *sql.DB, tableName string, columnName string) bool {
|
|
var found string
|
|
err := db.QueryRowContext(context.Background(), fmt.Sprintf(`SELECT COUNT(%s) FROM %s`, columnName, tableName)).Scan(
|
|
&found)
|
|
return err == nil
|
|
}
|
|
|
|
func otlpDriverAttribute(backend SqlBackend) attribute.KeyValue {
|
|
switch backend.GetDriverName() {
|
|
case "sqlite3":
|
|
return semconv.DBSystemSqlite
|
|
case "postgres":
|
|
return semconv.DBSystemPostgreSQL
|
|
case "mysql":
|
|
return semconv.DBSystemMySQL
|
|
default:
|
|
return semconv.DBSystemOtherSQL
|
|
}
|
|
}
|
|
|
|
func (h databaseHandler) GetBackend() config.Backend {
|
|
return h.backend
|
|
}
|
|
func (h databaseHandler) GetLog() *zerolog.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) {
|
|
ctx, span := h.tracer.Start(context.Background(), "plugins.databaseHandler.Bind")
|
|
defer span.End()
|
|
|
|
return h.ldohelper.Bind(ctx, h, bindDN, bindSimplePw, conn)
|
|
}
|
|
|
|
func (h databaseHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
|
|
ctx, span := h.tracer.Start(context.Background(), "plugins.databaseHandler.Search")
|
|
defer span.End()
|
|
|
|
return h.ldohelper.Search(ctx, 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) {
|
|
_, span := h.tracer.Start(context.Background(), "plugins.databaseHandler.Add")
|
|
defer span.End()
|
|
|
|
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) {
|
|
_, span := h.tracer.Start(context.Background(), "plugins.databaseHandler.Modify")
|
|
defer span.End()
|
|
|
|
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) {
|
|
_, span := h.tracer.Start(context.Background(), "plugins.databaseHandler.Delete")
|
|
defer span.End()
|
|
|
|
return ldap.LDAPResultInsufficientAccessRights, nil
|
|
}
|
|
|
|
func (h databaseHandler) FindUser(ctx context.Context, userName string, searchByUPN bool) (f bool, u config.User, err error) {
|
|
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.FindUser")
|
|
defer span.End()
|
|
|
|
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.QueryRowContext(
|
|
ctx,
|
|
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.QueryContext(ctx, 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(ctx context.Context, groupName string) (f bool, g config.Group, err error) {
|
|
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.FindGroup")
|
|
defer span.End()
|
|
|
|
group := config.Group{}
|
|
found := false
|
|
|
|
err = h.database.cnx.QueryRowContext(
|
|
ctx,
|
|
fmt.Sprintf(`
|
|
SELECT g.gidnumber FROM ldapgroups 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(ctx context.Context, hierarchy string) (entrylist []*ldap.Entry, err error) {
|
|
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.FindPosixAccounts")
|
|
defer span.End()
|
|
|
|
entries := []*ldap.Entry{}
|
|
|
|
h.MemGroups, err = h.memoizeGroups(ctx)
|
|
if err != nil {
|
|
return entries, err
|
|
}
|
|
|
|
rows, err := h.database.cnx.QueryContext(
|
|
ctx,
|
|
`
|
|
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(ctx, otherGroups)
|
|
u.Disabled = h.intToBool(disabled)
|
|
u.SSHKeys = h.commaListToStringTable(ctx, sshKeys)
|
|
|
|
entry := h.getAccount(ctx, 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.Warn().Str("key", key).Interface("value", attr).Msg("Unable to map custom attribute")
|
|
}
|
|
}
|
|
}
|
|
|
|
entries = append(entries, entry)
|
|
}
|
|
|
|
return entries, nil
|
|
}
|
|
|
|
func (h databaseHandler) FindPosixGroups(ctx context.Context, hierarchy string) (entrylist []*ldap.Entry, err error) {
|
|
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.FindPosixGroups")
|
|
defer span.End()
|
|
|
|
entries := []*ldap.Entry{}
|
|
|
|
h.MemGroups, err = h.memoizeGroups(ctx)
|
|
if err != nil {
|
|
return entries, err
|
|
}
|
|
|
|
for _, g := range h.MemGroups {
|
|
info := h.getGroup(ctx, 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 {
|
|
_, span := h.tracer.Start(context.Background(), "plugins.databaseHandler.Close")
|
|
defer span.End()
|
|
|
|
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(ctx context.Context, commaList string) []int {
|
|
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.commaListToIntTable")
|
|
defer span.End()
|
|
|
|
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(ctx context.Context, commaList string) []string {
|
|
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.commaListToStringTable")
|
|
defer span.End()
|
|
|
|
if len(commaList) == 0 {
|
|
return make([]string, 0)
|
|
}
|
|
return strings.Split(commaList, ",")
|
|
}
|
|
|
|
func (h databaseHandler) memoizeGroups(ctx context.Context) ([]config.Group, error) {
|
|
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.memoizeGroups")
|
|
defer span.End()
|
|
|
|
workMemGroups := make([]*config.Group, 0)
|
|
rows, err := h.database.cnx.QueryContext(
|
|
ctx,
|
|
`
|
|
SELECT g1.name,g1.gidnumber,ig.includegroupid
|
|
FROM ldapgroups g1
|
|
LEFT JOIN includegroups ig ON g1.gidnumber=ig.parentgroupid
|
|
LEFT JOIN ldapgroups 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(ctx context.Context, gid int) []string {
|
|
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.getGroupMemberDNs")
|
|
defer span.End()
|
|
|
|
var insertOuUsers string
|
|
if h.cfg.Behaviors.LegacyVersion > 0 && h.cfg.Behaviors.LegacyVersion <= 20100 {
|
|
insertOuUsers = ""
|
|
} else {
|
|
insertOuUsers = ",ou=users"
|
|
}
|
|
members := make(map[string]bool)
|
|
|
|
rows, err := h.database.cnx.QueryContext(
|
|
ctx,
|
|
`
|
|
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,%s", h.backend.NameFormatAsArray[0], u.Name, h.backend.GroupFormatAsArray[0], h.getGroupName(ctx, u.PrimaryGroup), insertOuUsers, h.backend.BaseDN)
|
|
members[dn] = true
|
|
} else {
|
|
u.OtherGroups = h.commaListToIntTable(ctx, otherGroups)
|
|
for _, othergid := range u.OtherGroups {
|
|
if othergid == gid {
|
|
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormatAsArray[0], u.Name, h.backend.GroupFormatAsArray[0], h.getGroupName(ctx, u.PrimaryGroup), insertOuUsers, 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(ctx, 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(ctx context.Context, gid int) []string {
|
|
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.getGroupMemberIDs")
|
|
defer span.End()
|
|
|
|
members := make(map[string]bool)
|
|
rows, err := h.database.cnx.QueryContext(
|
|
ctx,
|
|
`
|
|
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(ctx, 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.Warn().Msg(fmt.Sprintf("Group: %d - Ignoring myself as included group", includegroupid))
|
|
} else {
|
|
includegroupmemberids := h.getGroupMemberIDs(ctx, 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(ctx context.Context, gids []int) []string {
|
|
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.getGroupDNs")
|
|
defer span.End()
|
|
|
|
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.GroupFormatAsArray[0], g.Name, h.backend.BaseDN)
|
|
groups[dn] = true
|
|
}
|
|
|
|
for _, includegroupid := range g.IncludeGroups {
|
|
if includegroupid == gid && g.GIDNumber != gid {
|
|
includegroupdns := h.getGroupDNs(ctx, []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(ctx context.Context, gid int) string {
|
|
_, span := h.tracer.Start(ctx, "plugins.databaseHandler.getGroupName")
|
|
defer span.End()
|
|
|
|
for _, g := range h.MemGroups {
|
|
if g.GIDNumber == gid {
|
|
return g.Name
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Toolbox
|
|
func (h databaseHandler) getGroup(ctx context.Context, hierarchy string, g config.Group) *ldap.Entry {
|
|
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.getGroup")
|
|
defer span.End()
|
|
|
|
asGroupOfUniqueNames := hierarchy == "ou=groups"
|
|
|
|
attrs := []*ldap.EntryAttribute{}
|
|
for _, groupAttr := range h.backend.GroupFormatAsArray {
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: groupAttr, Values: []string{g.Name}})
|
|
}
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s via LDAP", g.Name)}})
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "gidNumber", Values: []string{fmt.Sprintf("%d", g.GIDNumber)}})
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "uniqueMember", Values: h.getGroupMemberDNs(ctx, g.GIDNumber)})
|
|
if asGroupOfUniqueNames {
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"groupOfUniqueNames", "top"}})
|
|
} else {
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberUid", Values: h.getGroupMemberIDs(ctx, g.GIDNumber)})
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixGroup", "top"}})
|
|
}
|
|
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormatAsArray[0], g.Name, h.backend.BaseDN)
|
|
return &ldap.Entry{DN: dn, Attributes: attrs}
|
|
}
|
|
|
|
func (h databaseHandler) getAccount(ctx context.Context, hierarchy string, u config.User) *ldap.Entry {
|
|
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.getAccount")
|
|
defer span.End()
|
|
|
|
attrs := []*ldap.EntryAttribute{}
|
|
for _, nameAttr := range h.backend.NameFormatAsArray {
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: nameAttr, Values: []string{u.Name}})
|
|
}
|
|
|
|
if len(u.GivenName) > 0 {
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "givenName", Values: []string{u.GivenName}})
|
|
}
|
|
|
|
if len(u.SN) > 0 {
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "sn", Values: []string{u.SN}})
|
|
}
|
|
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "ou", Values: []string{h.getGroupName(ctx, u.PrimaryGroup)}})
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "uidNumber", Values: []string{fmt.Sprintf("%d", u.UIDNumber)}})
|
|
|
|
if u.Disabled {
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}})
|
|
} else {
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}})
|
|
}
|
|
|
|
if len(u.Mail) > 0 {
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "mail", Values: []string{u.Mail}})
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "userPrincipalName", Values: []string{u.Mail}})
|
|
}
|
|
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixAccount"}})
|
|
|
|
if len(u.LoginShell) > 0 {
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "loginShell", Values: []string{u.LoginShell}})
|
|
} else {
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "loginShell", Values: []string{"/bin/bash"}})
|
|
}
|
|
|
|
if len(u.Homedir) > 0 {
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "homeDirectory", Values: []string{u.Homedir}})
|
|
} else {
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "homeDirectory", Values: []string{"/home/" + u.Name}})
|
|
}
|
|
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s via LDAP", u.Name)}})
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "gecos", Values: []string{fmt.Sprintf("%s via LDAP", u.Name)}})
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "gidNumber", Values: []string{fmt.Sprintf("%d", u.PrimaryGroup)}})
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberOf", Values: h.getGroupDNs(ctx, append(u.OtherGroups, u.PrimaryGroup))})
|
|
if len(u.SSHKeys) > 0 {
|
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "sshPublicKey", Values: u.SSHKeys})
|
|
}
|
|
var dn string
|
|
if hierarchy == "" {
|
|
dn = fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormatAsArray[0], u.Name, h.backend.GroupFormatAsArray[0], h.getGroupName(ctx, u.PrimaryGroup), h.backend.BaseDN)
|
|
} else {
|
|
dn = fmt.Sprintf("%s=%s,%s=%s,%s,%s", h.backend.NameFormatAsArray[0], u.Name, h.backend.GroupFormatAsArray[0], h.getGroupName(ctx, u.PrimaryGroup), hierarchy, h.backend.BaseDN)
|
|
}
|
|
return &ldap.Entry{DN: dn, Attributes: attrs}
|
|
}
|