diff --git a/README.md b/README.md index fad0594..449017f 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,22 @@ Guichet requires go 1.13 or later. To build Guichet, clone this repository outside of your `$GOPATH`. Then, run `make` in the root of the repo. +## Releasing Guichet + +To build Guichet in a controlled environment, because you plan to release it for example, please use Nix. + +```bash +nix-build -A bin # build only the Go binary +nix-build -A pkg # build the binary and add the ressources +nix-build -A docker # build a docker container +``` + +Publishing the container: + +```bash +docker load < $(nix-build -A docker) +docker push ??? +``` ## Configuration of Guichet diff --git a/admin.go b/admin.go index a2c70c5..3d6674e 100644 --- a/admin.go +++ b/admin.go @@ -48,7 +48,7 @@ type AdminUsersTplData struct { } func handleAdminUsers(w http.ResponseWriter, r *http.Request) { - templateAdminUsers := template.Must(template.ParseFiles("templates/layout.html", "templates/admin_users.html")) + templateAdminUsers := template.Must(template.ParseFiles(config.Resources[0]+"/templates/layout.html", config.Resources[0]+"/templates/admin_users.html")) login := checkAdminLogin(w, r) if login == nil { @@ -87,7 +87,7 @@ type AdminGroupsTplData struct { } func handleAdminGroups(w http.ResponseWriter, r *http.Request) { - templateAdminGroups := template.Must(template.ParseFiles("templates/layout.html", "templates/admin_groups.html")) + templateAdminGroups := template.Must(template.ParseFiles(config.Resources[0]+"/templates/layout.html", config.Resources[0]+"/templates/admin_groups.html")) login := checkAdminLogin(w, r) if login == nil { @@ -165,7 +165,7 @@ type PropValues struct { } func handleAdminLDAP(w http.ResponseWriter, r *http.Request) { - templateAdminLDAP := template.Must(template.ParseFiles("templates/layout.html", "templates/admin_ldap.html")) + templateAdminLDAP := template.Must(template.ParseFiles(config.Resources[0]+"/templates/layout.html", config.Resources[0]+"/templates/admin_ldap.html")) login := checkAdminLogin(w, r) if login == nil { @@ -551,7 +551,7 @@ type CreateData struct { } func handleAdminCreate(w http.ResponseWriter, r *http.Request) { - templateAdminCreate := template.Must(template.ParseFiles("templates/layout.html", "templates/admin_create.html")) + templateAdminCreate := template.Must(template.ParseFiles(config.Resources[0]+"/templates/layout.html", config.Resources[0]+"/templates/admin_create.html")) login := checkAdminLogin(w, r) if login == nil { diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..cf40d93 --- /dev/null +++ b/default.nix @@ -0,0 +1,53 @@ +let + pkgsSrc = fetchTarball { + # As of 2022-07-19 + url = "https://github.com/NixOS/nixpkgs/archive/d2db10786f27619d5519b12b03fb10dc8ca95e59.tar.gz"; + sha256 = "0s9gigs3ylnq5b94rfcmxvrmmr3kzhs497gksajf638d5bv7zcl5"; + }; + gomod2nix = fetchGit { + url = "https://github.com/tweag/gomod2nix.git"; + ref = "master"; + rev = "40d32f82fc60d66402eb0972e6e368aeab3faf58"; + }; + + pkgs = import pkgsSrc { + overlays = [ + (self: super: { + gomod = super.callPackage "${gomod2nix}/builder/" { }; + }) + ]; + }; +in rec { + bin = pkgs.gomod.buildGoApplication { + pname = "guichet-bin"; + version = "0.1.0"; + src = ./.; + modules = ./gomod2nix.toml; + + CGO_ENABLED=0; + + meta = with pkgs.lib; { + description = "Interface web pour gérer le LDAP: changer son mot de passe, ses infos de profil, inviter des gens, administration"; + homepage = "https://git.deuxfleurs.fr/Deuxfleurs/guichet"; + license = licenses.gpl3Plus; + platforms = platforms.linux; + }; + }; + pkg = pkgs.stdenv.mkDerivation { + pname = "guichet"; + version = "0.1.0"; + src = ./.; + + installPhase = '' + mkdir -p $out/ + cp ${bin}/bin/guichet $out/guichet + cp -r templates static $out/ + ''; + }; + docker = pkgs.dockerTools.buildImage { + name = "dxflrs/guichet"; + config = { + Cmd = [ "${pkg}/guichet" ]; + }; + }; +} diff --git a/directory.go b/directory.go index ab5dea3..213b11b 100644 --- a/directory.go +++ b/directory.go @@ -13,7 +13,7 @@ const FIELD_NAME_PROFILE_PICTURE = "profilePicture" const FIELD_NAME_DIRECTORY_VISIBILITY = "directoryVisibility" func handleDirectory(w http.ResponseWriter, r *http.Request) { - templateDirectory := template.Must(template.ParseFiles("templates/layout.html", "templates/directory.html")) + templateDirectory := template.Must(template.ParseFiles(config.Resources[0]+"/templates/layout.html", config.Resources[0]+"/templates/directory.html")) login := checkLogin(w, r) if login == nil { @@ -37,7 +37,7 @@ type SearchResults struct { } func handleDirectorySearch(w http.ResponseWriter, r *http.Request) { - templateDirectoryResults := template.Must(template.ParseFiles("templates/directory_results.html")) + templateDirectoryResults := template.Must(template.ParseFiles(config.Resources[0] + "/templates/directory_results.html")) //Get input value by user r.ParseMultipartForm(1024) diff --git a/gomod2nix.toml b/gomod2nix.toml new file mode 100644 index 0000000..690a31d --- /dev/null +++ b/gomod2nix.toml @@ -0,0 +1,111 @@ +schema = 3 + +[mod] + [mod."github.com/davecgh/go-spew"] + version = "v1.1.1" + hash = "sha256-nhzSUrE1fCkN0+RL04N4h8jWmRFPPPWbCuDc7Ss0akI=" + [mod."github.com/dustin/go-humanize"] + version = "v1.0.0" + hash = "sha256-gy4G1PnHD9iw2MitHX6y1y93qr3C9IncmXL7ttUMDs8=" + [mod."github.com/emersion/go-sasl"] + version = "v0.0.0-20191210011802-430746ea8b9b" + hash = "sha256-bADpAn0ZhlTTsEB3MsG8J31cQjTtHTzohX/wkL1aMIc=" + [mod."github.com/emersion/go-smtp"] + version = "v0.12.1" + hash = "sha256-fiss5y7chfHv80vIQ9Xwx3J+3qLMA63EOP4OG3DxAtI=" + [mod."github.com/go-asn1-ber/asn1-ber"] + version = "v1.3.1" + hash = "sha256-Alh6bUq9HoBDhY+n6W7xNBto/dUMxPGvucA6guarrjc=" + [mod."github.com/go-ldap/ldap/v3"] + version = "v3.1.6" + hash = "sha256-UPUdYKOoCQWgl2Onbq1Oql7XU4TeYQA/+j4atwhdKbE=" + [mod."github.com/google/gofuzz"] + version = "v1.0.0" + hash = "sha256-ZvgcSQt4kMwS6nvPp3jrlCHSH3bky1oBD6kytnEa5GM=" + [mod."github.com/google/uuid"] + version = "v1.1.1" + hash = "sha256-66PXC/RCPUyhS9PhkIPQFR3tbM2zZYDNPGXN7JJj3UE=" + [mod."github.com/gopherjs/gopherjs"] + version = "v0.0.0-20181017120253-0766667cb4d1" + hash = "sha256-AuXnjjoLbFZ85Oi8sldH117MBh+yCUB9HU5Y5syJ7Lg=" + [mod."github.com/gorilla/mux"] + version = "v1.7.3" + hash = "sha256-YZSIN7Ua+hPqSIrT+tiRz3aFqJ1EWHvwee+PptpHI28=" + [mod."github.com/gorilla/securecookie"] + version = "v1.1.1" + hash = "sha256-IBBYWfdOuXvQsb01DaA8tBizCfAE1J2KLXIn3W+NeJk=" + [mod."github.com/gorilla/sessions"] + version = "v1.2.0" + hash = "sha256-4V7yd/vf03CEsb3pz5dbLWwv7t9QgKkEhVXtc1/z5s8=" + [mod."github.com/jsimonetti/pwscheme"] + version = "v0.0.0-20220125093853-4d9895f5db73" + hash = "sha256-YF3RKU/4CWvLPgGzUd7Hk/2+41OUFuRWZgzQuqcsKg0=" + [mod."github.com/json-iterator/go"] + version = "v1.1.10" + hash = "sha256-jdS2C0WsgsWREBSj+YUzSqdZofMfUMecaOQ/lB9Mu6k=" + [mod."github.com/jtolds/gls"] + version = "v4.20.0+incompatible" + hash = "sha256-Zu5naRjnwOYBzRv0CYhIZTizA0AajzOg7mJrL7Bo/cw=" + [mod."github.com/klauspost/cpuid"] + version = "v1.2.3" + hash = "sha256-1IBlONMxKVgudV/mzNrFZB60z8w4xFjVbEU2DoIAoeg=" + [mod."github.com/konsorten/go-windows-terminal-sequences"] + version = "v1.0.3" + hash = "sha256-9HojTXKv7b3HiEhYaKXDxraEfvU5vrg47FbCjTRpOvs=" + [mod."github.com/minio/md5-simd"] + version = "v1.1.0" + hash = "sha256-jJbDwg7KlLB991wj1U6y+kJKOUxKVGQrDbM3nY+6qxE=" + [mod."github.com/minio/minio-go/v7"] + version = "v7.0.0" + hash = "sha256-xWAELgH6mWVGKFEe2gbzvigJDNk+ELmegJe09KvUqvY=" + [mod."github.com/minio/sha256-simd"] + version = "v0.1.1" + hash = "sha256-HpcuLTnpcyKe0ua2MN/ysK5cXdrwquDjrx4Y2dG6W2s=" + [mod."github.com/mitchellh/go-homedir"] + version = "v1.1.0" + hash = "sha256-oduBKXHAQG8X6aqLEpqZHs5DOKe84u6WkBwi4W6cv3k=" + [mod."github.com/modern-go/concurrent"] + version = "v0.0.0-20180228061459-e0a39a4cb421" + hash = "sha256-+bdeHUArnpkk4eMQIwXm9K249NkpwAjoTrXrGn/4VUE=" + [mod."github.com/modern-go/reflect2"] + version = "v0.0.0-20180701023420-4b7aa43c6742" + hash = "sha256-RyIwgrPwtd4lNjLGkBVxRvu5IdXLDqf5F69QWLm0zLw=" + [mod."github.com/nfnt/resize"] + version = "v0.0.0-20180221191011-83c6a9932646" + hash = "sha256-yvPV+HlDOyJsiwAcVHQkmtw8DHSXyw+cXHkigXm8rAA=" + [mod."github.com/pmezard/go-difflib"] + version = "v1.0.0" + hash = "sha256-/FtmHnaGjdvEIKAJtrUfEhV7EVo5A/eYrtdnUkuxLDA=" + [mod."github.com/sirupsen/logrus"] + version = "v1.6.0" + hash = "sha256-4v27X4yyl52BtZcZEnDe0tfvOaEq+TCcp7R8HBzreDM=" + [mod."github.com/smartystreets/assertions"] + version = "v0.0.0-20180927180507-b2de0cb4f26d" + hash = "sha256-PoE+VQEnzJogI/mDBJ6dTCCR217nFjHfYWXQt9Vr9MQ=" + [mod."github.com/smartystreets/goconvey"] + version = "v0.0.0-20190330032615-68dc04aab96a" + hash = "sha256-HD+AZE1agl1pVbQTFUKLKtjg3XdVSVLwRSu4u+UVV2M=" + [mod."github.com/stretchr/objx"] + version = "v0.1.0" + hash = "sha256-az0Vd4MG3JXfaYbj0Q6AOmNkrXgmXDeQm8+BBiDXmdA=" + [mod."github.com/stretchr/testify"] + version = "v1.3.0" + hash = "sha256-+mSebBNccNcxbY462iKTNTWmd5ZuUkUqFebccn3EtIA=" + [mod."golang.org/x/crypto"] + version = "v0.0.0-20200214034016-1d94cc7ab1c6" + hash = "sha256-fWTzdDxt/1E8Jx7b6tmYEVqqJs5FoVVya9aEK9gDbdY=" + [mod."golang.org/x/net"] + version = "v0.0.0-20190522155817-f3200d17e092" + hash = "sha256-KkNNFr+wx/pf7lSLN2ygwkQ9oCZQuef+hCtEjEX+gJE=" + [mod."golang.org/x/sys"] + version = "v0.0.0-20200223170610-d5e6a3e2c0ae" + hash = "sha256-IvG2XSER2dyrVfhYieEpHcp28LOz4FrjQqN0SCeFOek=" + [mod."golang.org/x/text"] + version = "v0.3.0" + hash = "sha256-0FFbaxF1ZuAQF3sCcA85e8MO6prFeHint36inija4NY=" + [mod."golang.org/x/tools"] + version = "v0.0.0-20190328211700-ab21143f2384" + hash = "sha256-OcjaTxx6C/cbnUZLN2ArTrOBlBCijWJVUPaMgK67MkY=" + [mod."gopkg.in/ini.v1"] + version = "v1.57.0" + hash = "sha256-WSjX+qHJ1Rf4FRMTs7udQwEBkIo+z8+EK3uB5CebrZ4=" diff --git a/invite.go b/invite.go index 689c7e4..654d179 100644 --- a/invite.go +++ b/invite.go @@ -60,7 +60,7 @@ func handleInvitationCode(w http.ResponseWriter, r *http.Request) { inviteDn := config.InvitationNameAttr + "=" + code_id + "," + config.InvitationBaseDN err := l.Bind(inviteDn, code_pw) if err != nil { - templateInviteInvalidCode := template.Must(template.ParseFiles("templates/layout.html", "templates/invite_invalid_code.html")) + templateInviteInvalidCode := template.Must(template.ParseFiles(config.Resources[0]+"/templates/layout.html", config.Resources[0]+"/templates/invite_invalid_code.html")) templateInviteInvalidCode.Execute(w, nil) return } @@ -110,7 +110,7 @@ type NewAccountData struct { } func handleNewAccount(w http.ResponseWriter, r *http.Request, l *ldap.Conn, invitedBy string) bool { - templateInviteNewAccount := template.Must(template.ParseFiles("templates/layout.html", "templates/invite_new_account.html")) + templateInviteNewAccount := template.Must(template.ParseFiles(config.Resources[0]+"/templates/layout.html", config.Resources[0]+"/templates/invite_new_account.html")) data := &NewAccountData{} @@ -174,7 +174,7 @@ func tryCreateAccount(l *ldap.Conn, data *NewAccountData, pass1 string, pass2 st if checkFailed { return - } + } // Actually create user req := ldap.NewAddRequest(userDn, nil) @@ -239,7 +239,7 @@ type CodeMailFields struct { } func handleInviteSendCode(w http.ResponseWriter, r *http.Request) { - templateInviteSendCode := template.Must(template.ParseFiles("templates/layout.html", "templates/invite_send_code.html")) + templateInviteSendCode := template.Must(template.ParseFiles(config.Resources[0]+"/templates/layout.html", config.Resources[0]+"/templates/invite_send_code.html")) login := checkInviterLogin(w, r) if login == nil { @@ -298,7 +298,7 @@ func trySendCode(login *LoginStatus, choice string, sendto string, data *SendCod return } - templateMail := template.Must(template.ParseFiles("templates/invite_mail.txt")) + templateMail := template.Must(template.ParseFiles(config.Resources[0] + "/templates/invite_mail.txt")) buf := bytes.NewBuffer([]byte{}) templateMail.Execute(buf, &CodeMailFields{ To: sendto, diff --git a/main.go b/main.go index d574f3f..c67a3dc 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "log" "net/http" "os" + "path/filepath" "strings" "github.com/go-ldap/ldap/v3" @@ -19,9 +20,10 @@ import ( ) type ConfigFile struct { - HttpBindAddr string `json:"http_bind_addr"` - LdapServerAddr string `json:"ldap_server_addr"` - LdapTLS bool `json:"ldap_tls"` + Resources []string `json:"resources"` + HttpBindAddr string `json:"http_bind_addr"` + LdapServerAddr string `json:"ldap_server_addr"` + LdapTLS bool `json:"ldap_tls"` BaseDN string `json:"base_dn"` UserBaseDN string `json:"user_base_dn"` @@ -62,6 +64,7 @@ var store sessions.Store = nil func readConfig() ConfigFile { // Default configuration values for certain fields config_file := ConfigFile{ + Resources: []string{}, HttpBindAddr: ":9991", LdapServerAddr: "ldap://127.0.0.1:389", @@ -91,13 +94,39 @@ func readConfig() ConfigFile { log.Fatal(err) } + // Enrich the Resource entry with default values + config_file.Resources = append(config_file.Resources, ".") + ex, err := os.Executable() + if err == nil { + exPath := filepath.Dir(ex) + config_file.Resources = append(config_file.Resources, exPath) + } + return config_file } +func selectResource(conf *ConfigFile) { +ResourceLoop: + for _, p := range conf.Resources { + for _, suffix := range []string{"", "/templates", "/static"} { + _, err := os.Stat(p + suffix) + if err != nil { + continue ResourceLoop + } + } + + // Success, this Resource folder is the one! + conf.Resources = []string{p} + return + } + log.Fatalf("Unable to find templates/ and static/ in the following paths: %s", conf.Resources) +} + func main() { flag.Parse() config_file := readConfig() + selectResource(&config_file) config = &config_file session_key := make([]byte, 32) @@ -127,7 +156,7 @@ func main() { r.HandleFunc("/admin/ldap/{dn}", handleAdminLDAP) r.HandleFunc("/admin/create/{template}/{super_dn}", handleAdminCreate) - staticfiles := http.FileServer(http.Dir("static")) + staticfiles := http.FileServer(http.Dir(config.Resources[0] + "/static")) r.Handle("/static/{file:.*}", http.StripPrefix("/static/", staticfiles)) log.Printf("Starting HTTP server on %s", config.HttpBindAddr) @@ -296,7 +325,7 @@ type HomePageData struct { } func handleHome(w http.ResponseWriter, r *http.Request) { - templateHome := template.Must(template.ParseFiles("templates/layout.html", "templates/home.html")) + templateHome := template.Must(template.ParseFiles(config.Resources[0]+"/templates/layout.html", config.Resources[0]+"/templates/home.html")) login := checkLogin(w, r) if login == nil { @@ -338,7 +367,7 @@ type LoginFormData struct { } func handleLogin(w http.ResponseWriter, r *http.Request) *LoginInfo { - templateLogin := template.Must(template.ParseFiles("templates/layout.html", "templates/login.html")) + templateLogin := template.Must(template.ParseFiles(config.Resources[0]+"/templates/layout.html", config.Resources[0]+"/templates/login.html")) if r.Method == "GET" { templateLogin.Execute(w, LoginFormData{}) diff --git a/profile.go b/profile.go index 603b10a..0b91e82 100644 --- a/profile.go +++ b/profile.go @@ -22,7 +22,7 @@ type ProfileTplData struct { } func handleProfile(w http.ResponseWriter, r *http.Request) { - templateProfile := template.Must(template.ParseFiles("templates/layout.html", "templates/profile.html")) + templateProfile := template.Must(template.ParseFiles(config.Resources[0]+"/templates/layout.html", config.Resources[0]+"/templates/profile.html")) login := checkLogin(w, r) if login == nil { @@ -97,7 +97,7 @@ type PasswdTplData struct { } func handlePasswd(w http.ResponseWriter, r *http.Request) { - templatePasswd := template.Must(template.ParseFiles("templates/layout.html", "templates/passwd.html")) + templatePasswd := template.Must(template.ParseFiles(config.Resources[0]+"/templates/layout.html", config.Resources[0]+"/templates/passwd.html")) login := checkLogin(w, r) if login == nil { @@ -122,7 +122,7 @@ func handlePasswd(w http.ResponseWriter, r *http.Request) { data.NoMatchError = true } else { modify_request := ldap.NewModifyRequest(login.Info.DN, nil) - pw, err := SSHAEncode(password); + pw, err := SSHAEncode(password) if err == nil { modify_request.Replace("userpassword", []string{pw}) err := login.conn.Modify(modify_request)