auth.ldap checks a username and password against the domain controllers over LDAPS or StartTLS: found with a service account, checked by binding as the person. Groups, nested ones included, give staff roles and places in the tree. Accounts are tied to the objectGUID; the leader reads the directory every 15 minutes, suspends whom it disabled or removed and reinstates them after, and refuses a sync that would suspend most. Entra ID sends no groups for someone in more than 200, only where to fetch them; that was read as none and took an administrator's role away. It is now left as it was, and logged. An account made by SCIM or OIDC can be reinstated again. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
93 lines
3.0 KiB
Go
93 lines
3.0 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"time"
|
|
)
|
|
|
|
// DirectoryLink is an account kept by an outside directory: which one, the
|
|
// account's id there, and whether the directory's sync is what suspended it.
|
|
type DirectoryLink struct {
|
|
UserID string
|
|
Directory string // "ldap"
|
|
ExternalID string // an AD objectGUID
|
|
// SuspendedBySync is an account suspended because the directory
|
|
// disabled or lost it, and so reinstated when the directory has it
|
|
// enabled again. One an administrator suspended is not.
|
|
SuspendedBySync bool
|
|
SyncedAt time.Time
|
|
}
|
|
|
|
// DirectoryUser is the account a directory knows by that id.
|
|
func (s *DB) DirectoryUser(ctx context.Context, directory, externalID string) (DirectoryLink, error) {
|
|
var l DirectoryLink
|
|
var suspended int
|
|
var synced int64
|
|
err := s.db.QueryRowContext(ctx,
|
|
`SELECT user_id, directory, external_id, suspended_by_sync, synced_at FROM user_directory
|
|
WHERE directory = ? AND external_id = ?`, directory, externalID).
|
|
Scan(&l.UserID, &l.Directory, &l.ExternalID, &suspended, &synced)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return DirectoryLink{}, ErrNotFound
|
|
}
|
|
if err != nil {
|
|
return DirectoryLink{}, err
|
|
}
|
|
l.SuspendedBySync, l.SyncedAt = suspended != 0, directoryTime(synced)
|
|
return l, nil
|
|
}
|
|
|
|
// LinkDirectory records that a directory keeps an account, by its id there.
|
|
func (s *DB) LinkDirectory(ctx context.Context, userID, directory, externalID string, at time.Time) error {
|
|
_, err := s.db.ExecContext(ctx,
|
|
`INSERT INTO user_directory (user_id, directory, external_id, synced_at) VALUES (?, ?, ?, ?)
|
|
ON CONFLICT (user_id) DO UPDATE SET directory = excluded.directory, external_id = excluded.external_id,
|
|
synced_at = excluded.synced_at`,
|
|
userID, directory, externalID, at.UnixMilli())
|
|
return err
|
|
}
|
|
|
|
// DirectoryLinks are every account a directory keeps.
|
|
func (s *DB) DirectoryLinks(ctx context.Context, directory string) ([]DirectoryLink, error) {
|
|
rows, err := s.db.QueryContext(ctx,
|
|
`SELECT user_id, directory, external_id, suspended_by_sync, synced_at FROM user_directory WHERE directory = ?`,
|
|
directory)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { _ = rows.Close() }()
|
|
var out []DirectoryLink
|
|
for rows.Next() {
|
|
var l DirectoryLink
|
|
var suspended int
|
|
var synced int64
|
|
if err := rows.Scan(&l.UserID, &l.Directory, &l.ExternalID, &suspended, &synced); err != nil {
|
|
return nil, err
|
|
}
|
|
l.SuspendedBySync, l.SyncedAt = suspended != 0, directoryTime(synced)
|
|
out = append(out, l)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// SetSyncSuspended says whether the directory's sync is what has an account
|
|
// suspended, and when it last looked.
|
|
func (s *DB) SetSyncSuspended(ctx context.Context, userID string, suspended bool, at time.Time) error {
|
|
v := 0
|
|
if suspended {
|
|
v = 1
|
|
}
|
|
_, err := s.db.ExecContext(ctx,
|
|
`UPDATE user_directory SET suspended_by_sync = ?, synced_at = ? WHERE user_id = ?`, v, at.UnixMilli(), userID)
|
|
return err
|
|
}
|
|
|
|
func directoryTime(ms int64) time.Time {
|
|
if ms == 0 {
|
|
return time.Time{}
|
|
}
|
|
return time.UnixMilli(ms)
|
|
}
|