It Compiles !
This commit is contained in:
parent
40ecbe2cfb
commit
23c77e0e23
19 changed files with 3892 additions and 0 deletions
73
controllers/controller.go
Normal file
73
controllers/controller.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
Routes the requests to the app
|
||||
*/
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"guichet/models"
|
||||
"guichet/views"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
|
||||
var staticPath = "./static"
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Create the different routes
|
||||
*/
|
||||
func MakeGVRouter() (*mux.Router, error) {
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/", views.HandleHome)
|
||||
|
||||
r.HandleFunc("/session/logout", views.HandleLogout)
|
||||
|
||||
r.HandleFunc("/user", views.HandleUser)
|
||||
r.HandleFunc("/user/new", views.HandleInviteNewAccount)
|
||||
r.HandleFunc("/user/new/", views.HandleInviteNewAccount)
|
||||
r.HandleFunc("/user/wait", views.HandleUserWait)
|
||||
r.HandleFunc("/user/mail", views.HandleUserMail)
|
||||
|
||||
r.HandleFunc("/picture/{name}", views.HandleDownloadPicture)
|
||||
|
||||
r.HandleFunc("/passwd", views.HandlePasswd)
|
||||
r.HandleFunc("/passwd/lost", views.HandleLostPassword)
|
||||
r.HandleFunc("/passwd/lost/{code}", views.HandleFoundPassword)
|
||||
|
||||
r.HandleFunc("/admin", views.HandleHome)
|
||||
r.HandleFunc("/admin/activate", views.HandleAdminActivateUsers)
|
||||
r.HandleFunc("/admin/unactivate/{cn}", views.HandleAdminUnactivateUser)
|
||||
r.HandleFunc("/admin/activate/{cn}", views.HandleAdminActivateUser)
|
||||
r.HandleFunc("/admin/users", views.HandleAdminUsers)
|
||||
r.HandleFunc("/admin/groups", views.HandleAdminGroups)
|
||||
r.HandleFunc("/admin/ldap/{dn}", views.HandleAdminLDAP)
|
||||
r.HandleFunc("/admin/create/{template}/{super_dn}", views.HandleAdminCreate)
|
||||
|
||||
// r.HandleFunc("/directory/search", views.HandleDirectorySearch)
|
||||
// r.HandleFunc("/directory", views.HandleDirectory)
|
||||
// r.HandleFunc("/garage/key", views.HandleGarageKey)
|
||||
// r.HandleFunc("/garage/website", views.HandleGarageWebsiteList)
|
||||
// r.HandleFunc("/garage/website/new", views.HandleGarageWebsiteNew)
|
||||
// r.HandleFunc("/garage/website/b/{bucket}", views.HandleGarageWebsiteInspect)
|
||||
|
||||
// r.HandleFunc("/user/send_code", views.HandleInviteSendCode)
|
||||
|
||||
// r.HandleFunc("/invitation/{code}", views.HandleInvitationCode)
|
||||
|
||||
// r.HandleFunc("/admin-mailing", views.HandleAdminMailing)
|
||||
// r.HandleFunc("/admin/mailing/{id}", views.HandleAdminMailingList)
|
||||
|
||||
staticFiles := http.FileServer(http.Dir(staticPath))
|
||||
r.Handle("/static/{file:.*}", http.StripPrefix("/static/", staticFiles))
|
||||
config_file := models.ReadConfig()
|
||||
|
||||
// log.Printf("Starting HTTP server on %s", config.HttpBindAddr)
|
||||
err := http.ListenAndServe(config_file.HttpBindAddr, views.LogRequest(r))
|
||||
|
||||
return r, err
|
||||
}
|
||||
|
||||
|
239
garage.notgo
Normal file
239
garage.notgo
Normal file
|
@ -0,0 +1,239 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var config = ReadConfig()
|
||||
|
||||
func gadmin() (*garage.APIClient, context.Context) {
|
||||
// Set Host and other parameters
|
||||
configuration := garage.NewConfiguration()
|
||||
configuration.Host = config.S3AdminEndpoint
|
||||
|
||||
// We can now generate a client
|
||||
client := garage.NewAPIClient(configuration)
|
||||
|
||||
// Authentication is handled through the context pattern
|
||||
ctx := context.WithValue(context.Background(), garage.ContextAccessToken, config.S3AdminToken)
|
||||
return client, ctx
|
||||
}
|
||||
|
||||
func grgCreateKey(name string) (*garage.KeyInfo, error) {
|
||||
client, ctx := gadmin()
|
||||
|
||||
kr := garage.AddKeyRequest{Name: &name}
|
||||
resp, _, err := client.KeyApi.AddKey(ctx).AddKeyRequest(kr).Execute()
|
||||
if err != nil {
|
||||
fmt.Printf("%+v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func grgGetKey(accessKey string) (*garage.KeyInfo, error) {
|
||||
client, ctx := gadmin()
|
||||
|
||||
resp, _, err := client.KeyApi.GetKey(ctx, accessKey).Execute()
|
||||
if err != nil {
|
||||
fmt.Printf("%+v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func grgCreateWebsite(gkey, bucket string) (*garage.BucketInfo, error) {
|
||||
client, ctx := gadmin()
|
||||
|
||||
br := garage.NewCreateBucketRequest()
|
||||
br.SetGlobalAlias(bucket)
|
||||
|
||||
// Create Bucket
|
||||
binfo, _, err := client.BucketApi.CreateBucket(ctx).CreateBucketRequest(*br).Execute()
|
||||
if err != nil {
|
||||
fmt.Printf("%+v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Allow user's key
|
||||
ar := garage.AllowBucketKeyRequest{
|
||||
BucketId: *binfo.Id,
|
||||
AccessKeyId: gkey,
|
||||
Permissions: *garage.NewAllowBucketKeyRequestPermissions(true, true, true),
|
||||
}
|
||||
binfo, _, err = client.BucketApi.AllowBucketKey(ctx).AllowBucketKeyRequest(ar).Execute()
|
||||
if err != nil {
|
||||
fmt.Printf("%+v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Expose website and set quota
|
||||
wr := garage.NewUpdateBucketRequestWebsiteAccess()
|
||||
wr.SetEnabled(true)
|
||||
wr.SetIndexDocument("index.html")
|
||||
wr.SetErrorDocument("error.html")
|
||||
|
||||
qr := garage.NewUpdateBucketRequestQuotas()
|
||||
qr.SetMaxSize(1024 * 1024 * 50) // 50MB
|
||||
qr.SetMaxObjects(10000) //10k objects
|
||||
|
||||
ur := garage.NewUpdateBucketRequest()
|
||||
ur.SetWebsiteAccess(*wr)
|
||||
ur.SetQuotas(*qr)
|
||||
|
||||
binfo, _, err = client.BucketApi.UpdateBucket(ctx, *binfo.Id).UpdateBucketRequest(*ur).Execute()
|
||||
if err != nil {
|
||||
fmt.Printf("%+v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return updated binfo
|
||||
return binfo, nil
|
||||
}
|
||||
|
||||
func grgGetBucket(bid string) (*garage.BucketInfo, error) {
|
||||
client, ctx := gadmin()
|
||||
|
||||
resp, _, err := client.BucketApi.GetBucketInfo(ctx, bid).Execute()
|
||||
if err != nil {
|
||||
fmt.Printf("%+v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
|
||||
}
|
||||
|
||||
func checkLoginAndS3(w http.ResponseWriter, r *http.Request) (*LoginStatus, *garage.KeyInfo, error) {
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
return nil, nil, errors.New("LDAP login failed")
|
||||
}
|
||||
|
||||
keyID := login.UserEntry.GetAttributeValue("garage_s3_access_key")
|
||||
if keyID == "" {
|
||||
keyPair, err := grgCreateKey(login.Info.Username)
|
||||
if err != nil {
|
||||
return login, nil, err
|
||||
}
|
||||
modify_request := ldap.NewModifyRequest(login.Info.DN, nil)
|
||||
modify_request.Replace("garage_s3_access_key", []string{*keyPair.AccessKeyId})
|
||||
// @FIXME compatibility feature for bagage (SFTP+webdav)
|
||||
// you can remove it once bagage will be updated to fetch the key from garage directly
|
||||
// or when bottin will be able to dynamically fetch it.
|
||||
modify_request.Replace("garage_s3_secret_key", []string{*keyPair.SecretAccessKey})
|
||||
err = login.conn.Modify(modify_request)
|
||||
return login, keyPair, err
|
||||
}
|
||||
// Note: we could simply return the login info, but LX asked we do not
|
||||
// store the secrets in LDAP in the future.
|
||||
keyPair, err := grgGetKey(keyID)
|
||||
return login, keyPair, err
|
||||
}
|
||||
|
||||
|
||||
|
||||
func HandleGarageKey(w http.ResponseWriter, r *http.Request) {
|
||||
login, s3key, err := checkLoginAndS3(w, r)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
view := keyView{Status: login, Key: s3key}
|
||||
|
||||
tKey := getTemplate("garage/key.html")
|
||||
tKey.Execute(w, &view)
|
||||
}
|
||||
|
||||
|
||||
|
||||
func HandleGarageWebsiteList(w http.ResponseWriter, r *http.Request) {
|
||||
login, s3key, err := checkLoginAndS3(w, r)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
view := webListView{Status: login, Key: s3key}
|
||||
|
||||
tWebsiteList := getTemplate("garage/website/list.html")
|
||||
tWebsiteList.Execute(w, &view)
|
||||
}
|
||||
|
||||
func HandleGarageWebsiteNew(w http.ResponseWriter, r *http.Request) {
|
||||
_, s3key, err := checkLoginAndS3(w, r)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
tWebsiteNew := getTemplate("garage/website/new.html")
|
||||
if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
log.Println(r.Form)
|
||||
|
||||
bucket := strings.Join(r.Form["bucket"], "")
|
||||
if bucket == "" {
|
||||
bucket = strings.Join(r.Form["bucket2"], "")
|
||||
}
|
||||
if bucket == "" {
|
||||
log.Println("Form empty")
|
||||
// @FIXME we need to return the error to the user
|
||||
tWebsiteNew.Execute(w, nil)
|
||||
return
|
||||
}
|
||||
|
||||
binfo, err := grgCreateWebsite(*s3key.AccessKeyId, bucket)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
// @FIXME we need to return the error to the user
|
||||
tWebsiteNew.Execute(w, nil)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/garage/website/b/"+*binfo.Id, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
tWebsiteNew.Execute(w, nil)
|
||||
}
|
||||
|
||||
|
||||
|
||||
func HandleGarageWebsiteInspect(w http.ResponseWriter, r *http.Request) {
|
||||
login, s3key, err := checkLoginAndS3(w, r)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
bucketId := mux.Vars(r)["bucket"]
|
||||
binfo, err := grgGetBucket(bucketId)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
wc := binfo.GetWebsiteConfig()
|
||||
q := binfo.GetQuotas()
|
||||
|
||||
view := webInspectView{
|
||||
Status: login,
|
||||
Key: s3key,
|
||||
Bucket: binfo,
|
||||
IndexDoc: (&wc).GetIndexDocument(),
|
||||
ErrorDoc: (&wc).GetErrorDocument(),
|
||||
MaxObjects: (&q).GetMaxObjects(),
|
||||
MaxSize: (&q).GetMaxSize(),
|
||||
}
|
||||
|
||||
tWebsiteInspect := getTemplate("garage/website/inspect.html")
|
||||
tWebsiteInspect.Execute(w, &view)
|
||||
}
|
52
models/config.go
Normal file
52
models/config.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
config Handles reading the config.json file at the root and processing the settings
|
||||
*/
|
||||
package models
|
||||
|
||||
type ConfigFile struct {
|
||||
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"`
|
||||
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"`
|
||||
|
||||
InvitationBaseDN string `json:"invitation_base_dn"`
|
||||
InvitationNameAttr string `json:"invitation_name_attr"`
|
||||
InvitedMailFormat string `json:"invited_mail_format"`
|
||||
InvitedAutoGroups []string `json:"invited_auto_groups"`
|
||||
|
||||
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"`
|
||||
|
||||
AdminAccount string `json:"admin_account"`
|
||||
GroupCanInvite string `json:"group_can_invite"`
|
||||
GroupCanAdmin string `json:"group_can_admin"`
|
||||
|
||||
S3AdminEndpoint string `json:"s3_admin_endpoint"`
|
||||
S3AdminToken string `json:"s3_admin_token"`
|
||||
|
||||
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"`
|
||||
|
||||
Org string `json:"org"`
|
||||
DomainName string `json:"domain_name"`
|
||||
NewUserDN string `json:"new_user_dn"`
|
||||
NewUserPassword string `json:"new_user_password"`
|
||||
NewUsersBaseDN string `json:"new_users_base_dn"`
|
||||
NewUserDefaultDomain string `json:"new_user_default_domain"`
|
||||
}
|
||||
|
118
models/ldap.go
Normal file
118
models/ldap.go
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
Utilities related to LDAP
|
||||
*/
|
||||
package models
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
var config = ReadConfig()
|
||||
const FIELD_NAME_PROFILE_PICTURE = "profilePicture"
|
||||
const FIELD_NAME_DIRECTORY_VISIBILITY = "directoryVisibility"
|
||||
|
||||
type SearchResult struct {
|
||||
DN string
|
||||
Id string
|
||||
DisplayName string
|
||||
Email string
|
||||
Description string
|
||||
ProfilePicture string
|
||||
}
|
||||
type SearchResults struct {
|
||||
Results []SearchResult
|
||||
}
|
||||
|
||||
|
||||
func (r *SearchResults) Len() int {
|
||||
return len(r.Results)
|
||||
}
|
||||
|
||||
func (r *SearchResults) Less(i, j int) bool {
|
||||
return r.Results[i].Id < r.Results[j].Id
|
||||
}
|
||||
|
||||
func (r *SearchResults) Swap(i, j int) {
|
||||
r.Results[i], r.Results[j] = r.Results[j], r.Results[i]
|
||||
}
|
||||
|
||||
|
||||
func ContainsI(a string, b string) bool {
|
||||
return strings.Contains(
|
||||
strings.ToLower(a),
|
||||
strings.ToLower(b),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// New account creation directly from interface
|
||||
|
||||
|
||||
func OpenNewUserLdap(config *ConfigFile) (*ldap.Conn, error) {
|
||||
l, err := openLdap(config)
|
||||
if err != nil {
|
||||
log.Printf("OpenNewUserLdap 1 : %v %v", err, l)
|
||||
log.Printf("OpenNewUserLdap 1 : %v", config)
|
||||
// data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
err = l.Bind(config.NewUserDN, config.NewUserPassword)
|
||||
if err != nil {
|
||||
log.Printf("OpenNewUserLdap Bind : %v", err)
|
||||
log.Printf("OpenNewUserLdap Bind : %v", config.NewUserDN)
|
||||
log.Printf("OpenNewUserLdap Bind : %v", config.NewUserPassword)
|
||||
log.Printf("OpenNewUserLdap Bind : %v", config)
|
||||
// data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
return l, err
|
||||
}
|
||||
|
||||
func DoDirectorySearch(ldapConn *ldap.Conn, input string) (SearchResults, error) {
|
||||
//Search values with ldap and filter
|
||||
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
config.UserBaseDN,
|
||||
ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false,
|
||||
"(&(objectclass=organizationalPerson)("+FIELD_NAME_DIRECTORY_VISIBILITY+"=on))",
|
||||
[]string{
|
||||
config.UserNameAttr,
|
||||
"displayname",
|
||||
"mail",
|
||||
"description",
|
||||
FIELD_NAME_PROFILE_PICTURE,
|
||||
},
|
||||
nil)
|
||||
//Transform the researh's result in a correct struct to send JSON
|
||||
results := []SearchResult{}
|
||||
sr, err := ldapConn.Search(searchRequest)
|
||||
if err != nil {
|
||||
return SearchResults{}, err
|
||||
}
|
||||
|
||||
|
||||
|
||||
for _, values := range sr.Entries {
|
||||
if input == "" ||
|
||||
ContainsI(values.GetAttributeValue(config.UserNameAttr), input) ||
|
||||
ContainsI(values.GetAttributeValue("displayname"), input) ||
|
||||
ContainsI(values.GetAttributeValue("mail"), input) {
|
||||
results = append(results, SearchResult{
|
||||
DN: values.DN,
|
||||
Id: values.GetAttributeValue(config.UserNameAttr),
|
||||
DisplayName: values.GetAttributeValue("displayname"),
|
||||
Email: values.GetAttributeValue("mail"),
|
||||
Description: values.GetAttributeValue("description"),
|
||||
ProfilePicture: values.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE),
|
||||
})
|
||||
}
|
||||
}
|
||||
// sort.Sort(&results)
|
||||
search_results := SearchResults{
|
||||
Results: results,
|
||||
}
|
||||
sort.Sort(&search_results)
|
||||
return search_results, nil
|
||||
}
|
67
models/model.go
Normal file
67
models/model.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
Centralises the models used in this application
|
||||
*/
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
// "crypto/tls"
|
||||
// "log"
|
||||
// "net"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
|
||||
/*
|
||||
Represents a user
|
||||
*/
|
||||
type User struct {
|
||||
DN string
|
||||
CN string
|
||||
GivenName string
|
||||
DisplayName string
|
||||
Mail string
|
||||
SN string
|
||||
UID string
|
||||
Description string
|
||||
Password string
|
||||
OtherMailbox string
|
||||
CanAdmin bool
|
||||
CanInvite bool
|
||||
UserEntry *ldap.Entry
|
||||
SeeAlso string
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// func openLdap(config *ConfigFile) (*ldap.Conn, error) {
|
||||
// var ldapConn *ldap.Conn
|
||||
// var err error
|
||||
// if config.LdapTLS {
|
||||
// tlsConf := &tls.Config{
|
||||
// ServerName: config.LdapServerAddr,
|
||||
// InsecureSkipVerify: true,
|
||||
// }
|
||||
// ldapConn, err = ldap.DialTLS("tcp", net.JoinHostPort(config.LdapServerAddr, "636"), tlsConf)
|
||||
// } else {
|
||||
// ldapConn, err = ldap.DialURL("ldap://" + config.LdapServerAddr)
|
||||
// }
|
||||
// if err != nil {
|
||||
// log.Printf("openLDAP %v", err)
|
||||
// log.Printf("openLDAP %v", config.LdapServerAddr)
|
||||
// }
|
||||
// return ldapConn, err
|
||||
|
||||
// // l, err := ldap.DialURL(config.LdapServerAddr)
|
||||
// // if err != nil {
|
||||
// // log.Printf(fmt.Sprint("Erreur connect LDAP %v", err))
|
||||
// // log.Printf(fmt.Sprint("Erreur connect LDAP %v", config.LdapServerAddr))
|
||||
// // return nil
|
||||
// // } else {
|
||||
// // return l
|
||||
// // }
|
||||
// }
|
135
models/modelutils.go
Normal file
135
models/modelutils.go
Normal file
|
@ -0,0 +1,135 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net"
|
||||
"net/smtp"
|
||||
|
||||
"html/template"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
// "golang.org/x/text/encoding/unicode"
|
||||
"encoding/json"
|
||||
|
||||
"io/ioutil"
|
||||
|
||||
"os"
|
||||
|
||||
"flag"
|
||||
)
|
||||
|
||||
//
|
||||
|
||||
func ReadConfig() ConfigFile {
|
||||
// Default configuration values for certain fields
|
||||
flag.Parse()
|
||||
var configFlag = flag.String("config", "./config.json", "Configuration file path")
|
||||
|
||||
config_file := ConfigFile{
|
||||
HttpBindAddr: ":9991",
|
||||
LdapServerAddr: "ldap://127.0.0.1:389",
|
||||
|
||||
UserNameAttr: "uid",
|
||||
GroupNameAttr: "gid",
|
||||
|
||||
InvitationNameAttr: "cn",
|
||||
InvitedAutoGroups: []string{},
|
||||
|
||||
Org: "ResDigita",
|
||||
}
|
||||
|
||||
_, err := os.Stat(*configFlag)
|
||||
if os.IsNotExist(err) {
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
type EmailContentVarsTplData struct {
|
||||
Code string
|
||||
SendAddress string
|
||||
InviteFrom string
|
||||
}
|
||||
|
||||
// Data to be passed to an email for sending
|
||||
type SendMailTplData struct {
|
||||
// Sender of the email
|
||||
To string
|
||||
// Receiver of the email
|
||||
From string
|
||||
// Relative path (without leading /) to the email template in the templates folder
|
||||
// usually ending in .txt
|
||||
RelTemplatePath string
|
||||
// Variables to be included in the template of the email
|
||||
EmailContentVars EmailContentVarsTplData
|
||||
}
|
||||
|
||||
|
||||
|
||||
func openLdap(config *ConfigFile) (*ldap.Conn, error) {
|
||||
var ldapConn *ldap.Conn
|
||||
var err error
|
||||
if config.LdapTLS {
|
||||
tlsConf := &tls.Config{
|
||||
ServerName: config.LdapServerAddr,
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
ldapConn, err = ldap.DialTLS("tcp", net.JoinHostPort(config.LdapServerAddr, "636"), tlsConf)
|
||||
} else {
|
||||
ldapConn, err = ldap.DialURL("ldap://" + config.LdapServerAddr)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("openLDAP %v", err)
|
||||
log.Printf("openLDAP %v", config.LdapServerAddr)
|
||||
}
|
||||
return ldapConn, err
|
||||
|
||||
// l, err := ldap.DialURL(config.LdapServerAddr)
|
||||
// if err != nil {
|
||||
// log.Printf(fmt.Sprint("Erreur connect LDAP %v", err))
|
||||
// log.Printf(fmt.Sprint("Erreur connect LDAP %v", config.LdapServerAddr))
|
||||
// return nil
|
||||
// } else {
|
||||
// return l
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Sends an email according to the enclosed information
|
||||
func sendMail(sendMailTplData SendMailTplData) error {
|
||||
log.Printf("sendMail")
|
||||
templateMail := template.Must(template.ParseFiles( "./templates" + sendMailTplData.RelTemplatePath))
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err := templateMail.Execute(buf, sendMailTplData)
|
||||
message := buf.Bytes()
|
||||
auth := smtp.PlainAuth("", config.SMTPUsername, config.SMTPPassword, config.SMTPServer)
|
||||
log.Printf("auth: %v", auth)
|
||||
err = smtp.SendMail(config.SMTPServer+":587", auth, config.SMTPUsername, []string{sendMailTplData.To}, message)
|
||||
if err != nil {
|
||||
log.Printf("sendMail smtp.SendMail %v", err)
|
||||
log.Printf("sendMail smtp.SendMail %v", sendMailTplData)
|
||||
return err
|
||||
}
|
||||
log.Printf("Mail sent.")
|
||||
return err
|
||||
}
|
222
models/passwd.go
Normal file
222
models/passwd.go
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
gpas is GVoisin password reset
|
||||
*/
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"math/rand"
|
||||
|
||||
// "github.com/emersion/go-sasl"
|
||||
// "github.com/emersion/go-smtp"
|
||||
"net/smtp"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
|
||||
// "strings"
|
||||
b64 "encoding/base64"
|
||||
)
|
||||
|
||||
var templatePath = "./templates"
|
||||
|
||||
type CodeMailFields struct {
|
||||
From string
|
||||
To string
|
||||
Code string
|
||||
InviteFrom string
|
||||
WebBaseAddress string
|
||||
Common NestedCommonTplData
|
||||
}
|
||||
type NestedCommonTplData struct {
|
||||
Error string
|
||||
ErrorMessage string
|
||||
CanAdmin bool
|
||||
CanInvite bool
|
||||
LoggedIn bool
|
||||
Success bool
|
||||
WarningMessage string
|
||||
WebsiteName string
|
||||
WebsiteURL string
|
||||
}
|
||||
// type InvitationAccount struct {
|
||||
// UID string
|
||||
// Password string
|
||||
// BaseDN string
|
||||
// }
|
||||
|
||||
// Suggesting a 12 char password with some excentrics
|
||||
func SuggestPassword() string {
|
||||
password := ""
|
||||
chars := "abcdfghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&*+_-="
|
||||
for i := 0; i < 12; i++ {
|
||||
password += string([]rune(chars)[rand.Intn(len(chars))])
|
||||
}
|
||||
return password
|
||||
}
|
||||
|
||||
// var EMAIL_REGEXP := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||
|
||||
func getInvitationBaseDN(config *ConfigFile) string {
|
||||
return config.InvitationBaseDN
|
||||
}
|
||||
|
||||
func PasswordLost(user User, config *ConfigFile, ldapConn *ldap.Conn) error {
|
||||
if user.CN == "" && user.Mail == "" && user.OtherMailbox == "" {
|
||||
return errors.New("Il n'y a pas de quoi identifier l'utilisateur")
|
||||
}
|
||||
searchFilter := "(|"
|
||||
if user.CN != "" {
|
||||
searchFilter += "(cn=" + user.UID + ")"
|
||||
}
|
||||
if user.Mail != "" {
|
||||
searchFilter += "(mail=" + user.Mail + ")"
|
||||
}
|
||||
if user.OtherMailbox != "" {
|
||||
searchFilter += "(carLicense=" + user.OtherMailbox + ")"
|
||||
}
|
||||
searchFilter += ")"
|
||||
searchReq := ldap.NewSearchRequest(config.UserBaseDN, ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false, searchFilter, []string{"cn", "uid", "mail", "carLicense", "sn", "displayName", "givenName"}, nil)
|
||||
searchRes, err := ldapConn.Search(searchReq)
|
||||
if err != nil {
|
||||
log.Printf("PasswordLost search : %v %v", err, ldapConn)
|
||||
log.Printf("PasswordLost search : %v", searchReq)
|
||||
log.Printf("PasswordLost search : %v", searchRes)
|
||||
log.Printf("PasswordLost search: %v", user)
|
||||
return err
|
||||
}
|
||||
if len(searchRes.Entries) == 0 {
|
||||
log.Printf("Il n'y a pas d'utilisateur qui correspond %v", searchReq)
|
||||
return errors.New("Il n'y a pas d'utilisateur qui correspond")
|
||||
}
|
||||
// log.Printf("PasswordLost 58 : %v", user)
|
||||
// log.Printf("PasswordLost 59 : %v", searchRes.Entries[0])
|
||||
// log.Printf("PasswordLost 60 : %v", searchRes.Entries[0].GetAttributeValue("cn"))
|
||||
// log.Printf("PasswordLost 61 : %v", searchRes.Entries[0].GetAttributeValue("uid"))
|
||||
// log.Printf("PasswordLost 62 : %v", searchRes.Entries[0].GetAttributeValue("mail"))
|
||||
// log.Printf("PasswordLost 63 : %v", searchRes.Entries[0].GetAttributeValue("carLicense"))
|
||||
// Préparation du courriel à envoyer
|
||||
|
||||
delReq := ldap.NewDelRequest("uid="+searchRes.Entries[0].GetAttributeValue("cn")+","+config.InvitationBaseDN, nil)
|
||||
err = ldapConn.Del(delReq)
|
||||
|
||||
user.Password = SuggestPassword()
|
||||
user.DN = "uid=" + searchRes.Entries[0].GetAttributeValue("cn") + "," + config.InvitationBaseDN
|
||||
user.UID = searchRes.Entries[0].GetAttributeValue("cn")
|
||||
user.CN = searchRes.Entries[0].GetAttributeValue("cn")
|
||||
user.Mail = searchRes.Entries[0].GetAttributeValue("mail")
|
||||
user.OtherMailbox = searchRes.Entries[0].GetAttributeValue("carLicense")
|
||||
code := b64.URLEncoding.EncodeToString([]byte(user.UID + ";" + user.Password))
|
||||
/* Check for outstanding invitation */
|
||||
searchReq = ldap.NewSearchRequest(config.InvitationBaseDN, ldap.ScopeSingleLevel,
|
||||
ldap.NeverDerefAliases, 0, 0, false, "(uid="+user.UID+")", []string{"seeAlso"}, nil)
|
||||
searchRes, err = ldapConn.Search(searchReq)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("PasswordLost (Check existing invitation) : %v", err))
|
||||
log.Printf(fmt.Sprintf("PasswordLost (Check existing invitation) : %v", user))
|
||||
return err
|
||||
}
|
||||
// if len(searchRes.Entries) == 0 {
|
||||
/* Add the invitation */
|
||||
addReq := ldap.NewAddRequest(
|
||||
"uid="+user.UID+","+config.InvitationBaseDN,
|
||||
nil)
|
||||
addReq.Attribute("objectClass", []string{"top", "account", "simpleSecurityObject"})
|
||||
addReq.Attribute("uid", []string{user.UID})
|
||||
addReq.Attribute("userPassword", []string{user.Password})
|
||||
addReq.Attribute("seeAlso", []string{config.UserNameAttr + "=" + user.UID + "," + config.UserBaseDN})
|
||||
// Password invitation may already exist
|
||||
|
||||
//
|
||||
err = ldapConn.Add(addReq)
|
||||
if err != nil {
|
||||
log.Printf("PasswordLost 83 : %v", err)
|
||||
log.Printf("PasswordLost 84 : %v", user)
|
||||
|
||||
log.Printf("PasswordLost 84 : %v", addReq)
|
||||
// // log.Printf("PasswordLost 85 : %v", searchRes.Entries[0]))
|
||||
// // For some reason I get here even if the entry exists already
|
||||
// return err
|
||||
}
|
||||
// }
|
||||
ldapNewConn, err := OpenNewUserLdap(config)
|
||||
if err != nil {
|
||||
log.Printf("PasswordLost OpenNewUserLdap : %v", err)
|
||||
}
|
||||
err = PassWD(user, config, ldapNewConn)
|
||||
if err != nil {
|
||||
log.Printf("PasswordLost PassWD : %v", err)
|
||||
log.Printf("PasswordLost PassWD : %v", user)
|
||||
log.Printf("PasswordLost PassWD : %v", searchRes.Entries[0])
|
||||
return err
|
||||
}
|
||||
templateMail := template.Must(template.ParseFiles(templatePath + "/passwd/lost_password_email.txt"))
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
templateMail.Execute(buf, &CodeMailFields{
|
||||
To: user.OtherMailbox,
|
||||
From: config.MailFrom,
|
||||
InviteFrom: user.UID,
|
||||
Code: code,
|
||||
WebBaseAddress: config.WebAddress,
|
||||
})
|
||||
// message := []byte("Hi " + user.OtherMailbox)
|
||||
log.Printf("Sending mail to: %s", user.OtherMailbox)
|
||||
// var auth sasl.Client = nil
|
||||
// if config.SMTPUsername != "" {
|
||||
// auth = sasl.NewPlainClient("", config.SMTPUsername, config.SMTPPassword)
|
||||
// }
|
||||
message := buf.Bytes()
|
||||
auth := smtp.PlainAuth("", config.SMTPUsername, config.SMTPPassword, config.SMTPServer)
|
||||
log.Printf("auth: %v", auth)
|
||||
err = smtp.SendMail(config.SMTPServer+":587", auth, config.SMTPUsername, []string{user.OtherMailbox}, message)
|
||||
if err != nil {
|
||||
log.Printf("email send error %v", err)
|
||||
return err
|
||||
}
|
||||
log.Printf("Mail sent.")
|
||||
return err
|
||||
}
|
||||
|
||||
func PasswordFound(user User, config *ConfigFile, ldapConn *ldap.Conn) (string, error) {
|
||||
l, err := openLdap(config)
|
||||
if err != nil {
|
||||
log.Printf("PasswordFound openLdap %v", err)
|
||||
// log.Printf("PasswordFound openLdap Config : %v", config)
|
||||
return "", err
|
||||
}
|
||||
if user.DN == "" && user.UID != "" {
|
||||
user.DN = "uid=" + user.UID + "," + config.InvitationBaseDN
|
||||
}
|
||||
err = l.Bind(user.DN, user.Password)
|
||||
if err != nil {
|
||||
log.Printf("PasswordFound l.Bind %v", err)
|
||||
log.Printf("PasswordFound l.Bind %v", user.DN)
|
||||
log.Printf("PasswordFound l.Bind %v", user.UID)
|
||||
return "", err
|
||||
}
|
||||
searchReq := ldap.NewSearchRequest(user.DN, ldap.ScopeBaseObject,
|
||||
ldap.NeverDerefAliases, 0, 0, false, "(uid="+user.UID+")", []string{"seeAlso"}, nil)
|
||||
var searchRes *ldap.SearchResult
|
||||
searchRes, err = ldapConn.Search(searchReq)
|
||||
if err != nil {
|
||||
log.Printf("PasswordFound %v", err)
|
||||
log.Printf("PasswordFound %v", searchReq)
|
||||
log.Printf("PasswordFound %v", ldapConn)
|
||||
log.Printf("PasswordFound %v", searchRes)
|
||||
return "", err
|
||||
}
|
||||
if len(searchRes.Entries) == 0 {
|
||||
log.Printf("PasswordFound %v", err)
|
||||
log.Printf("PasswordFound %v", searchReq)
|
||||
log.Printf("PasswordFound %v", ldapConn)
|
||||
log.Printf("PasswordFound %v", searchRes)
|
||||
return "", err
|
||||
}
|
||||
delReq := ldap.NewDelRequest("uid="+user.CN+","+config.InvitationBaseDN, nil)
|
||||
ldapConn.Del(delReq)
|
||||
return searchRes.Entries[0].GetAttributeValue("seeAlso"), err
|
||||
}
|
199
models/user.go
Normal file
199
models/user.go
Normal file
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
Model-User Handles everything having to do with the user.
|
||||
*/
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
func replaceIfContent(modifReq *ldap.ModifyRequest, key string, value string, previousValue string) error {
|
||||
if value != "" {
|
||||
modifReq.Replace(key, []string{value})
|
||||
} else if previousValue != "" {
|
||||
modifReq.Delete(key, []string{previousValue})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetUser(user User, config *ConfigFile, ldapConn *ldap.Conn) (*User, error) {
|
||||
return get(user, config, ldapConn)
|
||||
}
|
||||
|
||||
func get(user User, config *ConfigFile, ldapConn *ldap.Conn) (*User, error) {
|
||||
searchReq := ldap.NewSearchRequest(
|
||||
user.DN,
|
||||
ldap.ScopeBaseObject,
|
||||
ldap.NeverDerefAliases,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
"(objectClass=*)",
|
||||
[]string{
|
||||
"cn",
|
||||
"givenName",
|
||||
"displayName",
|
||||
"uid",
|
||||
"sn",
|
||||
"mail",
|
||||
"description",
|
||||
"carLicense",
|
||||
},
|
||||
nil)
|
||||
searchRes, err := ldapConn.Search(searchReq)
|
||||
if err != nil {
|
||||
log.Printf("get User : %v", err)
|
||||
log.Printf("get User : %v", searchReq)
|
||||
log.Printf("get User : %v", searchRes)
|
||||
return nil, err
|
||||
}
|
||||
userEntry := searchRes.Entries[0]
|
||||
resUser := User{
|
||||
DN: user.DN,
|
||||
GivenName: searchRes.Entries[0].GetAttributeValue("givenName"),
|
||||
DisplayName: searchRes.Entries[0].GetAttributeValue("displayName"),
|
||||
Description: searchRes.Entries[0].GetAttributeValue("description"),
|
||||
SN: searchRes.Entries[0].GetAttributeValue("sn"),
|
||||
UID: searchRes.Entries[0].GetAttributeValue("uid"),
|
||||
CN: searchRes.Entries[0].GetAttributeValue("cn"),
|
||||
Mail: searchRes.Entries[0].GetAttributeValue("mail"),
|
||||
OtherMailbox: searchRes.Entries[0].GetAttributeValue("carLicense"),
|
||||
CanAdmin: strings.EqualFold(user.DN, config.AdminAccount),
|
||||
CanInvite: true,
|
||||
UserEntry: userEntry,
|
||||
}
|
||||
searchReq.BaseDN = config.GroupCanAdmin
|
||||
searchReq.Filter = "(member=" + user.DN + ")"
|
||||
searchRes, err = ldapConn.Search(searchReq)
|
||||
if err == nil {
|
||||
if len(searchRes.Entries) > 0 {
|
||||
resUser.CanAdmin = true
|
||||
}
|
||||
}
|
||||
return &resUser, nil
|
||||
}
|
||||
|
||||
func AddUser(user User, config *ConfigFile, ldapConn *ldap.Conn) error {
|
||||
return add(user, config, ldapConn)
|
||||
}
|
||||
|
||||
// Adds a new user
|
||||
func add(user User, config *ConfigFile, ldapConn *ldap.Conn) error {
|
||||
log.Printf(fmt.Sprint("Adding New User"))
|
||||
// LDAP Add Object
|
||||
dn := user.DN
|
||||
req := ldap.NewAddRequest(dn, nil)
|
||||
req.Attribute("objectClass", []string{"top", "person", "organizationalPerson", "inetOrgPerson"})
|
||||
if user.DisplayName != "" {
|
||||
req.Attribute("displayName", []string{user.DisplayName})
|
||||
}
|
||||
if user.GivenName != "" {
|
||||
req.Attribute("givenName", []string{user.GivenName})
|
||||
}
|
||||
if user.Mail != "" {
|
||||
req.Attribute("mail", []string{user.Mail})
|
||||
}
|
||||
if user.UID != "" {
|
||||
req.Attribute("uid", []string{user.UID})
|
||||
}
|
||||
// if user.Member != "" {
|
||||
// req.Attribute("member", []string{user.Member})
|
||||
// }
|
||||
if user.SN != "" {
|
||||
req.Attribute("sn", []string{user.SN})
|
||||
}
|
||||
if user.OtherMailbox != "" {
|
||||
req.Attribute("carLicense", []string{user.OtherMailbox})
|
||||
}
|
||||
if user.Description != "" {
|
||||
req.Attribute("description", []string{user.Description})
|
||||
}
|
||||
// Add the User
|
||||
// err := ldapConn.Add(req)
|
||||
// var ldapNewConn *ldap.Conn
|
||||
ldapNewConn, err := OpenNewUserLdap(config)
|
||||
err = ldapNewConn.Add(req)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("add(User) ldapconn.Add: %v", err))
|
||||
log.Printf(fmt.Sprintf("add(User) ldapconn.Add: %v", req))
|
||||
log.Printf(fmt.Sprintf("add(User) ldapconn.Add: %v", user))
|
||||
//return err
|
||||
}
|
||||
// passwordModifyRequest := ldap.NewPasswordModifyRequest(user.DN, "", user.Password)
|
||||
// _, err = ldapConn.PasswordModify(passwordModifyRequest)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// Send the email
|
||||
|
||||
newUserLdapConn, _ := OpenNewUserLdap(config)
|
||||
user.OtherMailbox = ""
|
||||
err = PasswordLost(user, config, newUserLdapConn)
|
||||
if err != nil {
|
||||
log.Printf("add User PasswordLost %v", err)
|
||||
log.Printf("add User PasswordLost %v", newUserLdapConn)
|
||||
}
|
||||
|
||||
// sendMailTplData := SendMailTplData{
|
||||
// From: "alice@resdigita.org",
|
||||
// To: user.OtherMailbox,
|
||||
// RelTemplatePath: "user/new.email.txt",
|
||||
// EmailContentVars: EmailContentVarsTplData{
|
||||
// InviteFrom: "alice@resdigita.org",
|
||||
// SendAddress: "https://www.gvoisins.org",
|
||||
// Code: "...",
|
||||
// },
|
||||
// }
|
||||
// err = sendMail(sendMailTplData)
|
||||
// if err != nil {
|
||||
// log.Printf("add(user) sendMail: %v", err)
|
||||
// log.Printf("add(user) sendMail: %v", user)
|
||||
// log.Printf("add(user) sendMail: %v", sendMailTplData)
|
||||
// }
|
||||
return err
|
||||
}
|
||||
|
||||
func ModifyUser(user User, config *ConfigFile, ldapConn *ldap.Conn) error {
|
||||
return modify(user, config, ldapConn)
|
||||
}
|
||||
|
||||
func modify(user User, config *ConfigFile, ldapConn *ldap.Conn) error {
|
||||
modify_request := ldap.NewModifyRequest(user.DN, nil)
|
||||
previousUser, err := get(user, config, ldapConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
replaceIfContent(modify_request, "displayName", user.DisplayName, previousUser.DisplayName)
|
||||
replaceIfContent(modify_request, "givenName", user.GivenName, previousUser.GivenName)
|
||||
replaceIfContent(modify_request, "sn", user.SN, previousUser.SN)
|
||||
replaceIfContent(modify_request, "carLicense", user.OtherMailbox, user.OtherMailbox)
|
||||
replaceIfContent(modify_request, "description", user.Description, previousUser.Description)
|
||||
err = ldapConn.Modify(modify_request)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("71: %v", err))
|
||||
log.Printf(fmt.Sprintf("72: %v", modify_request))
|
||||
log.Printf(fmt.Sprintf("73: %v", user))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func PassWD(user User, config *ConfigFile, ldapConn *ldap.Conn) error {
|
||||
passwordModifyRequest := ldap.NewPasswordModifyRequest(user.DN, "", user.Password)
|
||||
_, err := ldapConn.PasswordModify(passwordModifyRequest)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("model-user PassWD : %v %v", err, ldapConn))
|
||||
log.Printf(fmt.Sprintf("model-user PassWD : %v", user))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func Bind(user User, config *ConfigFile, ldapConn *ldap.Conn) error {
|
||||
return ldapConn.Bind(user.DN, user.Password)
|
||||
}
|
||||
|
1029
views/admin.go
Normal file
1029
views/admin.go
Normal file
File diff suppressed because it is too large
Load diff
58
views/directory.go
Normal file
58
views/directory.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"guichet/models"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
// "sort"
|
||||
"strings"
|
||||
// "golang.org/x/crypto/openpgp/errors"
|
||||
// "honnef.co/go/tools/analysis/facts/nilness"
|
||||
// "github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
const FIELD_NAME_PROFILE_PICTURE = "profilePicture"
|
||||
const FIELD_NAME_DIRECTORY_VISIBILITY = "directoryVisibility"
|
||||
|
||||
func HandleDirectory(w http.ResponseWriter, r *http.Request) {
|
||||
templateDirectory := getTemplate("directory.html")
|
||||
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
return
|
||||
}
|
||||
|
||||
templateDirectory.Execute(w, nil)
|
||||
}
|
||||
|
||||
func HandleDirectorySearch(w http.ResponseWriter, r *http.Request) error {
|
||||
templateDirectoryResults := template.Must(template.ParseFiles(templatePath + "/directory_results.html"))
|
||||
|
||||
//Get input value by user
|
||||
r.ParseMultipartForm(1024)
|
||||
input := strings.TrimSpace(strings.Join(r.Form["query"], ""))
|
||||
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
//Log to allow the research
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
http.Error(w, "Login required", http.StatusUnauthorized)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
search_results, err := models.DoDirectorySearch(login.conn, input)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
templateDirectoryResults.Execute(w, search_results)
|
||||
return nil
|
||||
|
||||
}
|
43
views/home.go
Normal file
43
views/home.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
home show the home page
|
||||
*/
|
||||
|
||||
package views
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func HandleHome(w http.ResponseWriter, r *http.Request) {
|
||||
templateHome := getTemplate("home.html")
|
||||
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
status, _ := HandleLogin(w, r)
|
||||
if status == nil {
|
||||
return
|
||||
}
|
||||
login = checkLogin(w, r)
|
||||
}
|
||||
|
||||
can_admin := false
|
||||
if login != nil {
|
||||
can_admin = login.Common.CanAdmin
|
||||
}
|
||||
|
||||
data := HomePageData{
|
||||
Login: NestedLoginTplData{
|
||||
Login: login,
|
||||
},
|
||||
BaseDN: config.BaseDN,
|
||||
Org: config.Org,
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: can_admin,
|
||||
CanInvite: true,
|
||||
LoggedIn: true,
|
||||
},
|
||||
}
|
||||
execTemplate(w, templateHome, data.Common, data.Login, data)
|
||||
// templateHome.Execute(w, data)
|
||||
|
||||
}
|
18
views/http.go
Normal file
18
views/http.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
http-utils provide utility functions that interact with http
|
||||
*/
|
||||
|
||||
package views
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
|
430
views/invite.go
Normal file
430
views/invite.go
Normal file
|
@ -0,0 +1,430 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"guichet/models"
|
||||
"guichet/utils"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
// "crypto/rand"
|
||||
|
||||
// "github.com/emersion/go-smtp"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/crypto/argon2"
|
||||
)
|
||||
|
||||
var EMAIL_REGEXP = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||
|
||||
func checkInviterLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
|
||||
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// if !login.CanInvite {
|
||||
// http.Error(w, "Not authorized to invite new users.", http.StatusUnauthorized)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
return login
|
||||
}
|
||||
|
||||
|
||||
|
||||
func HandleInviteNewAccount(w http.ResponseWriter, r *http.Request) {
|
||||
l, err := ldapOpen(w)
|
||||
if err != nil {
|
||||
log.Printf("view-invite.go - HandleInviteNewAccount - ldapOpen : %v", err)
|
||||
log.Printf("view-invite.go - HandleInviteNewAccount - ldapOpen: %v", l)
|
||||
}
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = l.Bind(config.NewUserDN, config.NewUserPassword)
|
||||
if err != nil {
|
||||
log.Printf("view-invite.go - HandleInviteNewAccount - l.Bind : %v", err)
|
||||
log.Printf("view-invite.go - HandleInviteNewAccount - l.Bind: %v", config.NewUserDN)
|
||||
panic(fmt.Sprintf("view-invite.go - HandleInviteNewAccount - l.Bind : %v", err))
|
||||
}
|
||||
HandleNewAccount(w, r, l, config.NewUserDN)
|
||||
}
|
||||
|
||||
// New account creation using code
|
||||
func HandleInvitationCode(w http.ResponseWriter, r *http.Request) {
|
||||
code := mux.Vars(r)["code"]
|
||||
code_id, code_pw := readCode(code)
|
||||
login := checkLogin(w, r)
|
||||
inviteDn := config.InvitationNameAttr + "=" + code_id + "," + config.InvitationBaseDN
|
||||
err := login.conn.Bind(inviteDn, code_pw)
|
||||
if err != nil {
|
||||
templateInviteInvalidCode := getTemplate("user/code/invalid.html")
|
||||
templateInviteInvalidCode.Execute(w, nil)
|
||||
return
|
||||
}
|
||||
sReq := ldap.NewSearchRequest(
|
||||
inviteDn,
|
||||
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
||||
fmt.Sprintf("(objectclass=*)"),
|
||||
[]string{"dn", "creatorsname"},
|
||||
nil)
|
||||
sr, err := login.conn.Search(sReq)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if len(sr.Entries) != 1 {
|
||||
http.Error(w, fmt.Sprintf("Expected 1 entry, got %d", len(sr.Entries)), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
invitedBy := sr.Entries[0].GetAttributeValue("creatorsname")
|
||||
if HandleNewAccount(w, r, login.conn, invitedBy) {
|
||||
del_req := ldap.NewDelRequest(inviteDn, nil)
|
||||
err = login.conn.Del(del_req)
|
||||
if err != nil {
|
||||
log.Printf("Could not delete invitation %s: %s", inviteDn, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Common functions for new account
|
||||
func HandleNewAccount(w http.ResponseWriter, r *http.Request, l *ldap.Conn, invitedBy string) bool {
|
||||
templateInviteNewAccount := getTemplate("user/new.html")
|
||||
data := NewAccountData{
|
||||
NewUserDefaultDomain: config.NewUserDefaultDomain,
|
||||
}
|
||||
if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
newUser := models.User{}
|
||||
newUser.DisplayName = strings.TrimSpace(strings.Join(r.Form["displayname"], ""))
|
||||
newUser.GivenName = strings.TrimSpace(strings.Join(r.Form["givenname"], ""))
|
||||
newUser.SN = strings.TrimSpace(strings.Join(r.Form["surname"], ""))
|
||||
newUser.OtherMailbox = strings.TrimSpace(strings.Join(r.Form["othermailbox"], ""))
|
||||
newUser.Mail = strings.TrimSpace(strings.Join(r.Form["mail"], ""))
|
||||
newUser.UID = strings.TrimSpace(strings.Join(r.Form["username"], ""))
|
||||
newUser.CN = strings.TrimSpace(strings.Join(r.Form["username"], ""))
|
||||
newUser.DN = "cn=" + strings.TrimSpace(strings.Join(r.Form["username"], "")) + "," + config.UserBaseDN
|
||||
password1 := strings.Join(r.Form["password"], "")
|
||||
password2 := strings.Join(r.Form["password2"], "")
|
||||
if password1 != password2 {
|
||||
data.Common.Success = false
|
||||
data.ErrorPasswordMismatch = true
|
||||
} else {
|
||||
newUser.Password = password2
|
||||
l.Bind(config.NewUserDN, config.NewUserPassword)
|
||||
err := models.AddUser(newUser, &config, l)
|
||||
if err != nil {
|
||||
data.Common.Success = false
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
http.Redirect(w, r, "/user/wait", http.StatusFound)
|
||||
}
|
||||
// tryCreateAccount(l, data, password1, password2, invitedBy)
|
||||
} else {
|
||||
data.SuggestPW = fmt.Sprintf("%s", models.SuggestPassword())
|
||||
}
|
||||
data.Common.CanAdmin = false
|
||||
data.Common.LoggedIn = false
|
||||
|
||||
templateInviteNewAccount.Execute(w, data)
|
||||
return data.Common.Success
|
||||
}
|
||||
|
||||
func tryCreateAccount(l *ldap.Conn, data *NewAccountData, pass1 string, pass2 string, invitedBy string) {
|
||||
checkFailed := false
|
||||
// Check if username is correct
|
||||
if match, err := regexp.MatchString("^[a-z0-9._-]+$", data.Username); !(err == nil && match) {
|
||||
data.ErrorInvalidUsername = true
|
||||
checkFailed = true
|
||||
}
|
||||
// Check if user exists
|
||||
userDn := config.UserNameAttr + "=" + data.Username + "," + config.UserBaseDN
|
||||
searchRq := ldap.NewSearchRequest(
|
||||
userDn,
|
||||
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
||||
"(objectclass=*)",
|
||||
[]string{"dn"},
|
||||
nil)
|
||||
sr, err := l.Search(searchRq)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
checkFailed = true
|
||||
}
|
||||
if len(sr.Entries) > 0 {
|
||||
data.ErrorUsernameTaken = true
|
||||
checkFailed = true
|
||||
}
|
||||
// Check that password is long enough
|
||||
if len(pass1) < 8 {
|
||||
data.ErrorPasswordTooShort = true
|
||||
checkFailed = true
|
||||
}
|
||||
if pass1 != pass2 {
|
||||
data.ErrorPasswordMismatch = true
|
||||
checkFailed = true
|
||||
}
|
||||
if checkFailed {
|
||||
return
|
||||
}
|
||||
// Actually create user
|
||||
req := ldap.NewAddRequest(userDn, nil)
|
||||
req.Attribute("objectclass", []string{"inetOrgPerson", "organizationalPerson", "person", "top"})
|
||||
req.Attribute("structuralobjectclass", []string{"inetOrgPerson"})
|
||||
pw, err := utils.SSHAEncode(pass1)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
return
|
||||
}
|
||||
req.Attribute("userpassword", []string{pw})
|
||||
req.Attribute("invitedby", []string{invitedBy})
|
||||
if len(data.DisplayName) > 0 {
|
||||
req.Attribute("displayname", []string{data.DisplayName})
|
||||
}
|
||||
if len(data.GivenName) > 0 {
|
||||
req.Attribute("givenname", []string{data.GivenName})
|
||||
}
|
||||
if len(data.Surname) > 0 {
|
||||
req.Attribute("sn", []string{data.Surname})
|
||||
}
|
||||
if len(config.InvitedMailFormat) > 0 {
|
||||
email := strings.ReplaceAll(config.InvitedMailFormat, "{}", data.Username)
|
||||
req.Attribute("mail", []string{email})
|
||||
}
|
||||
err = l.Add(req)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
return
|
||||
}
|
||||
for _, group := range config.InvitedAutoGroups {
|
||||
req := ldap.NewModifyRequest(group, nil)
|
||||
req.Add("member", []string{userDn})
|
||||
err = l.Modify(req)
|
||||
if err != nil {
|
||||
data.Common.WarningMessage += fmt.Sprintf("Cannot add to %s: %s\n", group, err.Error())
|
||||
}
|
||||
}
|
||||
data.Common.Success = true
|
||||
}
|
||||
|
||||
// ---- Code generation ----
|
||||
func HandleInviteSendCode(w http.ResponseWriter, r *http.Request) {
|
||||
templateInviteSendCode := getTemplate("user/code/send.html")
|
||||
login := checkInviterLogin(w, r)
|
||||
if login == nil {
|
||||
return
|
||||
}
|
||||
if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
data := &SendCodeData{
|
||||
WebBaseAddress: config.WebAddress,
|
||||
}
|
||||
// modify_request := ldap.NewModifyRequest(login.UserEntry.DN, nil)
|
||||
// // choice := strings.Join(r.Form["choice"], "")
|
||||
// // sendto := strings.Join(r.Form["sendto"], "")
|
||||
code, code_id, code_pw := genCode()
|
||||
log.Printf("272: %v %v %v", code, code_id, code_pw)
|
||||
// // Create invitation object in database
|
||||
// modify_request.Add("carLicense", []string{fmt.Sprintf("%s,%s,%s",code, code_id, code_pw)})
|
||||
// err := login.conn.Modify(modify_request)
|
||||
// if err != nil {
|
||||
// data.Common.ErrorMessage = err.Error()
|
||||
// // return
|
||||
// } else {
|
||||
// data.Common.Success = true
|
||||
// data.CodeDisplay = code
|
||||
// }
|
||||
log.Printf("279: %v %v %v", code, code_id, code_pw)
|
||||
addReq := ldap.NewAddRequest("documentIdentifier="+code_id+","+config.InvitationBaseDN, nil)
|
||||
addReq.Attribute("objectClass", []string{"top", "document", "simpleSecurityObject"})
|
||||
addReq.Attribute("cn", []string{code})
|
||||
addReq.Attribute("userPassword", []string{code_pw})
|
||||
addReq.Attribute("documentIdentifier", []string{code_id})
|
||||
log.Printf("285: %v %v %v", code, code_id, code_pw)
|
||||
log.Printf("286: %v", addReq)
|
||||
err := login.conn.Add(addReq)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
// return
|
||||
} else {
|
||||
data.Common.Success = true
|
||||
data.CodeDisplay = code
|
||||
}
|
||||
data.Common.CanAdmin = login.Common.CanAdmin
|
||||
templateInviteSendCode.Execute(w, data)
|
||||
// if choice == "display" || choice == "send" {
|
||||
// log.Printf("260: %v %v %v %v", login, choice, sendto, data)
|
||||
// trySendCode(login, choice, sendto, data)
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func trySendCode(login *LoginStatus, choice string, sendto string, data *SendCodeData) {
|
||||
log.Printf("269: %v %v %v %v", login, choice, sendto, data)
|
||||
// Generate code
|
||||
code, code_id, code_pw := genCode()
|
||||
log.Printf("272: %v %v %v", code, code_id, code_pw)
|
||||
// Create invitation object in database
|
||||
// len_base_dn := len(strings.Split(config.BaseDN, ","))
|
||||
// dn_split := strings.Split(super_dn, ",")
|
||||
// for i := len_base_dn + 1; i <= len(dn_split); i++ {
|
||||
// path = append(path, PathItem{
|
||||
// DN: strings.Join(dn_split[len(dn_split)-i:len(dn_split)], ","),
|
||||
// Identifier: dn_split[len(dn_split)-i],
|
||||
// })
|
||||
// }
|
||||
// data := &SendCodeData{
|
||||
// WebBaseAddress: config.WebAddress,
|
||||
// }
|
||||
// // Handle data
|
||||
// data := &CreateData{
|
||||
// SuperDN: super_dn,
|
||||
// Path: path,
|
||||
// }
|
||||
// data.IdType = config.UserNameAttr
|
||||
// data.StructuralObjectClass = "inetOrgPerson"
|
||||
// data.ObjectClass = "inetOrgPerson\norganizationalPerson\nperson\ntop"
|
||||
// data.IdValue = strings.TrimSpace(strings.Join(r.Form["idvalue"], ""))
|
||||
// data.DisplayName = strings.TrimSpace(strings.Join(r.Form["displayname"], ""))
|
||||
// data.GivenName = strings.TrimSpace(strings.Join(r.Form["givenname"], ""))
|
||||
// data.Mail = strings.TrimSpace(strings.Join(r.Form["mail"], ""))
|
||||
// data.Member = strings.TrimSpace(strings.Join(r.Form["member"], ""))
|
||||
// data.Description = strings.TrimSpace(strings.Join(r.Form["description"], ""))
|
||||
// data.SN = strings.TrimSpace(strings.Join(r.Form["sn"], ""))
|
||||
// object_class := []string{}
|
||||
// for _, oc := range strings.Split(data.ObjectClass, "\n") {
|
||||
// x := strings.TrimSpace(oc)
|
||||
// if x != "" {
|
||||
// object_class = append(object_class, x)
|
||||
// }
|
||||
// }
|
||||
// dn := data.IdType + "=" + data.IdValue + "," + super_dn
|
||||
// req := ldap.NewAddRequest(dn, nil)
|
||||
// req.Attribute("objectclass", object_class)
|
||||
// // req.Attribute("mail", []string{data.IdValue})
|
||||
// /*
|
||||
// if data.StructuralObjectClass != "" {
|
||||
// req.Attribute("structuralobjectclass", []string{data.StructuralObjectClass})
|
||||
// }
|
||||
// */
|
||||
// if data.DisplayName != "" {
|
||||
// req.Attribute("displayname", []string{data.DisplayName})
|
||||
// }
|
||||
// if data.GivenName != "" {
|
||||
// req.Attribute("givenname", []string{data.GivenName})
|
||||
// }
|
||||
// if data.Mail != "" {
|
||||
// req.Attribute("mail", []string{data.Mail})
|
||||
// }
|
||||
// if data.Member != "" {
|
||||
// req.Attribute("member", []string{data.Member})
|
||||
// }
|
||||
// if data.SN != "" {
|
||||
// req.Attribute("sn", []string{data.SN})
|
||||
// }
|
||||
// if data.Description != "" {
|
||||
// req.Attribute("description", []string{data.Description})
|
||||
// }
|
||||
// err := login.conn.Add(req)
|
||||
// // log.Printf("899: %v",err)
|
||||
// // log.Printf("899: %v",req)
|
||||
// // log.Printf("899: %v",data)
|
||||
// if err != nil {
|
||||
// data.Common.Error = err.Error()
|
||||
// } else {
|
||||
// if template == "ml" {
|
||||
// http.Redirect(w, r, "/admin/mailing/"+data.IdValue, http.StatusFound)
|
||||
// } else {
|
||||
// http.Redirect(w, r, "/admin/ldap/"+dn, http.StatusFound)
|
||||
// }
|
||||
// }
|
||||
// inviteDn := config.InvitationNameAttr + "=" + code_id + "," + config.InvitationBaseDN
|
||||
// req := ldap.NewAddRequest(inviteDn, nil)
|
||||
// pw, err := SSHAEncode(code_pw)
|
||||
// if err != nil {
|
||||
// data.Common.ErrorMessage = err.Error()
|
||||
// return
|
||||
// }
|
||||
// req.Attribute("employeeNumber", []string{pw})
|
||||
// req.Attribute("objectclass", []string{"top", "invitationCode"})
|
||||
// err = login.conn.Add(req)
|
||||
// if err != nil {
|
||||
// log.Printf("286: %v", req)
|
||||
// data.Common.ErrorMessage = err.Error()
|
||||
// return
|
||||
// }
|
||||
|
||||
// If we want to display it, do so
|
||||
if choice == "display" {
|
||||
data.Common.Success = true
|
||||
data.CodeDisplay = code
|
||||
return
|
||||
}
|
||||
// Otherwise, we are sending a mail
|
||||
if !EMAIL_REGEXP.MatchString(sendto) {
|
||||
data.ErrorInvalidEmail = true
|
||||
return
|
||||
}
|
||||
templateMail := template.Must(template.ParseFiles(templatePath + "/invite_mail.txt"))
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
templateMail.Execute(buf, &models.CodeMailFields{
|
||||
To: sendto,
|
||||
From: config.MailFrom,
|
||||
InviteFrom: login.WelcomeName(),
|
||||
Code: code,
|
||||
WebBaseAddress: config.WebAddress,
|
||||
})
|
||||
log.Printf("Sending mail to: %s", sendto)
|
||||
// var auth sasl.Client = nil
|
||||
// if config.SMTPUsername != "" {
|
||||
// auth = sasl.NewPlainClient("", config.SMTPUsername, config.SMTPPassword)
|
||||
// }
|
||||
// err = smtp.SendMail(config.SMTPServer, auth, config.MailFrom, []string{sendto}, buf)
|
||||
// if err != nil {
|
||||
// data.Common.ErrorMessage = err.Error()
|
||||
// return
|
||||
// }
|
||||
// log.Printf("Mail sent.")
|
||||
data.Common.Success = true
|
||||
data.CodeSentTo = sendto
|
||||
}
|
||||
|
||||
func genCode() (code string, code_id string, code_pw string) {
|
||||
random := make([]byte, 32)
|
||||
n, err := rand.Read(random)
|
||||
if err != nil || n != 32 {
|
||||
log.Fatalf("Could not generate random bytes: %s", err)
|
||||
}
|
||||
a := binary.BigEndian.Uint32(random[0:4])
|
||||
b := binary.BigEndian.Uint32(random[4:8])
|
||||
c := binary.BigEndian.Uint32(random[8:12])
|
||||
code = fmt.Sprintf("%03d-%03d-%03d", a%1000, b%1000, c%1000)
|
||||
code_id, code_pw = readCode(code)
|
||||
log.Printf("342: %v %v %v", code, code_id, code_pw)
|
||||
return code, code_id, code_pw
|
||||
}
|
||||
|
||||
func readCode(code string) (code_id string, code_pw string) {
|
||||
// Strip everything that is not a digit
|
||||
code_digits := ""
|
||||
for _, c := range code {
|
||||
if c >= '0' && c <= '9' {
|
||||
code_digits = code_digits + string(c)
|
||||
}
|
||||
}
|
||||
id_hash := argon2.IDKey([]byte(code_digits), []byte("Guichet ID"), 2, 64*1024, 4, 32)
|
||||
pw_hash := argon2.IDKey([]byte(code_digits), []byte("Guichet PW"), 2, 64*1024, 4, 32)
|
||||
code_id = hex.EncodeToString(id_hash[:8])
|
||||
code_pw = hex.EncodeToString(pw_hash[:16])
|
||||
return code_id, code_pw
|
||||
}
|
144
views/login.go
Normal file
144
views/login.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
login Handles login and current-user verification
|
||||
*/
|
||||
|
||||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
func HandleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
err := logout(w, r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
}
|
||||
|
||||
func HandleLogin(w http.ResponseWriter, r *http.Request) (*LoginInfo, error) {
|
||||
templateLogin := getTemplate("login.html")
|
||||
|
||||
if r.Method == "POST" {
|
||||
// log.Printf("%v", "Parsing Form HandleLogin")
|
||||
r.ParseForm()
|
||||
|
||||
username := strings.Join(r.Form["username"], "")
|
||||
password := strings.Join(r.Form["password"], "")
|
||||
l, _ := ldapOpen(w)
|
||||
|
||||
user_dn := fmt.Sprintf("%s=%s,%s", config.UserNameAttr, username, config.UserBaseDN)
|
||||
|
||||
// log.Printf("%v", user_dn)
|
||||
// log.Printf("%v", username)
|
||||
|
||||
if strings.EqualFold(username, config.AdminAccount) {
|
||||
user_dn = username
|
||||
}
|
||||
|
||||
|
||||
err := l.Bind(user_dn, password)
|
||||
if err != nil {
|
||||
log.Printf("DoLogin : %v", err)
|
||||
log.Printf("DoLogin : %v", user_dn)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// func encodePassword(inPassword string) (string, error) {
|
||||
// utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
|
||||
// return utf16.NewEncoder().String("\"" + inPassword + "\"")
|
||||
// // if err != nil {
|
||||
// // log.Printf("Error encoding password: %s", err)
|
||||
// // return err
|
||||
// // }
|
||||
|
||||
// }
|
||||
|
||||
|
||||
// log.Printf("%v", LoginInfo)
|
||||
if err != nil {
|
||||
data := &LoginFormData{
|
||||
Username: username,
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: false,
|
||||
CanInvite: true,
|
||||
LoggedIn: false,
|
||||
},
|
||||
}
|
||||
if ldap.IsErrorWithCode(err, ldap.LDAPResultInvalidCredentials) {
|
||||
data.WrongPass = true
|
||||
} else if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
|
||||
data.WrongUser = true
|
||||
} else {
|
||||
log.Printf("%v", err)
|
||||
log.Printf("%v", user_dn)
|
||||
log.Printf("%v", username)
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
}
|
||||
// Successfully logged in, save it to session
|
||||
session, err := GuichetSessionStore.Get(r, SESSION_NAME)
|
||||
if err != nil {
|
||||
session, _ = GuichetSessionStore.New(r, SESSION_NAME)
|
||||
}
|
||||
session.Values["login_username"] = username
|
||||
session.Values["login_password"] = password
|
||||
session.Values["login_dn"] = user_dn
|
||||
|
||||
err = session.Save(r, w)
|
||||
if err != nil {
|
||||
log.Printf("DoLogin Session Save: %v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
// // templateLogin.Execute(w, data)
|
||||
// execTemplate(w, templateLogin, data.Common, NestedLoginTplData{}, *config, data)
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||
|
||||
} else if r.Method == "GET" {
|
||||
execTemplate(w, templateLogin, NestedCommonTplData{
|
||||
CanAdmin: false,
|
||||
CanInvite: true,
|
||||
LoggedIn: false}, NestedLoginTplData{}, LoginFormData{
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: false,
|
||||
CanInvite: true,
|
||||
LoggedIn: false}})
|
||||
// templateLogin.Execute(w, LoginFormData{
|
||||
// Common: NestedCommonTplData{
|
||||
// CanAdmin: false,
|
||||
// CanInvite: true,
|
||||
// LoggedIn: false}})
|
||||
return nil, nil
|
||||
} else {
|
||||
http.Error(w, "Unsupported method", http.StatusBadRequest)
|
||||
return nil, nil
|
||||
}
|
||||
// execTemplate(w, templateLogin, data.Common, NestedLoginTplData{}, *config, data)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// func NotDoLogin(w http.ResponseWriter, r *http.Request, username string, user_dn string, password string) (*LoginInfo, error) {
|
||||
|
||||
|
||||
// }
|
161
views/passwd.go
Normal file
161
views/passwd.go
Normal file
|
@ -0,0 +1,161 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"guichet/models"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
// "github.com/go-ldap/ldap/v3"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func HandleLostPassword(w http.ResponseWriter, r *http.Request) {
|
||||
templateLostPasswordPage := getTemplate("passwd/lost.html")
|
||||
if checkLogin(w, r) != nil {
|
||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
data := PasswordLostData{
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: false,
|
||||
LoggedIn: false},
|
||||
}
|
||||
|
||||
if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
data.Username = strings.TrimSpace(strings.Join(r.Form["username"], ""))
|
||||
data.Mail = strings.TrimSpace(strings.Join(r.Form["mail"], ""))
|
||||
data.OtherMailbox = strings.TrimSpace(strings.Join(r.Form["othermailbox"], ""))
|
||||
user := models.User{
|
||||
CN: strings.TrimSpace(strings.Join(r.Form["username"], "")),
|
||||
UID: strings.TrimSpace(strings.Join(r.Form["username"], "")),
|
||||
Mail: strings.TrimSpace(strings.Join(r.Form["mail"], "")),
|
||||
OtherMailbox: strings.TrimSpace(strings.Join(r.Form["othermailbox"], "")),
|
||||
}
|
||||
ldapNewConn, err := models.OpenNewUserLdap(&config)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("HandleLostPassword 99 : %v %v", err, ldapNewConn))
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("HandleLostPassword 104 : %v %v", err, ldapNewConn))
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
} else {
|
||||
// err = ldapConn.Bind(config.NewUserDN, config.NewUserPassword)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("HandleLostPassword 109 : %v %v", err, ldapNewConn))
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
} else {
|
||||
data.Common.Success = true
|
||||
}
|
||||
}
|
||||
err = models.PasswordLost(user, &config, ldapNewConn)
|
||||
}
|
||||
data.Common.CanAdmin = false
|
||||
// templateLostPasswordPage.Execute(w, data)
|
||||
execTemplate(w, templateLostPasswordPage, data.Common, NestedLoginTplData{}, data)
|
||||
}
|
||||
|
||||
func HandleFoundPassword(w http.ResponseWriter, r *http.Request) {
|
||||
templateFoundPasswordPage := getTemplate("passwd.html")
|
||||
data := PasswdTplData{
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: false,
|
||||
LoggedIn: false},
|
||||
}
|
||||
code := mux.Vars(r)["code"]
|
||||
// code = strings.TrimSpace(strings.Join([]string{code}, ""))
|
||||
newCode, _ := b64.URLEncoding.DecodeString(code)
|
||||
ldapNewConn, err := models.OpenNewUserLdap(&config)
|
||||
if err != nil {
|
||||
log.Printf("HandleFoundPassword OpenNewUserLdap(config) : %v", err)
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
codeArray := strings.Split(string(newCode), ";")
|
||||
user := models.User{
|
||||
UID: codeArray[0],
|
||||
Password: codeArray[1],
|
||||
DN: "uid=" + codeArray[0] + "," + config.InvitationBaseDN,
|
||||
}
|
||||
user.SeeAlso, err = models.PasswordFound(user, &config, ldapNewConn)
|
||||
if err != nil {
|
||||
log.Printf("PasswordFound(models.User, config, ldapConn) %v", err)
|
||||
log.Printf("PasswordFound(models.User, config, ldapConn) %v", user)
|
||||
log.Printf("PasswordFound(models.User, config, ldapConn) %v", ldapNewConn)
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
|
||||
password := strings.Join(r.Form["password"], "")
|
||||
password2 := strings.Join(r.Form["password2"], "")
|
||||
|
||||
if len(password) < 8 {
|
||||
data.TooShortError = true
|
||||
} else if password2 != password {
|
||||
data.NoMatchError = true
|
||||
} else {
|
||||
err := models.PassWD(models.User{
|
||||
DN: user.SeeAlso,
|
||||
Password: password,
|
||||
}, &config, ldapNewConn)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
} else {
|
||||
data.Common.Success = true
|
||||
}
|
||||
}
|
||||
}
|
||||
data.Common.CanAdmin = false
|
||||
// templateFoundPasswordPage.Execute(w, data)
|
||||
execTemplate(w, templateFoundPasswordPage, data.Common, data.Login, data)
|
||||
}
|
||||
|
||||
func HandlePasswd(w http.ResponseWriter, r *http.Request) {
|
||||
templatePasswd := getTemplate("passwd.html")
|
||||
data := &PasswdTplData{
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: false,
|
||||
LoggedIn: true,
|
||||
ErrorMessage: "",
|
||||
Success: false,
|
||||
},
|
||||
}
|
||||
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
data.Login.Status = login
|
||||
data.Common.CanAdmin = login.Common.CanAdmin
|
||||
|
||||
if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
|
||||
password := strings.Join(r.Form["password"], "")
|
||||
password2 := strings.Join(r.Form["password2"], "")
|
||||
|
||||
if len(password) < 8 {
|
||||
data.TooShortError = true
|
||||
} else if password2 != password {
|
||||
data.NoMatchError = true
|
||||
} else {
|
||||
err := models.PassWD(models.User{
|
||||
DN: login.Info.DN,
|
||||
Password: password,
|
||||
}, &config, login.conn)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
} else {
|
||||
data.Common.Success = true
|
||||
}
|
||||
}
|
||||
}
|
||||
data.Common.CanAdmin = false
|
||||
// templatePasswd.Execute(w, data)
|
||||
execTemplate(w, templatePasswd, data.Common, data.Login, data)
|
||||
}
|
184
views/picture.go
Normal file
184
views/picture.go
Normal file
|
@ -0,0 +1,184 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"image"
|
||||
"image/jpeg"
|
||||
_ "image/png"
|
||||
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/nfnt/resize"
|
||||
)
|
||||
|
||||
func newMinioClient() (*minio.Client, error) {
|
||||
endpoint := config.S3Endpoint
|
||||
accessKeyID := config.S3AccessKey
|
||||
secretKeyID := config.S3SecretKey
|
||||
useSSL := true
|
||||
|
||||
//Initialize Minio
|
||||
minioCLient, err := minio.New(endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(accessKeyID, secretKeyID, ""),
|
||||
Secure: useSSL,
|
||||
Region: config.S3Region,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return minioCLient, nil
|
||||
}
|
||||
|
||||
// Upload image through guichet server.
|
||||
func uploadProfilePicture(w http.ResponseWriter, r *http.Request, login *LoginStatus) (string, error) {
|
||||
file, _, err := r.FormFile("image")
|
||||
|
||||
if err == http.ErrMissingFile {
|
||||
return "", nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
err = checkImage(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buffFull := bytes.NewBuffer([]byte{})
|
||||
buffThumb := bytes.NewBuffer([]byte{})
|
||||
err = resizePicture(file, buffFull, buffThumb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
mc, err := newMinioClient()
|
||||
if err != nil || mc == nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// If a previous profile picture existed, delete it
|
||||
// (don't care about errors)
|
||||
if nameConsul := login.UserEntry.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE); nameConsul != "" {
|
||||
mc.RemoveObject(context.Background(), config.S3Bucket, nameConsul, minio.RemoveObjectOptions{})
|
||||
mc.RemoveObject(context.Background(), config.S3Bucket, nameConsul+"-thumb", minio.RemoveObjectOptions{})
|
||||
}
|
||||
|
||||
// Generate new random name for picture
|
||||
nameFull := uuid.New().String()
|
||||
nameThumb := nameFull + "-thumb"
|
||||
|
||||
_, err = mc.PutObject(context.Background(), config.S3Bucket, nameThumb, buffThumb, int64(buffThumb.Len()), minio.PutObjectOptions{
|
||||
ContentType: "image/jpeg",
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = mc.PutObject(context.Background(), config.S3Bucket, nameFull, buffFull, int64(buffFull.Len()), minio.PutObjectOptions{
|
||||
ContentType: "image/jpeg",
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return nameFull, nil
|
||||
}
|
||||
|
||||
func checkImage(file multipart.File) error {
|
||||
buff := make([]byte, 512) //Detect read only the first 512 bytes
|
||||
_, err := file.Read(buff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file.Seek(0, 0)
|
||||
|
||||
fileType := http.DetectContentType(buff)
|
||||
fileType = strings.Split(fileType, "/")[0]
|
||||
if fileType != "image" {
|
||||
return errors.New("bad type")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resizePicture(file multipart.File, buffFull, buffThumb *bytes.Buffer) error {
|
||||
file.Seek(0, 0)
|
||||
picture, _, err := image.Decode(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
thumbnail := resize.Thumbnail(90, 90, picture, resize.Lanczos3)
|
||||
picture = resize.Thumbnail(480, 480, picture, resize.Lanczos3)
|
||||
|
||||
err = jpeg.Encode(buffFull, picture, &jpeg.Options{
|
||||
Quality: 95,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = jpeg.Encode(buffThumb, thumbnail, &jpeg.Options{
|
||||
Quality: 100,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func HandleDownloadPicture(w http.ResponseWriter, r *http.Request) {
|
||||
name := mux.Vars(r)["name"]
|
||||
|
||||
//Check login
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
return
|
||||
}
|
||||
|
||||
//Get the object after connect MC
|
||||
mc, err := newMinioClient()
|
||||
if err != nil {
|
||||
http.Error(w, "MinioClient: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
obj, err := mc.GetObject(context.Background(), "bottin-pictures", name, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
http.Error(w, "MinioClient: GetObject: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer obj.Close()
|
||||
|
||||
objStat, err := obj.Stat()
|
||||
if err != nil {
|
||||
http.Error(w, "MiniObjet: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
//Send JSON through xhttp
|
||||
w.Header().Set("Content-Type", objStat.ContentType)
|
||||
w.Header().Set("Content-Length", strconv.Itoa(int(objStat.Size)))
|
||||
//Copy obj in w
|
||||
writting, err := io.Copy(w, obj)
|
||||
|
||||
if writting != objStat.Size || err != nil {
|
||||
http.Error(w, fmt.Sprintf("WriteBody: %s, bytes wrote %d on %d", err.Error(), writting, objStat.Size), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
177
views/session.go
Normal file
177
views/session.go
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
Handles session login and lougout with HTTP stuff
|
||||
*/
|
||||
package views
|
||||
|
||||
import (
|
||||
"guichet/models"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func checkLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
|
||||
var login_info *LoginInfo
|
||||
l, err := ldapOpen(w)
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
session, err := GuichetSessionStore.Get(r, SESSION_NAME)
|
||||
if err != nil {
|
||||
log.Printf("checkLogin ldapOpen : %v", err)
|
||||
log.Printf("checkLogin ldapOpen : %v", session)
|
||||
log.Printf("checkLogin ldapOpen : %v", session.Values)
|
||||
return 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),
|
||||
}
|
||||
err = models.Bind(models.User{
|
||||
DN: login_info.DN,
|
||||
Password: login_info.Password,
|
||||
}, &config, l)
|
||||
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)
|
||||
}
|
||||
ldapUser, err := models.GetUser(models.User{
|
||||
DN: login_info.DN,
|
||||
CN: login_info.Username,
|
||||
}, &config, l)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return nil
|
||||
}
|
||||
userEntry := ldapUser.UserEntry
|
||||
loginStatus :=LoginStatus{
|
||||
Info: login_info,
|
||||
conn: l,
|
||||
UserEntry: userEntry,
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: ldapUser.CanAdmin,
|
||||
CanInvite: ldapUser.CanInvite,
|
||||
},
|
||||
}
|
||||
return &loginStatus
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
requestKind := "(objectClass=organizationalPerson)"
|
||||
if strings.EqualFold(login_info.DN, config.AdminAccount) {
|
||||
requestKind = "(objectclass=*)"
|
||||
}
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
login_info.DN,
|
||||
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
||||
requestKind,
|
||||
[]string{
|
||||
"dn",
|
||||
"displayname",
|
||||
"givenname",
|
||||
"sn",
|
||||
"mail",
|
||||
"cn",
|
||||
"memberof",
|
||||
"description",
|
||||
"garage_s3_access_key",
|
||||
},
|
||||
nil)
|
||||
// FIELD_NAME_DIRECTORY_VISIBILITY,
|
||||
// FIELD_NAME_PROFILE_PICTURE,
|
||||
|
||||
sr, err := l.Search(searchRequest)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(sr.Entries) != 1 {
|
||||
http.Error(w, fmt.Sprintf("Unable to find entry for %s", login_info.DN), http.StatusInternalServerError)
|
||||
return nil
|
||||
}
|
||||
|
||||
loginStatus.UserEntry = sr.Entries[0]
|
||||
|
||||
loginStatus.CanAdmin = strings.EqualFold(loginStatus.Info.DN, config.AdminAccount)
|
||||
loginStatus.CanInvite = false
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
return loginStatus
|
||||
*/
|
||||
|
||||
func logout(w http.ResponseWriter, r *http.Request) error {
|
||||
session, err := GuichetSessionStore.Get(r, SESSION_NAME)
|
||||
if err != nil {
|
||||
session, _ = GuichetSessionStore.New(r, SESSION_NAME)
|
||||
// return err
|
||||
} else {
|
||||
delete(session.Values, "login_username")
|
||||
delete(session.Values, "login_password")
|
||||
delete(session.Values, "login_dn")
|
||||
|
||||
err = session.Save(r, w)
|
||||
}
|
||||
|
||||
// return err
|
||||
return nil
|
||||
}
|
175
views/user.go
Normal file
175
views/user.go
Normal file
|
@ -0,0 +1,175 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
// b64 "encoding/base64"
|
||||
"fmt"
|
||||
// "log"
|
||||
"guichet/models"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
// "github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func HandleUserWait(w http.ResponseWriter, r *http.Request) {
|
||||
templateUser := getTemplate("user/wait.html")
|
||||
templateUser.Execute(w, HomePageData{
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: false,
|
||||
LoggedIn: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func HandleUserMail(w http.ResponseWriter, r *http.Request) {
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
email := r.FormValue("email")
|
||||
action := r.FormValue("action")
|
||||
var err error
|
||||
if action == "Add" {
|
||||
// Add the new mail value to the entry
|
||||
modifyRequest := ldap.NewModifyRequest(login.Info.DN, nil)
|
||||
modifyRequest.Add("mail", []string{email})
|
||||
|
||||
err = login.conn.Modify(modifyRequest)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Error adding the email: %v", modifyRequest), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else if action == "Delete" {
|
||||
modifyRequest := ldap.NewModifyRequest(login.Info.DN, nil)
|
||||
modifyRequest.Delete("mail", []string{email})
|
||||
|
||||
log.Printf("HandleUserMail %v", modifyRequest)
|
||||
err = login.conn.Modify(modifyRequest)
|
||||
if err != nil {
|
||||
log.Printf("HandleUserMail DeleteMail %v", err)
|
||||
http.Error(w, fmt.Sprintf("Error deleting the email: %s", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("Mail value updated successfully to: %s", email)
|
||||
http.Redirect(w, r, "/user?message="+message, http.StatusSeeOther)
|
||||
|
||||
}
|
||||
|
||||
func toInteger(index string) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func HandleUser(w http.ResponseWriter, r *http.Request) {
|
||||
templateUser := getTemplate("user.html")
|
||||
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
data := &ProfileTplData{
|
||||
Login: NestedLoginTplData{
|
||||
Status: login,
|
||||
Login: login,
|
||||
},
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: login.Common.CanAdmin,
|
||||
LoggedIn: true,
|
||||
ErrorMessage: "",
|
||||
Success: false,
|
||||
},
|
||||
}
|
||||
|
||||
data.Mail = login.UserEntry.GetAttributeValue("mail")
|
||||
data.DisplayName = login.UserEntry.GetAttributeValue("displayName")
|
||||
data.GivenName = login.UserEntry.GetAttributeValue("givenName")
|
||||
data.Surname = login.UserEntry.GetAttributeValue("sn")
|
||||
data.OtherMailbox = login.UserEntry.GetAttributeValue("carLicense")
|
||||
data.MailValues = login.UserEntry.GetAttributeValues("mail")
|
||||
// data.Visibility = login.UserEntry.GetAttributeValue(FIELD_NAME_DIRECTORY_VISIBILITY)
|
||||
data.Description = login.UserEntry.GetAttributeValue("description")
|
||||
//data.ProfilePicture = login.UserEntry.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE)
|
||||
|
||||
if r.Method == "POST" {
|
||||
//5MB maximum size files
|
||||
r.ParseMultipartForm(5 << 20)
|
||||
user := models.User{
|
||||
DN: login.Info.DN,
|
||||
GivenName: strings.TrimSpace(strings.Join(r.Form["given_name"], "")),
|
||||
DisplayName: strings.TrimSpace(strings.Join(r.Form["display_name"], "")),
|
||||
Mail: strings.TrimSpace(strings.Join(r.Form["mail"], "")),
|
||||
SN: strings.TrimSpace(strings.Join(r.Form["surname"], "")),
|
||||
OtherMailbox: strings.TrimSpace(strings.Join(r.Form["othermailbox"], "")),
|
||||
Description: strings.TrimSpace(strings.Join(r.Form["description"], "")),
|
||||
// Password: ,
|
||||
//UID: ,
|
||||
// CN: ,
|
||||
}
|
||||
|
||||
if user.DisplayName != "" {
|
||||
err := models.ModifyUser(user, &config, login.conn)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = "HandleUser : " + err.Error()
|
||||
} else {
|
||||
data.Common.Success = true
|
||||
}
|
||||
}
|
||||
findUser, err := models.GetUser(user, &config, login.conn)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = "HandleUser : " + err.Error()
|
||||
}
|
||||
data.DisplayName = findUser.DisplayName
|
||||
data.GivenName = findUser.GivenName
|
||||
data.Surname = findUser.SN
|
||||
data.Description = findUser.Description
|
||||
data.Mail = findUser.Mail
|
||||
data.Common.LoggedIn = false
|
||||
|
||||
/*
|
||||
visible := strings.TrimSpace(strings.Join(r.Form["visibility"], ""))
|
||||
if visible != "" {
|
||||
visible = "on"
|
||||
} else {
|
||||
visible = "off"
|
||||
}
|
||||
data.Visibility = visible
|
||||
*/
|
||||
/*
|
||||
profilePicture, err := uploadProfilePicture(w, r, login)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
if profilePicture != "" {
|
||||
data.ProfilePicture = profilePicture
|
||||
}
|
||||
*/
|
||||
|
||||
//modify_request.Replace(FIELD_NAME_DIRECTORY_VISIBILITY, []string{data.Visibility})
|
||||
//modify_request.Replace(FIELD_NAME_DIRECTORY_VISIBILITY, []string{"on"})
|
||||
//if data.ProfilePicture != "" {
|
||||
// modify_request.Replace(FIELD_NAME_PROFILE_PICTURE, []string{data.ProfilePicture})
|
||||
// }
|
||||
|
||||
// err := login.conn.Modify(modify_request)
|
||||
// log.Printf(fmt.Sprintf("Profile:079: %v",modify_request))
|
||||
// log.Printf(fmt.Sprintf("Profile:079: %v",err))
|
||||
// log.Printf(fmt.Sprintf("Profile:079: %v",data))
|
||||
// if err != nil {
|
||||
// data.Common.ErrorMessage = err.Error()
|
||||
// } else {
|
||||
// data.Common.Success = true
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
log.Printf("HandleUser : %v", data)
|
||||
|
||||
// templateUser.Execute(w, data)
|
||||
execTemplate(w, templateUser, data.Common, data.Login, data)
|
||||
}
|
368
views/view.go
Normal file
368
views/view.go
Normal file
|
@ -0,0 +1,368 @@
|
|||
/*
|
||||
Creates the webpages to be processed by Guichet
|
||||
*/
|
||||
package views
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"guichet/models"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
|
||||
"flag"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
// "net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
const SESSION_NAME = "guichet_session"
|
||||
|
||||
var templatePath = "./templates"
|
||||
var GuichetSessionStore sessions.Store = nil
|
||||
|
||||
type EntryList []*ldap.Entry
|
||||
type LoginInfo struct {
|
||||
Username string
|
||||
DN string
|
||||
Password string
|
||||
}
|
||||
func ReadConfig() models.ConfigFile {
|
||||
// Default configuration values for certain fields
|
||||
flag.Parse()
|
||||
var configFlag = flag.String("config", "./config.json", "Configuration file path")
|
||||
|
||||
config_file := models.ConfigFile{
|
||||
HttpBindAddr: ":9991",
|
||||
LdapServerAddr: "ldap://127.0.0.1:389",
|
||||
|
||||
UserNameAttr: "uid",
|
||||
GroupNameAttr: "gid",
|
||||
|
||||
InvitationNameAttr: "cn",
|
||||
InvitedAutoGroups: []string{},
|
||||
|
||||
Org: "ResDigita",
|
||||
}
|
||||
|
||||
_, err := os.Stat(*configFlag)
|
||||
if os.IsNotExist(err) {
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
type LoginStatus struct {
|
||||
Info *LoginInfo
|
||||
conn *ldap.Conn
|
||||
UserEntry *ldap.Entry
|
||||
Common NestedCommonTplData
|
||||
}
|
||||
type NestedCommonTplData struct {
|
||||
Error string
|
||||
ErrorMessage string
|
||||
CanAdmin bool
|
||||
CanInvite bool
|
||||
LoggedIn bool
|
||||
Success bool
|
||||
WarningMessage string
|
||||
WebsiteName string
|
||||
WebsiteURL string
|
||||
}
|
||||
type CodeMailFields struct {
|
||||
From string
|
||||
To string
|
||||
Code string
|
||||
InviteFrom string
|
||||
WebBaseAddress string
|
||||
Common NestedCommonTplData
|
||||
}
|
||||
|
||||
var config = ReadConfig()
|
||||
|
||||
func ldapOpen(w http.ResponseWriter) (*ldap.Conn, error) {
|
||||
if config.LdapTLS {
|
||||
tlsConf := &tls.Config{
|
||||
ServerName: config.LdapServerAddr,
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
return ldap.DialTLS("tcp", net.JoinHostPort(config.LdapServerAddr, "636"), tlsConf)
|
||||
} else {
|
||||
return ldap.DialURL("ldap://" + config.LdapServerAddr)
|
||||
}
|
||||
|
||||
// if err != nil {
|
||||
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
// log.Printf(fmt.Sprintf("27: %v %v", err, l))
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// return l
|
||||
}
|
||||
|
||||
|
||||
// type keyView struct {
|
||||
// Status *LoginStatus
|
||||
// Key *garage.KeyInfo
|
||||
// }
|
||||
// type webInspectView struct {
|
||||
// Status *LoginStatus
|
||||
// Key *garage.KeyInfo
|
||||
// Bucket *garage.BucketInfo
|
||||
// IndexDoc string
|
||||
// ErrorDoc string
|
||||
// MaxObjects int64
|
||||
// MaxSize int64
|
||||
// UsedSizePct float64
|
||||
// }
|
||||
// type webListView struct {
|
||||
// Status *LoginStatus
|
||||
// Key *garage.KeyInfo
|
||||
// }
|
||||
type LayoutTemplateData struct {
|
||||
Common NestedCommonTplData
|
||||
Login NestedLoginTplData
|
||||
Data any
|
||||
}
|
||||
type NestedLoginTplData struct {
|
||||
Login *LoginStatus
|
||||
Username string
|
||||
Status *LoginStatus
|
||||
}
|
||||
|
||||
|
||||
func execTemplate(w http.ResponseWriter, t *template.Template, commonData NestedCommonTplData, loginData NestedLoginTplData, data any) error {
|
||||
commonData.WebsiteURL = config.WebAddress
|
||||
commonData.WebsiteName = config.Org
|
||||
return t.Execute(w, LayoutTemplateData{
|
||||
Common: commonData,
|
||||
Login: loginData,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
type AdminUsersTplData struct {
|
||||
UserNameAttr string
|
||||
UserBaseDN string
|
||||
Users EntryList
|
||||
Common NestedCommonTplData
|
||||
Login NestedLoginTplData
|
||||
}
|
||||
type AdminLDAPTplData struct {
|
||||
DN string
|
||||
|
||||
Path []PathItem
|
||||
ChildrenOU []Child
|
||||
ChildrenOther []Child
|
||||
CanAddChild bool
|
||||
Props map[string]*PropValues
|
||||
CanDelete bool
|
||||
|
||||
HasMembers bool
|
||||
Members []EntryName
|
||||
PossibleNewMembers []EntryName
|
||||
HasGroups bool
|
||||
Groups []EntryName
|
||||
PossibleNewGroups []EntryName
|
||||
|
||||
ListMemGro map[string]string
|
||||
|
||||
Common NestedCommonTplData
|
||||
Login NestedLoginTplData
|
||||
}
|
||||
type AdminMailingListTplData struct {
|
||||
Common NestedCommonTplData
|
||||
Login NestedLoginTplData
|
||||
MailingNameAttr string
|
||||
MailingBaseDN string
|
||||
MailingList *ldap.Entry
|
||||
Members EntryList
|
||||
PossibleNewMembers EntryList
|
||||
AllowGuest bool
|
||||
}
|
||||
type AdminMailingTplData struct {
|
||||
Common NestedCommonTplData
|
||||
Login NestedLoginTplData
|
||||
MailingNameAttr string
|
||||
MailingBaseDN string
|
||||
MailingLists EntryList
|
||||
}
|
||||
type AdminGroupsTplData struct {
|
||||
Common NestedCommonTplData
|
||||
Login NestedLoginTplData
|
||||
GroupNameAttr string
|
||||
GroupBaseDN string
|
||||
Groups EntryList
|
||||
}
|
||||
type EntryName struct {
|
||||
DN string
|
||||
Name string
|
||||
}
|
||||
type Child struct {
|
||||
DN string
|
||||
Identifier string
|
||||
Name string
|
||||
}
|
||||
type PathItem struct {
|
||||
DN string
|
||||
Identifier string
|
||||
Active bool
|
||||
}
|
||||
type PropValues struct {
|
||||
Name string
|
||||
Values []string
|
||||
Editable bool
|
||||
Deletable bool
|
||||
}
|
||||
type CreateData struct {
|
||||
SuperDN string
|
||||
Path []PathItem
|
||||
Template string
|
||||
|
||||
IdType string
|
||||
IdValue string
|
||||
DisplayName string
|
||||
GivenName string
|
||||
Member string
|
||||
Mail string
|
||||
Description string
|
||||
StructuralObjectClass string
|
||||
ObjectClass string
|
||||
SN string
|
||||
OtherMailbox string
|
||||
|
||||
Common NestedCommonTplData
|
||||
Login NestedLoginTplData
|
||||
}
|
||||
|
||||
|
||||
type HomePageData struct {
|
||||
Common NestedCommonTplData
|
||||
Login NestedLoginTplData
|
||||
BaseDN string
|
||||
Org string
|
||||
}
|
||||
type PasswordFoundData struct {
|
||||
Common NestedCommonTplData
|
||||
Login NestedLoginTplData
|
||||
Username string
|
||||
Mail string
|
||||
OtherMailbox string
|
||||
}
|
||||
type PasswordLostData struct {
|
||||
Common NestedCommonTplData
|
||||
ErrorMessage string
|
||||
Success bool
|
||||
Username string
|
||||
Mail string
|
||||
OtherMailbox string
|
||||
}
|
||||
type NewAccountData struct {
|
||||
Username string
|
||||
DisplayName string
|
||||
GivenName string
|
||||
Surname string
|
||||
Mail string
|
||||
SuggestPW string
|
||||
OtherMailbox string
|
||||
|
||||
ErrorUsernameTaken bool
|
||||
ErrorInvalidUsername bool
|
||||
ErrorPasswordTooShort bool
|
||||
ErrorPasswordMismatch bool
|
||||
Common NestedCommonTplData
|
||||
NewUserDefaultDomain string
|
||||
}
|
||||
type SendCodeData struct {
|
||||
Common NestedCommonTplData
|
||||
ErrorInvalidEmail bool
|
||||
|
||||
CodeDisplay string
|
||||
CodeSentTo string
|
||||
WebBaseAddress string
|
||||
}
|
||||
|
||||
|
||||
type ProfileTplData struct {
|
||||
Mail string
|
||||
MailValues []string
|
||||
DisplayName string
|
||||
GivenName string
|
||||
Surname string
|
||||
Description string
|
||||
OtherMailbox string
|
||||
Common NestedCommonTplData
|
||||
Login NestedLoginTplData
|
||||
}
|
||||
|
||||
//ProfilePicture string
|
||||
//Visibility string
|
||||
|
||||
type PasswdTplData struct {
|
||||
Common NestedCommonTplData
|
||||
Login NestedLoginTplData
|
||||
TooShortError bool
|
||||
NoMatchError bool
|
||||
}
|
||||
|
||||
|
||||
type LoginFormData struct {
|
||||
Username string
|
||||
WrongUser bool
|
||||
WrongPass bool
|
||||
Common NestedCommonTplData
|
||||
}
|
||||
|
||||
|
||||
|
||||
type WrapperTemplate struct {
|
||||
Template *template.Template
|
||||
}
|
||||
|
||||
|
||||
|
||||
func getTemplate(name string) *template.Template {
|
||||
return template.Must(template.New("layout.html").Funcs(template.FuncMap{
|
||||
"contains": strings.Contains,
|
||||
}).ParseFiles(
|
||||
templatePath+"/layout.html",
|
||||
templatePath+"/"+name,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in a new issue