user2357858
user2357858

Reputation: 65

golang webapp with LDAP

I try to build web application using Active Directory authentication. I also need to get email address of a user. I have a function that can get email address. Where and how should I use the function to get email in mainHandler()?

main.go

func main() {
    http.HandleFunc("/", auth.BasicAuth(mainHandler))
    http.ListenAndServe(":8080", nil)
}

func mainHandler(w http.ResponseWriter, r *http.Request) {
    tmpl, err := template.ParseFiles("templates/main.html")
    if err == nil {
        tmpl.Execute(w, nil)
    }
}

auth.go

type Handler func(w http.ResponseWriter, r *http.Request)

// BasicAuth - handler wrapper for authentication
func BasicAuth(pass Handler) Handler {

    return func(w http.ResponseWriter, r *http.Request) {
        username, password, ok := r.BasicAuth()
        err := ValidateAD(username, password)
        if err != nil || !ok {
            realm := "Please enter your corporate key and password"
            w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
            // w.WriteHeader(401)
            http.Error(w, "authorization failed", http.StatusUnauthorized)
            return
        }
        pass(w, r)
    }
}

var ErrEmptyUserOrPass = errors.New("Username or password cannot be empty")
var conn *ldap.Conn


// ValidateAD validation based on Active Directory
func ValidateAD(user, passwd string) error {
    if user == "" || passwd == "" {
        return ErrEmptyUserOrPass
    }
    tlsConfig := &tls.Config{InsecureSkipVerify: true}
    var err error
    conn, err = ldap.DialTLS("tcp", "ad.something.com:636", tlsConfig)
    if err != nil {
        return err
    }
    defer conn.Close()
    domainPrefix := "ad\\"
    err = conn.Bind(domainPrefix+user, passwd)
    if err != nil {
        return err
    }
    return nil
}

// GetLDAPEmail returns email address for given username and password
func GetLDAPEmail(user, password string) (string, error) {
    if err := ValidateAD(user, password); err != nil {
        return "", err
    }
    searchBase := "CN=" + user + ",OU=OU1,OU=OU2,OU=OU3,DC=ad,DC=something,DC=com"
    searchFilter := "(&(samAccountName=" + user + "))"
    searchRequest := ldap.NewSearchRequest(
        searchBase, // The base dn to search
        ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
        searchFilter,     // The filter to apply
        []string{"mail"}, // A list attributes to retrieve
        nil,
    )
    sr, err := conn.Search(searchRequest)
    if err != nil {
        return "", err
    }
    email := sr.Entries[0].GetAttributeValue("mail")
    return strings.ToLower(email), nil
}

Upvotes: 1

Views: 2335

Answers (1)

rbh
rbh

Reputation: 46

The way your functions and handlers are wired, I do not see a whole lot of "clean" options to pass state back from BasicAuth() to the mainHandler() per request.

If you are open to the idea of changing the way you setup your handlers, here is a skeleton structure which you can expand to fit your needs:

package main

import (
    "fmt"
    "log"
    "net/http"
)

type User struct {
    Name     string
    Password string
}

func main() {
    mux := http.NewServeMux()
    mux.Handle("/", &User{})
    s := &http.Server{Addr: "localhost:8080", Handler: mux}
    log.Fatal(s.ListenAndServe())
}

func (u *User) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    //Pull out the username and password
    u.BasicAuth()
    fmt.Println(u.Name)
    fmt.Println(u.Password)
    //The rest of your main handler
}

func (u *User) BasicAuth() {
    //This is to demonstrate the ability to pass state
    //Edit this to fit your needs
    u.Name = "user"
    u.Password = "pass"
}

The User struct implements the ServeHTTP function, effectively implementing the http.Handler interface which opens up the option of adding it to a multiplexer which in turn helps maintain the username and password per request. Although the methods receive a pointer type you could change it to better suit your needs.

Upvotes: 1

Related Questions