Commit 314a041
proxy/proxy.go
@@ -0,0 +1,154 @@
+package proxy
+
+import (
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "os"
+
+ "github.com/gorilla/mux"
+)
+
+type Proxy struct {
+ Domains []Domain
+}
+
+type Domain struct {
+ Address string `json:"domain"`
+ Users []User `json:"users"`
+}
+
+type User struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+}
+
+type Response struct {
+ Success bool `json:"access_granted"`
+ Reason string `json:"reason,omitempty"`
+}
+
+// generate and return base64 encoded sha256 digest of provided password
+func b64sha256(password string) string {
+ s256 := sha256.New()
+ s256.Write([]byte(password))
+ return base64.StdEncoding.EncodeToString(s256.Sum(nil))
+}
+
+// parse the json users file and return the proxy data type
+func NewProxy(filePath string) (*Proxy, error) {
+ usersJson, err := os.Open(filePath)
+ if err != nil {
+ return nil, err
+ }
+
+ p := &Proxy{}
+ err = json.NewDecoder(usersJson).Decode(&p.Domains)
+ if err != nil {
+ return nil, err
+ }
+
+ for i, d := range p.Domains {
+ for j, u := range d.Users {
+ p.Domains[i].Users[j].Password = "{SHA256}" + b64sha256(u.Password)
+ }
+ }
+ return p, nil
+
+}
+
+// search within the proxy for a domain
+func (p *Proxy) get(reqDomain string) (*Domain, error) {
+ for i, d := range p.Domains {
+ if d.Address == reqDomain {
+ return &p.Domains[i], nil
+ }
+ }
+ return nil, fmt.Errorf("No such domain")
+}
+
+// search within a domain for a user
+func (d *Domain) get(reqUser string) (*User, error) {
+ for i, u := range d.Users {
+ if u.Username == reqUser {
+ return &d.Users[i], nil
+ }
+ }
+ return nil, fmt.Errorf("No such user")
+}
+
+// Per policy: in case of authentication failure or validation errors.
+// The 'reason' is always is always same: "denied by policy".
+// Similary success is simply access_granted: true.
+// Having this separated as a function will be useful if different
+// responses are needed in the future
+// Assuption: 200 OK is golang default
+func writeSuccess(w http.ResponseWriter, success bool) {
+ var r *Response
+ out := json.NewEncoder(w)
+ if success {
+ r = &Response{
+ Success: success,
+ }
+ } else {
+ r = &Response{
+ Success: success,
+ Reason: "denied by policy",
+ }
+ }
+ out.Encode(r)
+}
+
+func (p *Proxy) Authenticate() http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ // domain lookup
+ vars := mux.Vars(r)
+ urlDomain, ok := vars["domain"]
+ if !ok {
+ w.WriteHeader(500) // Server error
+ return
+ }
+ d, err := p.get(urlDomain)
+ if err != nil {
+ w.WriteHeader(404) // No such domain
+ return
+ }
+
+ // parse parameters
+ err = r.ParseForm()
+ if err != nil {
+ w.WriteHeader(500) // Server error
+ return
+ }
+ username := r.Form.Get("username")
+ if username == "" {
+ writeSuccess(w, false) // no username provided
+ return
+ }
+ password := r.Form.Get("password")
+ if password == "" {
+ writeSuccess(w, false) // no password
+ return
+ }
+
+ // user lookup
+ u, err := d.get(username)
+ if err != nil {
+ writeSuccess(w, false) // no such user
+ return
+ }
+
+ // password validation
+ if u.Password != password {
+ writeSuccess(w, false) // password mismatch
+ return
+ } else {
+ writeSuccess(w, true) // successful authenticaton
+ return
+ }
+
+ })
+}
main.go
@@ -1,107 +1,28 @@
package main
import (
- "crypto/sha256"
- "encoding/base64"
- "encoding/json"
- "fmt"
"log"
"net/http"
- "os"
+ "./proxy"
"github.com/gorilla/mux"
)
-type proxy struct {
- Domains []domain
-}
-
-type domain struct {
- Address string `json:"domain"`
- Users []user `json:"users"`
-}
-
-type user struct {
- Username string `json:"username"`
- Password string `json:"password"`
-}
+func main() {
-func checkErr(err error, m string) {
+ proxy, err := proxy.NewProxy("./users.json")
if err != nil {
- log.Fatalln(m, ": ", err)
+ log.Fatalln("Proxy init failed: %s", err)
}
-}
-
-func b64sha256(password string) string {
- s256 := sha256.New()
- s256.Write([]byte(password))
- return base64.StdEncoding.EncodeToString(s256.Sum(nil))
-}
-
-func (p *proxy) get(domain, username string) (*user, error) {
- for i, d := range p.Domains {
- if d.Address == domain {
- for j, u := range d.Users {
- if u.Username == username {
- return &p.Domains[i].Users[j], nil
- }
- }
- }
- }
- return nil, fmt.Errorf("user not found")
-}
-func (p *proxy) authenticate() http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- var username, password, domain string
- vars := mux.Vars(r)
- if val, ok := vars["domain"]; ok {
- domain = val
- }
- err := r.ParseForm()
- checkErr(err, "ParseForm: ")
- if val := r.Header.Get("Content-Type"); val == "" {
- // no content-type
- }
-
- if val := r.Form.Get("username"); val != "" {
- username = val
- } else {
- // no username
-
- }
-
- if val := r.Form.Get("password"); val != "" {
- password = val
- } else {
- // no password
-
- }
- fmt.Println(r.Header.Get("Content-Type"), domain, username, password)
- if _, err := p.get(domain, username); err != nil {
- fmt.Fprintf(w, "{'access_granted':true}")
- }
-
- })
-}
-
-func main() {
- filePath := "./users.json"
- usersJson, err := os.Open(filePath)
- checkErr(err, "Open "+filePath+": ")
-
- proxy := &proxy{}
- err = json.NewDecoder(usersJson).Decode(&proxy.Domains)
- checkErr(err, "Decode: ")
+ r := mux.NewRouter()
- for i, d := range proxy.Domains {
- for j, u := range d.Users {
- proxy.Domains[i].Users[j].Password = "{SHA256}" + b64sha256(u.Password)
- }
- }
+ // define the api method expectations, useful for future api handles
+ api := r.
+ Methods("POST").
+ Headers("Content-Type", "application/x-www-form-urlencoded").
+ Subrouter()
- r := mux.NewRouter()
- get := r.Methods("POST").Subrouter()
- get.Handle("/api/2/domains/{domain}/proxyauth/", proxy.authenticate())
+ api.Handle("/api/2/domains/{domain}/proxyauth/", proxy.Authenticate())
http.ListenAndServe(":8080", r)
}