blob: e3ec2ee993245be99438ba0394a2404f3d7b20fb [file] [log] [blame]
package main
import (
"context"
"crypto/tls"
"flag"
"fmt"
"net/http"
"regexp"
"strings"
"sync"
ldap "github.com/go-ldap/ldap/v3"
"github.com/golang/glog"
)
type server struct {
mu sync.Mutex
ldap *ldap.Conn
}
var reURL = regexp.MustCompile(`^/([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)$`)
func (s *server) handle(rw http.ResponseWriter, req *http.Request) {
if req.Method != "GET" {
rw.WriteHeader(http.StatusMethodNotAllowed)
fmt.Fprintf(rw, "method not allowed")
return
}
reqParts := reURL.FindStringSubmatch(req.URL.Path)
if len(reqParts) != 3 {
fmt.Fprintf(rw, "usage: GET /capability/user, eg. GET /staff/q3k")
return
}
c := reqParts[1]
u := reqParts[2]
res, err := s.capacify(c, u)
l := ""
r := ""
switch {
case err != nil:
l = fmt.Sprintf("%v", err)
r = "ERROR"
rw.WriteHeader(500)
case res:
l = "yes"
r = "YES"
rw.WriteHeader(200)
default:
l = "no"
r = "NO"
rw.WriteHeader(401)
}
glog.Infof("%s: GET /%s/%s: %s", req.RemoteAddr, c, u, l)
fmt.Fprintf(rw, "%s", r)
}
func (s *server) capacify(c, u string) (bool, error) {
switch c {
case "xmpp":
return s.checkLdap(u, "cn=xmpp-users,ou=Group,dc=hackerspace,dc=pl")
case "wiki_admin":
return s.checkLdap(u, "cn=admin,dc=wiki,dc=hackerspace,dc=pl")
case "twitter":
return s.checkLdap(u, "cn=twitter,ou=Group,dc=hackerspace,dc=pl")
case "lulzbot_access":
return s.checkLdap(u, "cn=lulzbot-access,ou=Group,dc=hackerspace,dc=pl")
case "staff":
return s.checkLdap(u, "cn=staff,ou=Group,dc=hackerspace,dc=pl")
case "kasownik_access":
return s.checkLdap(u, "cn=kasownik-access,ou=Group,dc=hackerspace,dc=pl")
case "starving":
return s.checkLdap(u, "cn=starving,ou=Group,dc=hackerspace,dc=pl")
case "fatty":
return s.checkLdap(u, "cn=fatty,ou=Group,dc=hackerspace,dc=pl")
case "member":
// Where we're going we don't need applicatives.
res, err := s.capacify("fatty", u)
if err != nil {
return false, err
}
if res {
return true, nil
}
return s.capacify("starving", u)
default:
return false, nil
}
}
func (s *server) getLdap() (*ldap.Conn, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.ldap == nil {
lconn, err := connectLdap()
if err != nil {
return nil, err
}
s.ldap = lconn
}
return s.ldap, nil
}
func (s *server) closeLdap() {
s.mu.Lock()
defer s.mu.Unlock()
if s.ldap != nil {
s.ldap.Close()
s.ldap = nil
}
}
func (s *server) checkLdap(u, dn string) (bool, error) {
lconn, err := s.getLdap()
if err != nil {
return false, err
}
if strings.ContainsAny(u, `\#+<>,;"=`) {
return false, nil
}
filter := fmt.Sprintf("(uniqueMember=uid=%s,ou=People,dc=hackerspace,dc=pl)", u)
search := ldap.NewSearchRequest(
dn,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter, []string{"dn", "cn"}, nil,
)
sr, err := lconn.Search(search)
if err != nil {
s.closeLdap()
return false, fmt.Errorf("search failed: %w", err)
}
for _, entry := range sr.Entries {
if entry.DN == dn {
return true, nil
}
}
return false, nil
}
func init() {
flag.Set("logtostderr", "true")
}
var (
flagLDAPServer string
flagLDAPBindDN string
flagLDAPBindPW string
flagListen string
)
func connectLdap() (*ldap.Conn, error) {
tlsConfig := &tls.Config{}
lconn, err := ldap.DialTLS("tcp", flagLDAPServer, tlsConfig)
if err != nil {
return nil, fmt.Errorf("ldap.DialTLS: %v", err)
}
if err := lconn.Bind(flagLDAPBindDN, flagLDAPBindPW); err != nil {
lconn.Close()
return nil, fmt.Errorf("ldap.Bind: %v", err)
}
return lconn, nil
}
func main() {
flag.StringVar(&flagListen, "api_listen", ":2137", "Address to listen on for API requests")
flag.StringVar(&flagLDAPServer, "ldap_server", "ldap.hackerspace.pl:636", "LDAP server address")
flag.StringVar(&flagLDAPBindDN, "ldap_bind_dn", "cn=capacifier,ou=Services,dc=hackerspace,dc=pl", "LDAP bind DN")
flag.StringVar(&flagLDAPBindPW, "ldap_bind_pw", "", "LDAP bind password")
flag.Parse()
if flagLDAPBindPW == "" {
glog.Exitf("-ldap_bind_pw must be set")
}
// TODO(q3k): use sigint-interruptible context
ctx := context.Background()
s := &server{}
mux := http.NewServeMux()
mux.HandleFunc("/", s.handle)
go func() {
glog.Infof("API Listening on %s", flagListen)
if err := http.ListenAndServe(flagListen, mux); err != nil {
glog.Exitf("API Listen failed: %v", err)
}
}()
<-ctx.Done()
}