2020-02-09 13:46:52 +00:00
package main
import (
2020-02-09 14:01:20 +00:00
"crypto/rand"
2020-02-09 14:44:18 +00:00
"crypto/tls"
2020-02-09 15:46:26 +00:00
"encoding/json"
"flag"
2020-02-09 16:52:48 +00:00
"fmt"
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" `
2023-07-18 14:09:10 +00:00
Org string ` json:"org" `
2020-02-09 14:01:20 +00:00
}
2020-02-09 13:46:52 +00:00
2020-02-09 14:01:20 +00:00
var configFlag = flag . String ( "config" , "./config.json" , "Configuration file path" )
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 { } ,
2023-07-18 14:09:10 +00:00
Org : "ResDigita" ,
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 ) {
2023-07-17 09:18:21 +00:00
log . Fatalf ( "Could not find Guichet configuration file at %s. Please create this file, for exemple starting with config.json.exemple 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 ( ) {
2020-02-09 14:01:20 +00:00
flag . Parse ( )
2020-02-09 13:46:52 +00:00
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 )
r . HandleFunc ( "/logout" , handleLogout )
2021-08-16 13:30:14 +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-04-19 13:07:46 +00:00
r . HandleFunc ( "/garage/key" , handleGarageKey )
r . HandleFunc ( "/garage/website" , handleGarageWebsiteList )
r . HandleFunc ( "/garage/website/new" , handleGarageWebsiteNew )
r . HandleFunc ( "/garage/website/b/{bucket}" , handleGarageWebsiteInspect )
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
2023-07-20 08:21:51 +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
type LoginInfo struct {
Username string
2020-02-09 15:46:26 +00:00
DN string
2020-02-09 14:44:18 +00:00
Password string
2020-02-09 15:46:26 +00:00
}
2020-02-09 14:44:18 +00:00
2020-02-09 16:35:16 +00:00
type LoginStatus struct {
2020-02-09 16:52:48 +00:00
Info * LoginInfo
conn * ldap . Conn
2020-02-09 16:35:16 +00:00
UserEntry * ldap . Entry
2020-02-11 23:13:24 +00:00
CanAdmin bool
CanInvite bool
2020-02-09 16:35:16 +00:00
}
2020-02-14 20:58:34 +00:00
func ( login * LoginStatus ) WelcomeName ( ) string {
ret := login . UserEntry . GetAttributeValue ( "givenname" )
if ret == "" {
ret = login . UserEntry . GetAttributeValue ( "displayname" )
}
if ret == "" {
ret = login . Info . Username
}
return ret
}
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 ) {
2023-07-20 08:21:51 +00:00
// log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
2020-02-09 14:44:18 +00:00
handler . ServeHTTP ( w , r )
} )
}
2020-02-09 16:35:16 +00:00
func checkLogin ( w http . ResponseWriter , r * http . Request ) * LoginStatus {
2020-02-10 14:26:02 +00:00
var login_info * LoginInfo
2020-02-09 14:44:18 +00:00
session , err := store . Get ( r , SESSION_NAME )
2020-02-10 14:26:02 +00:00
if err == nil {
username , ok := session . Values [ "login_username" ]
password , ok2 := session . Values [ "login_password" ]
user_dn , ok3 := session . Values [ "login_dn" ]
if ok && ok2 && ok3 {
login_info = & LoginInfo {
DN : user_dn . ( string ) ,
Username : username . ( string ) ,
Password : password . ( string ) ,
}
}
2020-02-09 15:46:26 +00:00
}
2020-02-09 14:44:18 +00:00
2020-02-10 14:26:02 +00:00
if login_info == nil {
2020-02-09 16:35:16 +00:00
login_info = handleLogin ( w , r )
if login_info == nil {
return nil
}
2020-02-09 14:44:18 +00:00
}
2020-02-09 16:35:16 +00:00
l := ldapOpen ( w )
if l == nil {
return nil
}
err = l . Bind ( login_info . DN , login_info . Password )
if err != nil {
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 nil
}
return checkLogin ( w , r )
}
2020-02-10 14:26:02 +00:00
loginStatus := & LoginStatus {
2020-02-10 14:26:02 +00:00
Info : login_info ,
conn : l ,
2020-02-10 14:26:02 +00:00
}
requestKind := "(objectClass=organizationalPerson)"
2020-02-11 23:13:24 +00:00
if strings . EqualFold ( login_info . DN , config . AdminAccount ) {
2020-02-10 14:26:02 +00:00
requestKind = "(objectclass=*)"
}
2020-02-09 16:35:16 +00:00
searchRequest := ldap . NewSearchRequest (
login_info . DN ,
ldap . ScopeBaseObject , ldap . NeverDerefAliases , 0 , 0 , false ,
2020-02-10 14:26:02 +00:00
requestKind ,
2021-08-16 13:30:14 +00:00
[ ] string {
"dn" ,
"displayname" ,
"givenname" ,
"sn" ,
"mail" ,
2023-07-17 08:19:01 +00:00
"cn" ,
2021-08-16 13:30:14 +00:00
"memberof" ,
"description" ,
2023-04-19 13:07:46 +00:00
"garage_s3_access_key" ,
2021-08-16 13:30:14 +00:00
} ,
2020-02-09 16:35:16 +00:00
nil )
2023-07-17 08:19:01 +00:00
// FIELD_NAME_DIRECTORY_VISIBILITY,
// FIELD_NAME_PROFILE_PICTURE,
2020-02-09 16:35:16 +00:00
sr , err := l . Search ( searchRequest )
2020-02-09 16:52:48 +00:00
if err != nil {
2020-02-09 16:35:16 +00:00
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return nil
}
if len ( sr . Entries ) != 1 {
2020-02-10 14:26:02 +00:00
http . Error ( w , fmt . Sprintf ( "Unable to find entry for %s" , login_info . DN ) , http . StatusInternalServerError )
2020-02-09 16:35:16 +00:00
return nil
}
2020-02-10 14:26:02 +00:00
loginStatus . UserEntry = sr . Entries [ 0 ]
2020-02-11 23:13:24 +00:00
loginStatus . CanAdmin = strings . EqualFold ( loginStatus . Info . DN , config . AdminAccount )
loginStatus . CanInvite = false
2023-07-20 08:21:51 +00:00
groups := [ ] EntryName { }
searchRequest = ldap . NewSearchRequest (
config . GroupBaseDN ,
ldap . ScopeWholeSubtree , ldap . NeverDerefAliases , 0 , 0 , false ,
fmt . Sprintf ( "(&(objectClass=groupOfNames)(member=%s))" , login_info . DN ) ,
[ ] string { "dn" , "displayName" , "cn" , "description" } ,
nil )
// // log.Printf(fmt.Sprintf("708: %v",searchRequest))
sr , err = l . Search ( searchRequest )
// if err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// return
// }
//// log.Printf(fmt.Sprintf("303: %v",sr.Entries))
for _ , ent := range sr . Entries {
// log.Printf(fmt.Sprintf("305: %v",ent.DN))
groups = append ( groups , EntryName {
DN : ent . DN ,
Name : ent . GetAttributeValue ( "cn" ) ,
} )
// log.Printf(fmt.Sprintf("310: %v",config.GroupCanInvite))
if config . GroupCanInvite != "" && strings . EqualFold ( ent . DN , config . GroupCanInvite ) {
loginStatus . CanInvite = true
}
// log.Printf(fmt.Sprintf("314: %v",config.GroupCanAdmin))
if config . GroupCanAdmin != "" && strings . EqualFold ( ent . DN , config . GroupCanAdmin ) {
loginStatus . CanAdmin = true
2020-02-11 23:13:24 +00:00
}
}
2023-07-20 08:21:51 +00:00
// for _, attr := range loginStatus.UserEntry.Attributes {
// if strings.EqualFold(attr.Name, "memberof") {
// for _, group := range attr.Values {
// if config.GroupCanInvite != "" && strings.EqualFold(group, config.GroupCanInvite) {
// loginStatus.CanInvite = true
// }
// if config.GroupCanAdmin != "" && strings.EqualFold(group, config.GroupCanAdmin) {
// loginStatus.CanAdmin = true
// }
// }
// }
// }
2020-02-10 14:26:02 +00:00
return loginStatus
2020-02-09 14:44:18 +00:00
}
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
}
2020-02-09 14:01:20 +00:00
// Page handlers ----
2020-02-09 16:52:48 +00:00
type HomePageData struct {
2020-02-14 20:58:34 +00:00
Login * LoginStatus
BaseDN string
2023-07-18 14:09:10 +00:00
Org 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
2020-02-09 14:44:18 +00:00
login := checkLogin ( w , r )
if login == nil {
return
}
2020-02-10 14:26:02 +00:00
data := & HomePageData {
2020-02-14 20:58:34 +00:00
Login : login ,
BaseDN : config . BaseDN ,
2023-07-18 13:59:46 +00:00
Org : config . Org ,
2020-02-10 14:26:02 +00:00
}
templateHome . Execute ( w , data )
2020-02-09 14:44:18 +00:00
}
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
}
http . Redirect ( w , r , "/" , http . StatusFound )
}
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
}
2020-02-09 14:44:18 +00:00
func handleLogin ( w http . ResponseWriter , r * http . Request ) * LoginInfo {
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 { } )
return nil
} 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" ] , "" )
2020-02-09 17:03:10 +00:00
user_dn := fmt . Sprintf ( "%s=%s,%s" , config . UserNameAttr , username , config . UserBaseDN )
2020-02-11 23:13:24 +00:00
if strings . EqualFold ( username , config . AdminAccount ) {
2020-02-10 14:26:02 +00:00
user_dn = username
}
2020-02-09 14:44:18 +00:00
l := ldapOpen ( w )
if l == nil {
return nil
}
2020-02-09 15:46:26 +00:00
err := l . Bind ( user_dn , 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 )
2020-02-09 14:44:18 +00:00
return nil
}
// 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
session . Values [ "login_dn" ] = user_dn
err = session . Save ( r , w )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return nil
}
return & LoginInfo {
DN : user_dn ,
Username : username ,
Password : password ,
}
2020-02-09 14:44:18 +00:00
} else {
http . Error ( w , "Unsupported method" , http . StatusBadRequest )
return nil
}
2020-02-09 14:01:20 +00:00
}
2023-07-20 08:21:51 +00:00