2020-02-09 13:46:52 +00:00
package main
import (
2020-02-09 14:01:20 +00:00
"crypto/rand"
2020-02-09 15:46:26 +00:00
"encoding/json"
"flag"
2020-02-09 14:44:18 +00:00
"html/template"
2020-02-09 15:46:26 +00:00
"io/ioutil"
"log"
"net/http"
"os"
"strings"
2020-02-09 13:46:52 +00:00
2020-02-09 14:44:18 +00:00
"github.com/go-ldap/ldap/v3"
2020-02-09 21:06:33 +00:00
"github.com/gorilla/mux"
2020-02-09 22:04:37 +00:00
"github.com/gorilla/sessions"
2020-02-09 13:46:52 +00:00
)
2020-02-09 14:01:20 +00:00
type ConfigFile struct {
HttpBindAddr string ` json:"http_bind_addr" `
LdapServerAddr string ` json:"ldap_server_addr" `
2020-02-09 15:46:26 +00:00
LdapTLS bool ` json:"ldap_tls" `
2020-02-09 17:03:10 +00:00
2023-02-08 15:46:13 +00:00
BaseDN string ` json:"base_dn" `
UserBaseDN string ` json:"user_base_dn" `
UserNameAttr string ` json:"user_name_attr" `
GroupBaseDN string ` json:"group_base_dn" `
GroupNameAttr string ` json:"group_name_attr" `
MailingBaseDN string ` json:"mailing_list_base_dn" `
MailingNameAttr string ` json:"mailing_list_name_attr" `
MailingGuestsBaseDN string ` json:"mailing_list_guest_user_base_dn" `
2020-02-14 20:58:34 +00:00
2020-02-14 17:57:25 +00:00
InvitationBaseDN string ` json:"invitation_base_dn" `
InvitationNameAttr string ` json:"invitation_name_attr" `
InvitedMailFormat string ` json:"invited_mail_format" `
InvitedAutoGroups [ ] string ` json:"invited_auto_groups" `
2020-02-09 17:03:10 +00:00
2020-02-14 20:58:34 +00:00
WebAddress string ` json:"web_address" `
MailFrom string ` json:"mail_from" `
SMTPServer string ` json:"smtp_server" `
SMTPUsername string ` json:"smtp_username" `
SMTPPassword string ` json:"smtp_password" `
2020-02-10 14:26:02 +00:00
AdminAccount string ` json:"admin_account" `
2020-02-09 16:52:48 +00:00
GroupCanInvite string ` json:"group_can_invite" `
GroupCanAdmin string ` json:"group_can_admin" `
2021-07-29 22:04:17 +00:00
2023-04-19 13:07:46 +00:00
S3AdminEndpoint string ` json:"s3_admin_endpoint" `
S3AdminToken string ` json:"s3_admin_token" `
2023-04-18 20:45:04 +00:00
2021-08-16 13:30:14 +00:00
S3Endpoint string ` json:"s3_endpoint" `
S3AccessKey string ` json:"s3_access_key" `
S3SecretKey string ` json:"s3_secret_key" `
S3Region string ` json:"s3_region" `
S3Bucket string ` json:"s3_bucket" `
2020-02-09 14:01:20 +00:00
}
2020-02-09 13:46:52 +00:00
2023-09-25 08:27:49 +00:00
var fsServer = flag . NewFlagSet ( "server" , flag . ContinueOnError )
var configFlag = fsServer . String ( "config" , "./config.json" , "Configuration file path" )
2020-02-09 14:01:20 +00:00
2020-02-09 14:44:18 +00:00
var config * ConfigFile
const SESSION_NAME = "guichet_session"
2022-12-01 22:05:59 +00:00
var staticPath = "./static"
var templatePath = "./templates"
2020-02-09 15:46:26 +00:00
var store sessions . Store = nil
func readConfig ( ) ConfigFile {
2021-08-16 12:36:14 +00:00
// Default configuration values for certain fields
2020-02-09 14:01:20 +00:00
config_file := ConfigFile {
HttpBindAddr : ":9991" ,
2020-02-09 14:44:18 +00:00
LdapServerAddr : "ldap://127.0.0.1:389" ,
2020-02-14 20:58:34 +00:00
UserNameAttr : "uid" ,
GroupNameAttr : "gid" ,
InvitationNameAttr : "cn" ,
InvitedAutoGroups : [ ] string { } ,
2020-02-09 14:01:20 +00:00
}
2020-02-10 14:26:02 +00:00
_ , err := os . Stat ( * configFlag )
2020-02-09 14:01:20 +00:00
if os . IsNotExist ( err ) {
2021-08-16 12:36:14 +00:00
log . Fatalf ( "Could not find Guichet configuration file at %s. Please create this file, for example starting with config.json.example and customizing it for your deployment." , * configFlag )
2020-02-09 14:01:20 +00:00
}
if err != nil {
log . Fatal ( err )
}
bytes , err := ioutil . ReadFile ( * configFlag )
if err != nil {
log . Fatal ( err )
}
err = json . Unmarshal ( bytes , & config_file )
if err != nil {
log . Fatal ( err )
}
return config_file
2020-02-09 13:46:52 +00:00
}
2022-12-01 22:05:59 +00:00
func getTemplate ( name string ) * template . Template {
2023-04-19 13:07:46 +00:00
return template . Must ( template . New ( "layout.html" ) . Funcs ( template . FuncMap {
"contains" : strings . Contains ,
} ) . ParseFiles (
templatePath + "/layout.html" ,
templatePath + "/" + name ,
) )
2022-12-01 22:05:59 +00:00
}
2020-02-09 13:46:52 +00:00
func main ( ) {
2023-09-25 08:27:49 +00:00
if len ( os . Args ) < 2 {
server ( os . Args [ 1 : ] )
return
}
switch os . Args [ 1 ] {
case "cli" :
cliMain ( os . Args [ 2 : ] )
case "server" :
server ( os . Args [ 2 : ] )
default :
log . Println ( "Usage: guichet [server|cli] --help" )
os . Exit ( 1 )
}
}
2020-02-09 13:46:52 +00:00
2023-09-25 08:27:49 +00:00
func server ( args [ ] string ) {
log . Println ( "Starting Guichet Server" )
fsServer . Parse ( args )
2020-02-09 14:44:18 +00:00
config_file := readConfig ( )
config = & config_file
2020-02-10 14:26:02 +00:00
session_key := make ( [ ] byte , 32 )
n , err := rand . Read ( session_key )
if err != nil || n != 32 {
log . Fatal ( err )
}
store = sessions . NewCookieStore ( session_key )
2020-02-09 14:01:20 +00:00
2020-02-09 21:06:33 +00:00
r := mux . NewRouter ( )
r . HandleFunc ( "/" , handleHome )
2023-09-25 13:35:54 +00:00
r . HandleFunc ( "/login" , handleLogin )
2020-02-09 21:06:33 +00:00
r . HandleFunc ( "/logout" , handleLogout )
2021-08-16 13:30:14 +00:00
2023-09-25 17:07:07 +00:00
r . HandleFunc ( "/api/unstable/website/{bucket}" , handleAPIWebsite )
2023-09-15 12:32:44 +00:00
2020-02-09 21:06:33 +00:00
r . HandleFunc ( "/profile" , handleProfile )
r . HandleFunc ( "/passwd" , handlePasswd )
2021-08-16 13:30:14 +00:00
r . HandleFunc ( "/picture/{name}" , handleDownloadPicture )
2021-07-29 22:04:17 +00:00
2021-08-16 14:27:20 +00:00
r . HandleFunc ( "/directory/search" , handleDirectorySearch )
2021-07-21 19:21:46 +00:00
r . HandleFunc ( "/directory" , handleDirectory )
2023-09-25 17:07:07 +00:00
r . HandleFunc ( "/website" , handleWebsiteList )
r . HandleFunc ( "/website/new" , handleWebsiteNew )
r . HandleFunc ( "/website/configure" , handleWebsiteConfigure )
r . HandleFunc ( "/website/inspect/{bucket}" , handleWebsiteInspect )
2023-04-18 17:37:51 +00:00
2020-02-14 17:57:25 +00:00
r . HandleFunc ( "/invite/new_account" , handleInviteNewAccount )
r . HandleFunc ( "/invite/send_code" , handleInviteSendCode )
2020-02-14 20:58:34 +00:00
r . HandleFunc ( "/invitation/{code}" , handleInvitationCode )
2020-02-14 17:57:25 +00:00
2020-02-09 21:06:33 +00:00
r . HandleFunc ( "/admin/users" , handleAdminUsers )
r . HandleFunc ( "/admin/groups" , handleAdminGroups )
2023-02-08 12:11:43 +00:00
r . HandleFunc ( "/admin/mailing" , handleAdminMailing )
r . HandleFunc ( "/admin/mailing/{id}" , handleAdminMailingList )
2020-02-09 21:06:33 +00:00
r . HandleFunc ( "/admin/ldap/{dn}" , handleAdminLDAP )
2020-02-09 22:04:27 +00:00
r . HandleFunc ( "/admin/create/{template}/{super_dn}" , handleAdminCreate )
2020-02-09 17:28:42 +00:00
2022-12-01 22:05:59 +00:00
staticfiles := http . FileServer ( http . Dir ( staticPath ) )
2020-02-09 21:06:33 +00:00
r . Handle ( "/static/{file:.*}" , http . StripPrefix ( "/static/" , staticfiles ) )
2020-02-09 14:44:18 +00:00
2020-02-10 14:26:02 +00:00
log . Printf ( "Starting HTTP server on %s" , config . HttpBindAddr )
2020-02-10 14:26:02 +00:00
err = http . ListenAndServe ( config . HttpBindAddr , logRequest ( r ) )
2020-02-09 13:46:52 +00:00
if err != nil {
log . Fatal ( "Cannot start http server: " , err )
}
}
2020-02-09 14:01:20 +00:00
2020-02-09 14:44:18 +00:00
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 )
} )
}
2020-02-09 14:01:20 +00:00
// Page handlers ----
2023-09-25 13:35:54 +00:00
// --- Home Controller
2020-02-09 16:52:48 +00:00
type HomePageData struct {
2023-09-25 13:35:54 +00:00
User * LoggedUser
2020-02-14 20:58:34 +00:00
BaseDN string
2020-02-09 16:52:48 +00:00
}
2020-02-09 14:01:20 +00:00
func handleHome ( w http . ResponseWriter , r * http . Request ) {
2022-12-01 22:05:59 +00:00
templateHome := getTemplate ( "home.html" )
2020-02-09 15:46:26 +00:00
2023-09-25 13:35:54 +00:00
user := RequireUserHtml ( w , r )
if user == nil {
2020-02-09 14:44:18 +00:00
return
}
2020-02-10 14:26:02 +00:00
data := & HomePageData {
2023-09-25 13:35:54 +00:00
User : user ,
2020-02-14 20:58:34 +00:00
BaseDN : config . BaseDN ,
2020-02-10 14:26:02 +00:00
}
templateHome . Execute ( w , data )
2020-02-09 14:44:18 +00:00
}
2023-09-25 13:35:54 +00:00
// --- Logout Controller
2020-02-09 15:46:26 +00:00
func handleLogout ( w http . ResponseWriter , r * http . Request ) {
session , err := store . Get ( r , SESSION_NAME )
if err != nil {
2020-02-14 17:57:25 +00:00
session , _ = store . New ( r , SESSION_NAME )
2020-02-09 15:46:26 +00:00
}
delete ( session . Values , "login_username" )
delete ( session . Values , "login_password" )
delete ( session . Values , "login_dn" )
err = session . Save ( r , w )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
2023-09-25 13:35:54 +00:00
http . Redirect ( w , r , "/login" , http . StatusFound )
2020-02-09 15:46:26 +00:00
}
2023-09-25 13:35:54 +00:00
// --- Login Controller ---
2020-02-09 17:03:10 +00:00
type LoginFormData struct {
Username string
2020-02-12 15:02:42 +00:00
WrongUser bool
WrongPass bool
2020-02-09 17:03:10 +00:00
ErrorMessage string
}
2023-09-25 13:35:54 +00:00
func handleLogin ( w http . ResponseWriter , r * http . Request ) {
2022-12-01 22:05:59 +00:00
templateLogin := getTemplate ( "login.html" )
2020-02-09 15:46:26 +00:00
2020-02-09 14:44:18 +00:00
if r . Method == "GET" {
templateLogin . Execute ( w , LoginFormData { } )
2023-09-25 13:35:54 +00:00
return
2020-02-09 14:44:18 +00:00
} else if r . Method == "POST" {
r . ParseForm ( )
username := strings . Join ( r . Form [ "username" ] , "" )
2020-02-09 15:46:26 +00:00
password := strings . Join ( r . Form [ "password" ] , "" )
2023-09-25 13:35:54 +00:00
loginInfo := LoginInfo { username , password }
2020-02-09 14:44:18 +00:00
2023-09-25 13:35:54 +00:00
l , err := NewLdapCon ( )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
2020-02-09 14:44:18 +00:00
}
2023-09-25 13:35:54 +00:00
err = l . Bind ( loginInfo . DN ( ) , loginInfo . Password )
2020-02-09 14:44:18 +00:00
if err != nil {
2020-02-12 15:02:42 +00:00
data := & LoginFormData {
Username : username ,
}
if ldap . IsErrorWithCode ( err , ldap . LDAPResultInvalidCredentials ) {
data . WrongPass = true
} else if ldap . IsErrorWithCode ( err , ldap . LDAPResultNoSuchObject ) {
data . WrongUser = true
} else {
data . ErrorMessage = err . Error ( )
}
templateLogin . Execute ( w , data )
2023-09-25 13:35:54 +00:00
return
2020-02-09 14:44:18 +00:00
}
// Successfully logged in, save it to session
session , err := store . Get ( r , SESSION_NAME )
if err != nil {
2020-02-10 14:26:02 +00:00
session , _ = store . New ( r , SESSION_NAME )
2020-02-09 14:44:18 +00:00
}
2020-02-09 15:46:26 +00:00
session . Values [ "login_username" ] = username
session . Values [ "login_password" ] = password
err = session . Save ( r , w )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
2023-09-25 13:35:54 +00:00
return
2020-02-09 15:46:26 +00:00
}
2023-09-25 13:35:54 +00:00
http . Redirect ( w , r , "/" , http . StatusFound )
2020-02-09 14:44:18 +00:00
} else {
http . Error ( w , "Unsupported method" , http . StatusBadRequest )
}
2020-02-09 14:01:20 +00:00
}