Code angepasst

This commit is contained in:
Steffen Probst 2025-03-31 17:52:55 +02:00
parent b0c9541c14
commit 0b02fb196b
6 changed files with 337 additions and 312 deletions

4
.gitignore vendored
View File

@ -1 +1,5 @@
.env
*.swp
go.mod
go.sum
ldap_phonebook_html

72
config.go Normal file
View File

@ -0,0 +1,72 @@
package main
import (
"html/template"
"log"
"os"
"path/filepath"
"gopkg.in/yaml.v2"
)
func init() {
loadEnvConfig()
loadYAMLConfig()
loadTemplate()
}
func loadEnvConfig() {
requiredEnvVars := []string{
"LDAP_SERVER", "LDAP_PORT", "LDAP_BIND_DN", "LDAP_BIND_PASSWORD",
"LDAP_BASE_DN", "LDAP_FILTER", "SERVER_PORT",
}
for _, envVar := range requiredEnvVars {
if value := os.Getenv(envVar); value == "" {
log.Fatalf("Required environment variable %s is not set", envVar)
}
}
ldapConfig = LDAPConfig{
Server: os.Getenv("LDAP_SERVER"),
Port: os.Getenv("LDAP_PORT"),
BindDN: os.Getenv("LDAP_BIND_DN"),
BindPassword: os.Getenv("LDAP_BIND_PASSWORD"),
BaseDN: os.Getenv("LDAP_BASE_DN"),
Filter: os.Getenv("LDAP_FILTER"),
}
serverPort = os.Getenv("SERVER_PORT")
}
func loadYAMLConfig() {
configPath := filepath.Join("static", "config.yaml")
configFile, err := os.ReadFile(configPath)
if err != nil {
log.Fatalf("Failed to read config file: %v", err)
}
err = yaml.Unmarshal(configFile, &config)
if err != nil {
log.Fatalf("Failed to parse config file: %v", err)
}
validateConfig()
}
func validateConfig() {
if config.PhoneRules.Country.Prefix == "" ||
len(config.PhoneRules.Country.AreaCodes) == 0 ||
config.PhoneRules.Country.InternalPrefix == "" ||
config.PhoneRules.InvalidNumber == "" {
log.Fatalf("Missing required configuration in config.yaml")
}
}
func loadTemplate() {
var err error
tmpl, err = template.ParseFiles(filepath.Join("static", "index.html"))
if err != nil {
log.Fatalf("Failed to parse template: %v", err)
}
}

125
ldap.go Normal file
View File

@ -0,0 +1,125 @@
package main
import (
"crypto/tls"
"fmt"
"strings"
"github.com/go-ldap/ldap/v3"
)
func fetchDataFromLDAP() ([]Person, error) {
l, err := ldap.DialTLS("tcp", ldapConfig.Server+":"+ldapConfig.Port, &tls.Config{InsecureSkipVerify: true})
if err != nil {
return nil, fmt.Errorf("failed to connect to LDAP server: %v", err)
}
defer l.Close()
err = l.Bind(ldapConfig.BindDN, ldapConfig.BindPassword)
if err != nil {
return nil, fmt.Errorf("failed to bind to LDAP server: %v", err)
}
// Erweitere den Filter, um sowohl Benutzer als auch Kontakte einzuschließen
combinedFilter := fmt.Sprintf("(|(objectClass=user)(objectClass=contact)%s)", ldapConfig.Filter)
searchRequest := ldap.NewSearchRequest(
ldapConfig.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
combinedFilter,
[]string{"objectClass", "givenName", "sn", "mail", "telephoneNumber", "mobile", "otherTelephone", "physicalDeliveryOfficeName"},
nil,
)
sr, err := l.Search(searchRequest)
if err != nil {
return nil, fmt.Errorf("failed to search LDAP server: %v", err)
}
var people []Person
for _, entry := range sr.Entries {
if !isValidEntry(entry) {
continue
}
isContact := isContactObject(entry)
person := Person{
FirstName: entry.GetAttributeValue("givenName"),
LastName: entry.GetAttributeValue("sn"),
Email: entry.GetAttributeValue("mail"),
Department: entry.GetAttributeValue("physicalDeliveryOfficeName"),
IsContact: isContact,
}
officePhone := entry.GetAttributeValue("telephoneNumber")
mobilePhone := entry.GetAttributeValue("mobile")
// Normalisiere die Telefonnummern für den Vergleich
normalizedOffice := normalizePhoneNumber(officePhone)
normalizedMobile := normalizePhoneNumber(mobilePhone)
if normalizedOffice == normalizedMobile && mobilePhone != "" {
formattedMobile := formatPhoneNumber(mobilePhone)
person.Phones = append(person.Phones, Phone{PhoneNumber: formattedMobile, Type: "Mobil"})
person.RawPhoneNumber = formattedMobile
person.InternalPhone = "" // Keine interne Rufnummer für Mobiltelefone
} else if officePhone != "" {
formattedPhone := formatPhoneNumber(officePhone)
person.Phones = append(person.Phones, Phone{PhoneNumber: formattedPhone, Type: "Office"})
person.InternalPhone = extractInternalNumber(officePhone)
person.RawPhoneNumber = formattedPhone
if mobilePhone != "" && normalizedOffice != normalizedMobile {
formattedMobile := formatPhoneNumber(mobilePhone)
person.Phones = append(person.Phones, Phone{PhoneNumber: formattedMobile, Type: "Mobil"})
}
} else if mobilePhone != "" {
formattedMobile := formatPhoneNumber(mobilePhone)
person.Phones = append(person.Phones, Phone{PhoneNumber: formattedMobile, Type: "Mobil"})
person.RawPhoneNumber = formattedMobile
person.InternalPhone = "" // Keine interne Rufnummer für Mobiltelefone
}
for _, otherPhone := range entry.GetAttributeValues("otherTelephone") {
person.Phones = append(person.Phones, Phone{PhoneNumber: formatPhoneNumber(otherPhone), Type: "Other"})
}
if (len(person.Phones) > 0 && person.Email != "") || isContact {
people = append(people, person)
}
}
return people, nil
}
func isValidEntry(entry *ldap.Entry) bool {
firstName := entry.GetAttributeValue("givenName")
lastName := entry.GetAttributeValue("sn")
telephoneNumber := entry.GetAttributeValue("telephoneNumber")
mobile := entry.GetAttributeValue("mobile")
// Für Kontakte erlauben wir auch Einträge ohne Telefonnummer
isContact := isContactObject(entry)
return (firstName != "" && lastName != "") &&
(isContact || telephoneNumber != config.PhoneRules.InvalidNumber || mobile != "")
}
func isContactObject(entry *ldap.Entry) bool {
objectClasses := entry.GetAttributeValues("objectClass")
for _, class := range objectClasses {
if strings.ToLower(class) == "contact" {
return true
}
}
return false
}
func isValidContact(entry *ldap.Entry) bool {
firstName := entry.GetAttributeValue("givenName")
lastName := entry.GetAttributeValue("sn")
telephoneNumber := entry.GetAttributeValue("telephoneNumber")
return firstName != "" && lastName != "" && telephoneNumber != config.PhoneRules.InvalidNumber
}

313
main.go
View File

@ -20,128 +20,11 @@ Entwickelt mit Unterstützung von Claude.ai, einem KI-Assistenten von Anthropic,
*/
import (
"crypto/tls"
"fmt"
"html/template"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"unicode"
"github.com/go-ldap/ldap/v3"
"gopkg.in/yaml.v2"
)
type Person struct {
FirstName string
LastName string
Email string
Department string
Phones []Phone
RawPhoneNumber string
InternalPhone string
IsContact bool // Neues Feld zur Unterscheidung zwischen Benutzern und Kontakten
}
type Phone struct {
PhoneNumber string
Type string
}
type Config struct {
PhoneRules struct {
Country struct {
Prefix string `yaml:"prefix"`
AreaCodes []string `yaml:"area_codes"`
InternalPrefix string `yaml:"internal_prefix"`
} `yaml:"country"`
InvalidNumber string `yaml:"invalid_number"`
} `yaml:"phone_rules"`
}
type LDAPConfig struct {
Server string
Port string
BindDN string
BindPassword string
BaseDN string
Filter string
}
var (
cachedData []Person
lastUpdate time.Time
tmpl *template.Template
ldapConfig LDAPConfig
serverPort string
config Config
)
func init() {
loadEnvConfig()
loadYAMLConfig()
loadTemplate()
}
func loadEnvConfig() {
requiredEnvVars := []string{
"LDAP_SERVER", "LDAP_PORT", "LDAP_BIND_DN", "LDAP_BIND_PASSWORD",
"LDAP_BASE_DN", "LDAP_FILTER", "SERVER_PORT",
}
for _, envVar := range requiredEnvVars {
if value := os.Getenv(envVar); value == "" {
log.Fatalf("Required environment variable %s is not set", envVar)
}
}
ldapConfig = LDAPConfig{
Server: os.Getenv("LDAP_SERVER"),
Port: os.Getenv("LDAP_PORT"),
BindDN: os.Getenv("LDAP_BIND_DN"),
BindPassword: os.Getenv("LDAP_BIND_PASSWORD"),
BaseDN: os.Getenv("LDAP_BASE_DN"),
Filter: os.Getenv("LDAP_FILTER"),
}
serverPort = os.Getenv("SERVER_PORT")
}
func loadYAMLConfig() {
configPath := filepath.Join("static", "config.yaml")
configFile, err := os.ReadFile(configPath)
if err != nil {
log.Fatalf("Failed to read config file: %v", err)
}
err = yaml.Unmarshal(configFile, &config)
if err != nil {
log.Fatalf("Failed to parse config file: %v", err)
}
validateConfig()
}
func validateConfig() {
if config.PhoneRules.Country.Prefix == "" ||
len(config.PhoneRules.Country.AreaCodes) == 0 ||
config.PhoneRules.Country.InternalPrefix == "" ||
config.PhoneRules.InvalidNumber == "" {
log.Fatalf("Missing required configuration in config.yaml")
}
}
func loadTemplate() {
var err error
tmpl, err = template.ParseFiles(filepath.Join("static", "index.html"))
if err != nil {
log.Fatalf("Failed to parse template: %v", err)
}
}
func main() {
go cacheRefresher()
@ -166,39 +49,6 @@ func cacheRefresher() {
}
}
func dataEqual(a, b []Person) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if !personEqual(a[i], b[i]) {
return false
}
}
return true
}
func personEqual(a, b Person) bool {
return a.FirstName == b.FirstName &&
a.LastName == b.LastName &&
a.Email == b.Email &&
a.InternalPhone == b.InternalPhone &&
a.Department == b.Department &&
phonesEqual(a.Phones, b.Phones)
}
func phonesEqual(a, b []Phone) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func handler(w http.ResponseWriter, r *http.Request) {
log.Printf("Received request for: %s", r.URL.Path)
@ -216,165 +66,4 @@ func handler(w http.ResponseWriter, r *http.Request) {
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func fetchDataFromLDAP() ([]Person, error) {
l, err := ldap.DialTLS("tcp", ldapConfig.Server+":"+ldapConfig.Port, &tls.Config{InsecureSkipVerify: true})
if err != nil {
return nil, fmt.Errorf("failed to connect to LDAP server: %v", err)
}
defer l.Close()
err = l.Bind(ldapConfig.BindDN, ldapConfig.BindPassword)
if err != nil {
return nil, fmt.Errorf("failed to bind to LDAP server: %v", err)
}
// Erweitere den Filter, um sowohl Benutzer als auch Kontakte einzuschließen
combinedFilter := fmt.Sprintf("(|(objectClass=user)(objectClass=contact)%s)", ldapConfig.Filter)
searchRequest := ldap.NewSearchRequest(
ldapConfig.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
combinedFilter,
[]string{"objectClass", "givenName", "sn", "mail", "telephoneNumber", "mobile", "otherTelephone", "physicalDeliveryOfficeName"},
nil,
)
sr, err := l.Search(searchRequest)
if err != nil {
return nil, fmt.Errorf("failed to search LDAP server: %v", err)
}
var people []Person
for _, entry := range sr.Entries {
if !isValidEntry(entry) {
continue
}
isContact := isContactObject(entry)
person := Person{
FirstName: entry.GetAttributeValue("givenName"),
LastName: entry.GetAttributeValue("sn"),
Email: entry.GetAttributeValue("mail"),
Department: entry.GetAttributeValue("physicalDeliveryOfficeName"),
IsContact: isContact,
}
officePhone := entry.GetAttributeValue("telephoneNumber")
mobilePhone := entry.GetAttributeValue("mobile")
// Normalisiere die Telefonnummern für den Vergleich
normalizedOffice := normalizePhoneNumber(officePhone)
normalizedMobile := normalizePhoneNumber(mobilePhone)
if normalizedOffice == normalizedMobile && mobilePhone != "" {
formattedMobile := formatPhoneNumber(mobilePhone)
person.Phones = append(person.Phones, Phone{PhoneNumber: formattedMobile, Type: "Mobil"})
person.RawPhoneNumber = formattedMobile
person.InternalPhone = "" // Keine interne Rufnummer für Mobiltelefone
} else if officePhone != "" {
formattedPhone := formatPhoneNumber(officePhone)
person.Phones = append(person.Phones, Phone{PhoneNumber: formattedPhone, Type: "Office"})
person.InternalPhone = extractInternalNumber(officePhone)
person.RawPhoneNumber = formattedPhone
if mobilePhone != "" && normalizedOffice != normalizedMobile {
formattedMobile := formatPhoneNumber(mobilePhone)
person.Phones = append(person.Phones, Phone{PhoneNumber: formattedMobile, Type: "Mobil"})
}
} else if mobilePhone != "" {
formattedMobile := formatPhoneNumber(mobilePhone)
person.Phones = append(person.Phones, Phone{PhoneNumber: formattedMobile, Type: "Mobil"})
person.RawPhoneNumber = formattedMobile
person.InternalPhone = "" // Keine interne Rufnummer für Mobiltelefone
}
for _, otherPhone := range entry.GetAttributeValues("otherTelephone") {
person.Phones = append(person.Phones, Phone{PhoneNumber: formatPhoneNumber(otherPhone), Type: "Other"})
}
if (len(person.Phones) > 0 && person.Email != "") || isContact {
people = append(people, person)
}
}
return people, nil
}
func isValidEntry(entry *ldap.Entry) bool {
firstName := entry.GetAttributeValue("givenName")
lastName := entry.GetAttributeValue("sn")
telephoneNumber := entry.GetAttributeValue("telephoneNumber")
mobile := entry.GetAttributeValue("mobile")
// Für Kontakte erlauben wir auch Einträge ohne Telefonnummer
isContact := isContactObject(entry)
return (firstName != "" && lastName != "") &&
(isContact || telephoneNumber != config.PhoneRules.InvalidNumber || mobile != "")
}
func isContactObject(entry *ldap.Entry) bool {
objectClasses := entry.GetAttributeValues("objectClass")
for _, class := range objectClasses {
if strings.ToLower(class) == "contact" {
return true
}
}
return false
}
func isValidContact(entry *ldap.Entry) bool {
firstName := entry.GetAttributeValue("givenName")
lastName := entry.GetAttributeValue("sn")
telephoneNumber := entry.GetAttributeValue("telephoneNumber")
return firstName != "" && lastName != "" && telephoneNumber != config.PhoneRules.InvalidNumber
}
func formatPhoneNumber(number string) string {
// Entferne alle Nicht-Ziffern
digits := strings.Map(func(r rune) rune {
if unicode.IsDigit(r) {
return r
}
return -1
}, number)
// Entferne führende Nullen
digits = strings.TrimLeft(digits, "0")
// Entferne den Länderprefix, falls vorhanden
countryPrefix := config.PhoneRules.Country.Prefix
if strings.HasPrefix(digits, countryPrefix) {
digits = digits[len(countryPrefix):]
}
// Füge eine führende "0" hinzu, wenn nicht vorhanden
if !strings.HasPrefix(digits, "0") {
digits = "0" + digits
}
return digits
}
func extractInternalNumber(phoneNumber string) string {
digits := formatPhoneNumber(phoneNumber)
for _, areaCode := range config.PhoneRules.Country.AreaCodes {
if strings.HasPrefix(digits[1:], areaCode) { // Ignoriere die führende 0
remaining := digits[1+len(areaCode):]
if strings.HasPrefix(remaining, config.PhoneRules.Country.InternalPrefix) {
return remaining[len(config.PhoneRules.Country.InternalPrefix):]
}
}
}
return ""
}
func normalizePhoneNumber(number string) string {
return formatPhoneNumber(number)
}
}

51
models.go Normal file
View File

@ -0,0 +1,51 @@
package main
import (
"html/template"
"time"
)
type Person struct {
FirstName string
LastName string
Email string
Department string
Phones []Phone
RawPhoneNumber string
InternalPhone string
IsContact bool // Neues Feld zur Unterscheidung zwischen Benutzern und Kontakten
}
type Phone struct {
PhoneNumber string
Type string
}
type Config struct {
PhoneRules struct {
Country struct {
Prefix string `yaml:"prefix"`
AreaCodes []string `yaml:"area_codes"`
InternalPrefix string `yaml:"internal_prefix"`
} `yaml:"country"`
InvalidNumber string `yaml:"invalid_number"`
} `yaml:"phone_rules"`
}
type LDAPConfig struct {
Server string
Port string
BindDN string
BindPassword string
BaseDN string
Filter string
}
var (
cachedData []Person
lastUpdate time.Time
tmpl *template.Template
ldapConfig LDAPConfig
serverPort string
config Config
)

84
util.go Normal file
View File

@ -0,0 +1,84 @@
package main
import (
"strings"
"unicode"
)
func dataEqual(a, b []Person) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if !personEqual(a[i], b[i]) {
return false
}
}
return true
}
func personEqual(a, b Person) bool {
return a.FirstName == b.FirstName &&
a.LastName == b.LastName &&
a.Email == b.Email &&
a.InternalPhone == b.InternalPhone &&
a.Department == b.Department &&
phonesEqual(a.Phones, b.Phones)
}
func phonesEqual(a, b []Phone) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func formatPhoneNumber(number string) string {
// Entferne alle Nicht-Ziffern
digits := strings.Map(func(r rune) rune {
if unicode.IsDigit(r) {
return r
}
return -1
}, number)
// Entferne führende Nullen
digits = strings.TrimLeft(digits, "0")
// Entferne den Länderprefix, falls vorhanden
countryPrefix := config.PhoneRules.Country.Prefix
if strings.HasPrefix(digits, countryPrefix) {
digits = digits[len(countryPrefix):]
}
// Füge eine führende "0" hinzu, wenn nicht vorhanden
if !strings.HasPrefix(digits, "0") {
digits = "0" + digits
}
return digits
}
func extractInternalNumber(phoneNumber string) string {
digits := formatPhoneNumber(phoneNumber)
for _, areaCode := range config.PhoneRules.Country.AreaCodes {
if strings.HasPrefix(digits[1:], areaCode) { // Ignoriere die führende 0
remaining := digits[1+len(areaCode):]
if strings.HasPrefix(remaining, config.PhoneRules.Country.InternalPrefix) {
return remaining[len(config.PhoneRules.Country.InternalPrefix):]
}
}
}
return ""
}
func normalizePhoneNumber(number string) string {
return formatPhoneNumber(number)
}