mirror of
https://github.com/h44z/wg-portal
synced 2025-02-26 05:49:14 +00:00
179 lines
4.7 KiB
Go
179 lines
4.7 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/go-ldap/ldap/v3"
|
|
"github.com/h44z/wg-portal/internal"
|
|
"github.com/h44z/wg-portal/internal/config"
|
|
"github.com/h44z/wg-portal/internal/domain"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type LdapAuthenticator struct {
|
|
cfg *config.LdapProvider
|
|
}
|
|
|
|
func newLdapAuthenticator(_ context.Context, cfg *config.LdapProvider) (*LdapAuthenticator, error) {
|
|
var provider = &LdapAuthenticator{}
|
|
|
|
provider.cfg = cfg
|
|
|
|
dn, err := ldap.ParseDN(cfg.AdminGroupDN)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse admin group DN: %w", err)
|
|
}
|
|
provider.cfg.FieldMap = provider.getLdapFieldMapping(cfg.FieldMap)
|
|
provider.cfg.ParsedAdminGroupDN = dn
|
|
|
|
return provider, nil
|
|
}
|
|
|
|
func (l LdapAuthenticator) GetName() string {
|
|
return l.cfg.ProviderName
|
|
}
|
|
|
|
func (l LdapAuthenticator) RegistrationEnabled() bool {
|
|
return l.cfg.RegistrationEnabled
|
|
}
|
|
|
|
func (l LdapAuthenticator) PlaintextAuthentication(userId domain.UserIdentifier, plainPassword string) error {
|
|
conn, err := internal.LdapConnect(l.cfg)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to setup connection: %w", err)
|
|
}
|
|
defer internal.LdapDisconnect(conn)
|
|
|
|
attrs := []string{"dn"}
|
|
|
|
loginFilter := strings.Replace(l.cfg.LoginFilter, "{{login_identifier}}", string(userId), -1)
|
|
searchRequest := ldap.NewSearchRequest(
|
|
l.cfg.BaseDN,
|
|
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 20, false, // 20 second time limit
|
|
loginFilter, attrs, nil,
|
|
)
|
|
|
|
sr, err := conn.Search(searchRequest)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to search in ldap: %w", err)
|
|
}
|
|
|
|
if len(sr.Entries) == 0 {
|
|
return domain.ErrNotFound
|
|
}
|
|
|
|
if len(sr.Entries) > 1 {
|
|
return domain.ErrNotUnique
|
|
}
|
|
|
|
// Bind as the user to verify their password
|
|
userDN := sr.Entries[0].DN
|
|
err = conn.Bind(userDN, plainPassword)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid credentials: %w", err)
|
|
}
|
|
_ = conn.Unbind()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l LdapAuthenticator) GetUserInfo(_ context.Context, userId domain.UserIdentifier) (
|
|
map[string]interface{},
|
|
error,
|
|
) {
|
|
conn, err := internal.LdapConnect(l.cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to setup connection: %w", err)
|
|
}
|
|
defer internal.LdapDisconnect(conn)
|
|
|
|
attrs := internal.LdapSearchAttributes(&l.cfg.FieldMap)
|
|
|
|
loginFilter := strings.Replace(l.cfg.LoginFilter, "{{login_identifier}}", string(userId), -1)
|
|
searchRequest := ldap.NewSearchRequest(
|
|
l.cfg.BaseDN,
|
|
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 20, false, // 20 second time limit
|
|
loginFilter, attrs, nil,
|
|
)
|
|
|
|
sr, err := conn.Search(searchRequest)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to search in ldap: %w", err)
|
|
}
|
|
|
|
if len(sr.Entries) == 0 {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
|
|
if len(sr.Entries) > 1 {
|
|
return nil, domain.ErrNotUnique
|
|
}
|
|
|
|
users := internal.LdapConvertEntries(sr, &l.cfg.FieldMap)
|
|
|
|
if l.cfg.LogUserInfo {
|
|
contents, _ := json.Marshal(users[0])
|
|
logrus.Tracef("User info from LDAP source %s for %s: %v", l.GetName(), userId, string(contents))
|
|
}
|
|
|
|
return users[0], nil
|
|
}
|
|
|
|
func (l LdapAuthenticator) ParseUserInfo(raw map[string]interface{}) (*domain.AuthenticatorUserInfo, error) {
|
|
isAdmin, err := internal.LdapIsMemberOf(raw[l.cfg.FieldMap.GroupMembership].([][]byte), l.cfg.ParsedAdminGroupDN)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to check admin group: %w", err)
|
|
}
|
|
userInfo := &domain.AuthenticatorUserInfo{
|
|
Identifier: domain.UserIdentifier(internal.MapDefaultString(raw, l.cfg.FieldMap.UserIdentifier, "")),
|
|
Email: internal.MapDefaultString(raw, l.cfg.FieldMap.Email, ""),
|
|
Firstname: internal.MapDefaultString(raw, l.cfg.FieldMap.Firstname, ""),
|
|
Lastname: internal.MapDefaultString(raw, l.cfg.FieldMap.Lastname, ""),
|
|
Phone: internal.MapDefaultString(raw, l.cfg.FieldMap.Phone, ""),
|
|
Department: internal.MapDefaultString(raw, l.cfg.FieldMap.Department, ""),
|
|
IsAdmin: isAdmin,
|
|
}
|
|
|
|
return userInfo, nil
|
|
}
|
|
|
|
func (l LdapAuthenticator) getLdapFieldMapping(f config.LdapFields) config.LdapFields {
|
|
defaultMap := config.LdapFields{
|
|
BaseFields: config.BaseFields{
|
|
UserIdentifier: "mail",
|
|
Email: "mail",
|
|
Firstname: "givenName",
|
|
Lastname: "sn",
|
|
Phone: "telephoneNumber",
|
|
Department: "department",
|
|
},
|
|
GroupMembership: "memberOf",
|
|
}
|
|
if f.UserIdentifier != "" {
|
|
defaultMap.UserIdentifier = f.UserIdentifier
|
|
}
|
|
if f.Email != "" {
|
|
defaultMap.Email = f.Email
|
|
}
|
|
if f.Firstname != "" {
|
|
defaultMap.Firstname = f.Firstname
|
|
}
|
|
if f.Lastname != "" {
|
|
defaultMap.Lastname = f.Lastname
|
|
}
|
|
if f.Phone != "" {
|
|
defaultMap.Phone = f.Phone
|
|
}
|
|
if f.Department != "" {
|
|
defaultMap.Department = f.Department
|
|
}
|
|
if f.GroupMembership != "" {
|
|
defaultMap.GroupMembership = f.GroupMembership
|
|
}
|
|
|
|
return defaultMap
|
|
}
|