forked from Nixius/authelia
1
0
Fork 0
ATLAS/docker/ss-atlas/internal/ldap/client.go

199 lines
5.1 KiB
Go

package ldap
import (
"crypto/rand"
"encoding/base64"
"fmt"
"log"
"git.nixc.us/a250/ss-atlas/internal/config"
goldap "github.com/go-ldap/ldap/v3"
)
type Client struct {
cfg *config.Config
}
type ProvisionResult struct {
Username string
Password string
IsNew bool
}
func New(cfg *config.Config) *Client {
return &Client{cfg: cfg}
}
func (c *Client) connect() (*goldap.Conn, error) {
conn, err := goldap.DialURL(c.cfg.LDAPUrl)
if err != nil {
return nil, fmt.Errorf("ldap dial: %w", err)
}
if err := conn.Bind(c.cfg.LDAPAdminDN, c.cfg.LDAPAdminPassword); err != nil {
conn.Close()
return nil, fmt.Errorf("ldap bind: %w", err)
}
return conn, nil
}
func (c *Client) ProvisionUser(username, email, stripeCustomerID string) (*ProvisionResult, error) {
conn, err := c.connect()
if err != nil {
return nil, err
}
defer conn.Close()
exists, err := c.userExists(conn, username)
if err != nil {
return nil, err
}
if exists {
log.Printf("ldap user %s already exists", username)
return &ProvisionResult{Username: username, IsNew: false}, nil
}
password := generatePassword()
userDN := fmt.Sprintf("uid=%s,ou=people,%s", username, c.cfg.LDAPBaseDN)
addReq := goldap.NewAddRequest(userDN, nil)
addReq.Attribute("objectClass", []string{"inetOrgPerson"})
addReq.Attribute("cn", []string{username})
addReq.Attribute("sn", []string{username})
addReq.Attribute("uid", []string{username})
addReq.Attribute("mail", []string{email})
addReq.Attribute("userPassword", []string{password})
addReq.Attribute("description", []string{stripeCustomerID})
if err := conn.Add(addReq); err != nil {
return nil, fmt.Errorf("ldap add user %s: %w", username, err)
}
log.Printf("created ldap user %s (%s)", username, email)
return &ProvisionResult{Username: username, Password: password, IsNew: true}, nil
}
func (c *Client) EnsureUser(username, email, stripeCustomerID string) error {
_, err := c.ProvisionUser(username, email, stripeCustomerID)
return err
}
func (c *Client) AddToGroup(username, groupName string) error {
conn, err := c.connect()
if err != nil {
return err
}
defer conn.Close()
groupDN := fmt.Sprintf("cn=%s,ou=groups,%s", groupName, c.cfg.LDAPBaseDN)
userDN := fmt.Sprintf("uid=%s,ou=people,%s", username, c.cfg.LDAPBaseDN)
modReq := goldap.NewModifyRequest(groupDN, nil)
modReq.Add("member", []string{userDN})
if err := conn.Modify(modReq); err != nil {
return fmt.Errorf("ldap add %s to group %s: %w", username, groupName, err)
}
log.Printf("added %s to group %s", username, groupName)
return nil
}
func (c *Client) RemoveFromGroup(username, groupName string) error {
conn, err := c.connect()
if err != nil {
return err
}
defer conn.Close()
groupDN := fmt.Sprintf("cn=%s,ou=groups,%s", groupName, c.cfg.LDAPBaseDN)
userDN := fmt.Sprintf("uid=%s,ou=people,%s", username, c.cfg.LDAPBaseDN)
modReq := goldap.NewModifyRequest(groupDN, nil)
modReq.Delete("member", []string{userDN})
if err := conn.Modify(modReq); err != nil {
return fmt.Errorf("ldap remove %s from group %s: %w", username, groupName, err)
}
log.Printf("removed %s from group %s", username, groupName)
return nil
}
func (c *Client) IsInGroup(username, groupName string) (bool, error) {
conn, err := c.connect()
if err != nil {
return false, err
}
defer conn.Close()
groupDN := fmt.Sprintf("cn=%s,ou=groups,%s", groupName, c.cfg.LDAPBaseDN)
userDN := fmt.Sprintf("uid=%s,ou=people,%s", username, c.cfg.LDAPBaseDN)
searchReq := goldap.NewSearchRequest(
groupDN,
goldap.ScopeBaseObject, goldap.NeverDerefAliases, 1, 0, false,
fmt.Sprintf("(member=%s)", goldap.EscapeFilter(userDN)),
[]string{"cn"},
nil,
)
result, err := conn.Search(searchReq)
if err != nil {
return false, nil
}
return len(result.Entries) > 0, nil
}
func (c *Client) FindUserByDescription(stripeCustomerID string) (string, error) {
conn, err := c.connect()
if err != nil {
return "", err
}
defer conn.Close()
searchReq := goldap.NewSearchRequest(
fmt.Sprintf("ou=people,%s", c.cfg.LDAPBaseDN),
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 1, 0, false,
fmt.Sprintf("(description=%s)", goldap.EscapeFilter(stripeCustomerID)),
[]string{"uid"},
nil,
)
result, err := conn.Search(searchReq)
if err != nil {
return "", fmt.Errorf("ldap search by description: %w", err)
}
if len(result.Entries) == 0 {
return "", fmt.Errorf("no user found with stripe customer %s", stripeCustomerID)
}
return result.Entries[0].GetAttributeValue("uid"), nil
}
func (c *Client) userExists(conn *goldap.Conn, username string) (bool, error) {
searchReq := goldap.NewSearchRequest(
fmt.Sprintf("ou=people,%s", c.cfg.LDAPBaseDN),
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 1, 0, false,
fmt.Sprintf("(uid=%s)", goldap.EscapeFilter(username)),
[]string{"uid"},
nil,
)
result, err := conn.Search(searchReq)
if err != nil {
return false, fmt.Errorf("ldap search: %w", err)
}
return len(result.Entries) > 0, nil
}
func generatePassword() string {
b := make([]byte, 18)
if _, err := rand.Read(b); err != nil {
panic("crypto/rand failed: " + err.Error())
}
return base64.URLEncoding.EncodeToString(b)
}