forked from hswaw/hscloud
radex
d318d7e6d4
Change-Id: I3afbe1857c321ac6db1255d8a2fe1d9aa3da5c12 Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1689 Reviewed-by: q3k <q3k@hackerspace.pl>
196 lines
4.3 KiB
Go
196 lines
4.3 KiB
Go
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()
|
|
}
|