aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2020-02-09 15:44:18 +0100
committerAlex Auvolat <alex@adnab.me>2020-02-09 15:44:18 +0100
commite72034c430eb8e78a87d4b1d312e4a9c11f3df14 (patch)
tree6aa953d3969757ab2de76b525f0840abf0b23e99
parent8f5ab6cab3d21029d08e66c5ce99159bdd8a4c7f (diff)
downloadguichet-e72034c430eb8e78a87d4b1d312e4a9c11f3df14.tar.gz
guichet-e72034c430eb8e78a87d4b1d312e4a9c11f3df14.zip
Implement login
-rw-r--r--go.mod6
-rw-r--r--go.sum6
-rw-r--r--main.go136
-rw-r--r--static/style.css6
-rw-r--r--templates/layout.html13
-rw-r--r--templates/login.html12
6 files changed, 171 insertions, 8 deletions
diff --git a/go.mod b/go.mod
index 684bdec..76d92ca 100644
--- a/go.mod
+++ b/go.mod
@@ -2,4 +2,8 @@ module deuxfleurs.fr/Deuxfleurs/guichet
go 1.13
-require github.com/gorilla/sessions v1.2.0
+require (
+ github.com/go-ldap/ldap v3.0.3+incompatible
+ github.com/go-ldap/ldap/v3 v3.1.6
+ github.com/gorilla/sessions v1.2.0
+)
diff --git a/go.sum b/go.sum
index f5e9347..b9d3f98 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,9 @@
+github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck=
+github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
+github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
+github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
+github.com/go-ldap/ldap/v3 v3.1.6 h1:VTihvB7egSAvU6KOagaiA/EvgJMR2jsjRAVIho2ydBo=
+github.com/go-ldap/ldap/v3 v3.1.6/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
diff --git a/main.go b/main.go
index 84eb402..ee01c6e 100644
--- a/main.go
+++ b/main.go
@@ -2,6 +2,7 @@ package main
import (
"os"
+ "strings"
"flag"
"fmt"
"log"
@@ -10,19 +11,29 @@ import (
"encoding/json"
"encoding/base64"
"crypto/rand"
+ "crypto/tls"
+ "html/template"
"github.com/gorilla/sessions"
+ "github.com/go-ldap/ldap/v3"
)
type ConfigFile struct {
HttpBindAddr string `json:"http_bind_addr"`
SessionKey string `json:"session_key"`
LdapServerAddr string `json:"ldap_server_addr"`
+ LdapTLS bool `json:"ldap_tls"`
+ UserFormat string `json:"user_format"`
}
var configFlag = flag.String("config", "./config.json", "Configuration file path")
-func readConfig() ConfigFile {
+var config *ConfigFile
+
+const SESSION_NAME = "guichet_session"
+var store *sessions.CookieStore = nil
+
+func readConfig() ConfigFile{
key_bytes := make([]byte, 32)
n, err := rand.Read(key_bytes)
if err!= nil || n != 32 {
@@ -32,7 +43,9 @@ func readConfig() ConfigFile {
config_file := ConfigFile{
HttpBindAddr: ":9991",
SessionKey: base64.StdEncoding.EncodeToString(key_bytes),
- LdapServerAddr: "127.0.0.1:389",
+ LdapServerAddr: "ldap://127.0.0.1:389",
+ LdapTLS: false,
+ UserFormat: "cn=%s,ou=users,dc=example,dc=com",
}
_, err = os.Stat(*configFlag)
@@ -70,24 +83,133 @@ func readConfig() ConfigFile {
return config_file
}
-var store *sessions.CookieStore = nil
-
func main() {
flag.Parse()
- config := readConfig()
+ config_file := readConfig()
+ config = &config_file
store = sessions.NewCookieStore([]byte(config.SessionKey))
http.HandleFunc("/", handleHome)
- err := http.ListenAndServe(config.HttpBindAddr, nil)
+ staticfiles := http.FileServer(http.Dir("static"))
+ http.Handle("/static/", http.StripPrefix("/static/", staticfiles))
+
+ err := http.ListenAndServe(config.HttpBindAddr, logRequest(http.DefaultServeMux))
if err != nil {
log.Fatal("Cannot start http server: ", err)
}
}
+type LoginInfo struct {
+ Username string
+ DN string
+ Password string
+ }
+
+func logRequest(handler http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
+ handler.ServeHTTP(w, r)
+ })
+}
+
+func checkLogin(w http.ResponseWriter, r *http.Request) *LoginInfo {
+ session, err := store.Get(r, SESSION_NAME)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return nil
+ }
+
+ login_info, has_login_info := session.Values["login_info"]
+ if !has_login_info {
+ return handleLogin(w, r)
+ }
+
+ return login_info.(*LoginInfo)
+}
+
+func ldapOpen(w http.ResponseWriter) *ldap.Conn {
+ l, err := ldap.DialURL(config.LdapServerAddr)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return nil
+ }
+
+ if config.LdapTLS {
+ err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return nil
+ }
+ }
+
+ return l
+}
+
+// Templates ----
+
+type LoginFormData struct {
+ Username string
+ ErrorMessage string
+}
+
+var (
+ templateLogin = template.Must(template.ParseFiles("templates/layout.html", "templates/login.html"))
+)
+
// Page handlers ----
func handleHome(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintf(w, "Hello, world!")
+ login := checkLogin(w, r)
+ if login == nil {
+ return
+ }
+
+ fmt.Fprintf(w, "Welcome, %s!", login.Username)
+}
+
+func handleLogin(w http.ResponseWriter, r *http.Request) *LoginInfo {
+ if r.Method == "GET" {
+ templateLogin.Execute(w, LoginFormData{})
+ return nil
+ } else if r.Method == "POST" {
+ r.ParseForm()
+
+ username := strings.Join(r.Form["username"], "")
+ user_dn := strings.ReplaceAll(config.UserFormat, "%s", username)
+
+ login_info := &LoginInfo{
+ DN: user_dn,
+ Username: username,
+ Password: strings.Join(r.Form["password"], ""),
+ }
+
+ l := ldapOpen(w)
+ if l == nil {
+ return nil
+ }
+
+ err := l.Bind(user_dn, login_info.Password)
+ if err != nil {
+ templateLogin.Execute(w, LoginFormData{
+ Username: username,
+ ErrorMessage: err.Error(),
+ })
+ return nil
+ }
+
+ // Successfully logged in, save it to session
+ session, err := store.Get(r, SESSION_NAME)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return nil
+ }
+
+ session.Values["login_info"] = login_info
+ return login_info
+ } else {
+ http.Error(w, "Unsupported method", http.StatusBadRequest)
+ return nil
+ }
}
diff --git a/static/style.css b/static/style.css
new file mode 100644
index 0000000..057814d
--- /dev/null
+++ b/static/style.css
@@ -0,0 +1,6 @@
+#contents {
+ width: 800px;
+ margin: auto;
+ margin-top: 16px;
+}
+
diff --git a/templates/layout.html b/templates/layout.html
new file mode 100644
index 0000000..dd20e7a
--- /dev/null
+++ b/templates/layout.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>{{template "title"}}</title>
+ <link rel="stylesheet" href="/static/style.css">
+</head>
+<body>
+ <div id="contents">
+ {{template "body" .}}
+ </div>
+</body>
+</html>
diff --git a/templates/login.html b/templates/login.html
new file mode 100644
index 0000000..80642ea
--- /dev/null
+++ b/templates/login.html
@@ -0,0 +1,12 @@
+{{define "title"}}Log in{{end}}
+
+{{define "body"}}
+<h1>Log in</h1>
+
+<form method="POST">
+ <div>{{ .ErrorMessage }}</div>
+ Username: <input type="text" name="username" value="{{ .Username }}" /><br />
+ Password: <input type="password" name="password" /><br />
+ <input type="submit" value="log in">
+</form>
+{{end}}