github.com/10ego/gthp@v0.0.0-20241025155251-e1514fa71fbb/internal/auth/ldap.go (about)

     1  package auth
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/go-ldap/ldap/v3"
     9  )
    10  
    11  type Client struct {
    12  	Host       string
    13  	Port       int
    14  	BaseDN     string
    15  	UserFilter string
    16  	GroupDN    string
    17  }
    18  
    19  func NewClient(host string, port int, baseDN, userFilter, groupDN string) *Client {
    20  	return &Client{
    21  		Host:       host,
    22  		Port:       port,
    23  		BaseDN:     baseDN,
    24  		UserFilter: userFilter,
    25  		GroupDN:    groupDN,
    26  	}
    27  }
    28  
    29  func (c *Client) Authenticate(ctx context.Context, username, password string) (bool, error) {
    30  	// Create a new LDAP connection
    31  	l, err := ldap.DialURL(fmt.Sprintf("ldap://%s:%d", c.Host, c.Port))
    32  	if err != nil {
    33  		return false, fmt.Errorf("failed to connect to LDAP server: %v", err)
    34  	}
    35  	defer l.Close()
    36  
    37  	// Construct the user's DN
    38  	userDN := fmt.Sprintf("uid=%s,%s", username, c.UserFilter)
    39  
    40  	// Set a timeout for the connection
    41  	ldapTimeout := 10 * time.Second
    42  	l.SetTimeout(ldapTimeout)
    43  
    44  	// Attempt to bind as the user
    45  	err = l.Bind(userDN, password)
    46  	if err != nil {
    47  		// If binding fails, the credentials are invalid
    48  		return false, nil
    49  	}
    50  
    51  	// Search for the user to check group membership
    52  	searchRequest := ldap.NewSearchRequest(
    53  		c.BaseDN,
    54  		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, int(ldapTimeout.Seconds()), false,
    55  		fmt.Sprintf("(&(objectClass=user)(uid=%s)(memberOf=%s))", ldap.EscapeFilter(username), c.GroupDN),
    56  		[]string{"dn"},
    57  		nil,
    58  	)
    59  
    60  	// Perform the search
    61  	sr, err := l.Search(searchRequest)
    62  	if err != nil {
    63  		return false, fmt.Errorf("LDAP search error: %v", err)
    64  	}
    65  
    66  	// Check if the search was interrupted by context cancellation
    67  	select {
    68  	case <-ctx.Done():
    69  		return false, ctx.Err()
    70  	default:
    71  		// If we found an entry, the user is authenticated and in the correct group
    72  		return len(sr.Entries) > 0, nil
    73  	}
    74  }