blob: 34c62a1b88b33728405b1e8204b9ae4ac5fcb1e5 [file] [log] [blame]
Serge Bazanski0aa29102023-04-01 23:18:05 +00001package main
2
3import (
4 "crypto/tls"
5 "flag"
6 "fmt"
7 "net/http"
8 "regexp"
9 "strings"
10 "sync"
11
12 "github.com/golang/glog"
13 ldap "gopkg.in/ldap.v3"
14
15 "code.hackerspace.pl/hscloud/go/mirko"
16)
17
18type server struct {
19 mu sync.Mutex
20 ldap *ldap.Conn
21}
22
23var reURL = regexp.MustCompile(`^/([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)$`)
24
25func (s *server) handle(rw http.ResponseWriter, req *http.Request) {
26 if req.Method != "GET" {
27 rw.WriteHeader(http.StatusMethodNotAllowed)
28 fmt.Fprintf(rw, "method not allowed")
29 return
30 }
31
32 reqParts := reURL.FindStringSubmatch(req.URL.Path)
33 if len(reqParts) != 3 {
34 fmt.Fprintf(rw, "usage: GET /capability/user, eg. GET /staff/q3k")
35 return
36 }
37 c := reqParts[1]
38 u := reqParts[2]
39
40 res, err := s.capacify(c, u)
41 l := ""
42 r := ""
43 switch {
44 case err != nil:
45 l = fmt.Sprintf("%v", err)
46 r = "ERROR"
47 rw.WriteHeader(500)
48 case res:
49 l = "yes"
50 r = "YES"
51 rw.WriteHeader(200)
52 default:
53 l = "no"
54 r = "NO"
55 rw.WriteHeader(401)
56 }
57 glog.Infof("%s: GET /%s/%s: %s", req.RemoteAddr, c, u, l)
58 fmt.Fprintf(rw, "%s", r)
59}
60
61func (s *server) capacify(c, u string) (bool, error) {
62 switch c {
63 case "xmpp":
64 return s.checkLdap(u, "cn=xmpp-users,ou=Group,dc=hackerspace,dc=pl")
65 case "wiki_admin":
66 return s.checkLdap(u, "cn=admin,dc=wiki,dc=hackerspace,dc=pl")
67 case "twitter":
68 return s.checkLdap(u, "cn=twitter,ou=Group,dc=hackerspace,dc=pl")
69 case "lulzbot_access":
70 return s.checkLdap(u, "cn=lulzbot-access,ou=Group,dc=hackerspace,dc=pl")
71 case "staff":
72 return s.checkLdap(u, "cn=staff,ou=Group,dc=hackerspace,dc=pl")
73 case "kasownik_access":
74 return s.checkLdap(u, "cn=kasownik-access,ou=Group,dc=hackerspace,dc=pl")
75 case "starving":
76 return s.checkLdap(u, "cn=starving,ou=Group,dc=hackerspace,dc=pl")
77 case "fatty":
78 return s.checkLdap(u, "cn=fatty,ou=Group,dc=hackerspace,dc=pl")
79 case "member":
80 // Where we're going we don't need applicatives.
81 res, err := s.capacify("fatty", u)
82 if err != nil {
83 return false, err
84 }
85 if res {
86 return true, nil
87 }
88 return s.capacify("starving", u)
89 default:
90 return false, nil
91 }
92}
93
94func (s *server) getLdap() (*ldap.Conn, error) {
95 s.mu.Lock()
96 defer s.mu.Unlock()
97 if s.ldap == nil {
98 lconn, err := connectLdap()
99 if err != nil {
100 return nil, err
101 }
102 s.ldap = lconn
103 }
104 return s.ldap, nil
105}
106
107func (s *server) closeLdap() {
108 s.mu.Lock()
109 defer s.mu.Unlock()
110 if s.ldap != nil {
111 s.ldap.Close()
112 s.ldap = nil
113 }
114}
115
116func (s *server) checkLdap(u, dn string) (bool, error) {
117 lconn, err := s.getLdap()
118 if err != nil {
119 return false, err
120 }
121
122 if strings.ContainsAny(u, `\#+<>,;"=`) {
123 return false, nil
124 }
125 filter := fmt.Sprintf("(uniqueMember=uid=%s,ou=People,dc=hackerspace,dc=pl)", u)
126 search := ldap.NewSearchRequest(
127 dn,
128 ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
129 filter, []string{"dn", "cn"}, nil,
130 )
131 sr, err := lconn.Search(search)
132 if err != nil {
133 s.closeLdap()
134 return false, fmt.Errorf("search failed: %w", err)
135 }
136
137 for _, entry := range sr.Entries {
138 if entry.DN == dn {
139 return true, nil
140 }
141 }
142
143 return false, nil
144}
145
146func init() {
147 flag.Set("logtostderr", "true")
148}
149
150var (
151 flagLDAPServer string
152 flagLDAPBindDN string
153 flagLDAPBindPW string
154 flagListen string
155)
156
157func connectLdap() (*ldap.Conn, error) {
158 tlsConfig := &tls.Config{}
159 lconn, err := ldap.DialTLS("tcp", flagLDAPServer, tlsConfig)
160 if err != nil {
161 return nil, fmt.Errorf("ldap.DialTLS: %v", err)
162 }
163
164 if err := lconn.Bind(flagLDAPBindDN, flagLDAPBindPW); err != nil {
165 lconn.Close()
166 return nil, fmt.Errorf("ldap.Bind: %v", err)
167 }
168 return lconn, nil
169}
170
171func main() {
172 flag.StringVar(&flagListen, "api_listen", ":2137", "Address to listen on for API requests")
173 flag.StringVar(&flagLDAPServer, "ldap_server", "ldap.hackerspace.pl:636", "LDAP server address")
174 flag.StringVar(&flagLDAPBindDN, "ldap_bind_dn", "cn=capacifier,ou=Services,dc=hackerspace,dc=pl", "LDAP bind DN")
175 flag.StringVar(&flagLDAPBindPW, "ldap_bind_pw", "", "LDAP bind password")
176 flag.Parse()
177
178 if flagLDAPBindPW == "" {
179 glog.Exitf("-ldap_bind_pw must be set")
180 }
181
182 m := mirko.New()
183 if err := m.Listen(); err != nil {
184 glog.Exitf("Listen(): %v", err)
185 }
186
187 s := &server{}
188 mux := http.NewServeMux()
189 mux.HandleFunc("/", s.handle)
190
191 go func() {
192 glog.Infof("API Listening on %s", flagListen)
193 if err := http.ListenAndServe(flagListen, mux); err != nil {
194 glog.Exitf("API Listen failed: %v", err)
195 }
196 }()
197
198 if err := m.Serve(); err != nil {
199 glog.Exitf("Serve(): %v", err)
200 }
201
202 <-m.Done()
203}