Add_Directory_and_ProfilePicture #9
14 changed files with 575 additions and 53 deletions
2
Makefile
2
Makefile
|
@ -1,5 +1,5 @@
|
|||
BIN=guichet
|
||||
SRC=main.go ssha.go profile.go admin.go invite.go
|
||||
SRC=main.go ssha.go profile.go admin.go invite.go directory.go picture.go
|
||||
DOCKER=lxpz/guichet_amd64
|
||||
|
||||
all: $(BIN)
|
||||
|
|
34
config.json.example
Normal file
34
config.json.example
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"http_bind_addr": ":9991",
|
||||
"ldap_server_addr": "ldap://127.0.0.1:389",
|
||||
|
||||
"base_dn": "dc=example,dc=com",
|
||||
"user_base_dn": "ou=users,dc=example,dc=com",
|
||||
"user_name_attr": "uid",
|
||||
"group_base_dn": "ou=groups,dc=example,dc=com",
|
||||
"group_name_attr": "gid",
|
||||
|
||||
"invitation_base_dn": "ou=invitations,dc=example,dc=com",
|
||||
"invitation_name_attr": "cn",
|
||||
"invited_mail_format": "{}@example.com",
|
||||
"invited_auto_groups": [
|
||||
"cn=email,ou=groups,dc=example,dc=com"
|
||||
],
|
||||
|
||||
"web_address": "https://guichet.example.com",
|
||||
"mail_from": "welcome@example.com",
|
||||
"smtp_server": "smtp.example.com",
|
||||
"smtp_username": "guichet",
|
||||
"smtp_password": "",
|
||||
|
||||
"admin_account": "uid=admin,dc=example,dc=com",
|
||||
"group_can_admin": "gid=admin,ou=groups,dc=example,dc=com",
|
||||
"group_can_invite": ""
|
||||
|
||||
"s3_endpoint": "garage.example.com",
|
||||
"s3_access_key": "",
|
||||
"s3_secret_key": "",
|
||||
"s3_region": "garage",
|
||||
"s3_bucket": "bottin-pictures"
|
||||
}
|
||||
|
121
directory.go
Normal file
121
directory.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"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 := template.Must(template.ParseFiles("templates/layout.html", "templates/directory.html"))
|
||||
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
return
|
||||
}
|
||||
|
||||
templateDirectory.Execute(w, nil)
|
||||
}
|
||||
|
||||
type SearchResult struct {
|
||||
DN string
|
||||
Id string
|
||||
DisplayName string
|
||||
Email string
|
||||
Description string
|
||||
ProfilePicture string
|
||||
}
|
||||
|
||||
type SearchResults struct {
|
||||
Results []SearchResult
|
||||
}
|
||||
|
||||
func handleDirectorySearch(w http.ResponseWriter, r *http.Request) {
|
||||
templateDirectoryResults := template.Must(template.ParseFiles("templates/directory_results.html"))
|
||||
|
||||
//Get input value by user
|
||||
r.ParseMultipartForm(1024)
|
||||
input := strings.TrimSpace(strings.Join(r.Form["query"], ""))
|
||||
|
||||
if r.Method != "POST" || input == "" {
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
//Log to allow the research
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
http.Error(w, "Login required", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
//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)
|
||||
|
||||
sr, err := login.conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
//Transform the researh's result in a correct struct to send JSON
|
||||
results := []SearchResult{}
|
||||
|
||||
for _, values := range sr.Entries {
|
||||
if 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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
search_results := SearchResults{
|
||||
Results: results,
|
||||
}
|
||||
sort.Sort(&search_results)
|
||||
|
||||
templateDirectoryResults.Execute(w, search_results)
|
||||
}
|
||||
|
||||
func ContainsI(a string, b string) bool {
|
||||
return strings.Contains(
|
||||
strings.ToLower(a),
|
||||
strings.ToLower(b),
|
||||
)
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
6
go.mod
6
go.mod
|
@ -7,8 +7,12 @@ require (
|
|||
github.com/emersion/go-smtp v0.12.1
|
||||
github.com/go-ldap/ldap v3.0.3+incompatible
|
||||
github.com/go-ldap/ldap/v3 v3.1.6
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/gorilla/sessions v1.2.0
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/minio/minio-go/v7 v7.0.0
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/stretchr/objx v0.1.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
)
|
||||
|
|
43
go.sum
43
go.sum
|
@ -1,4 +1,6 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
||||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b h1:uhWtEWBHgop1rqEk2klKaxPAkVDCXexai6hSuRQ7Nvs=
|
||||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
||||
|
@ -10,25 +12,66 @@ github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHj
|
|||
github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
|
||||
github.com/go-ldap/ldap/v3 v3.1.6 h1:VTihvB7egSAvU6KOagaiA/EvgJMR2jsjRAVIho2ydBo=
|
||||
github.com/go-ldap/ldap/v3 v3.1.6/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
|
||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
|
||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
|
||||
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
|
||||
github.com/minio/minio-go/v7 v7.0.0 h1:99hRCmsmMi+hKK93C26iPnRQebTsdK8GEx8Xb4XLr7I=
|
||||
github.com/minio/minio-go/v7 v7.0.0/go.mod h1:dJ80Mv2HeGkYLH1sqS/ksz07ON6csH3S6JUMSQ2zAns=
|
||||
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg=
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
|
||||
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
|
|
53
main.go
53
main.go
|
@ -43,6 +43,12 @@ type ConfigFile struct {
|
|||
AdminAccount string `json:"admin_account"`
|
||||
GroupCanInvite string `json:"group_can_invite"`
|
||||
GroupCanAdmin string `json:"group_can_admin"`
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
var configFlag = flag.String("config", "./config.json", "Configuration file path")
|
||||
|
@ -54,47 +60,21 @@ const SESSION_NAME = "guichet_session"
|
|||
var store sessions.Store = nil
|
||||
|
||||
func readConfig() ConfigFile {
|
||||
// Default configuration values for certain fields
|
||||
config_file := ConfigFile{
|
||||
HttpBindAddr: ":9991",
|
||||
LdapServerAddr: "ldap://127.0.0.1:389",
|
||||
LdapTLS: false,
|
||||
|
||||
BaseDN: "dc=example,dc=com",
|
||||
UserBaseDN: "ou=users,dc=example,dc=com",
|
||||
UserNameAttr: "uid",
|
||||
GroupBaseDN: "ou=groups,dc=example,dc=com",
|
||||
GroupNameAttr: "gid",
|
||||
|
||||
InvitationBaseDN: "ou=invitations,dc=example,dc=com",
|
||||
InvitationNameAttr: "cn",
|
||||
InvitedMailFormat: "{}@example.com",
|
||||
InvitedAutoGroups: []string{},
|
||||
|
||||
WebAddress: "https://guichet.example.com",
|
||||
MailFrom: "guichet@example.com",
|
||||
SMTPServer: "smtp.example.com",
|
||||
|
||||
AdminAccount: "uid=admin,dc=example,dc=com",
|
||||
GroupCanInvite: "",
|
||||
GroupCanAdmin: "gid=admin,ou=groups,dc=example,dc=com",
|
||||
}
|
||||
|
||||
_, err := os.Stat(*configFlag)
|
||||
if os.IsNotExist(err) {
|
||||
// Generate default config file
|
||||
log.Printf("Generating default config file as %s", *configFlag)
|
||||
|
||||
bytes, err := json.MarshalIndent(&config_file, "", " ")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(*configFlag, bytes, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return config_file
|
||||
log.Fatalf("Could not find Guichet configuration file at %s. Please create this file, for example starting with config.json.example and customizing it for your deployment.", *configFlag)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -130,8 +110,13 @@ func main() {
|
|||
r := mux.NewRouter()
|
||||
r.HandleFunc("/", handleHome)
|
||||
r.HandleFunc("/logout", handleLogout)
|
||||
|
||||
r.HandleFunc("/profile", handleProfile)
|
||||
r.HandleFunc("/passwd", handlePasswd)
|
||||
r.HandleFunc("/picture/{name}", handleDownloadPicture)
|
||||
|
||||
r.HandleFunc("/directory/search", handleDirectorySearch)
|
||||
r.HandleFunc("/directory", handleDirectory)
|
||||
|
||||
r.HandleFunc("/invite/new_account", handleInviteNewAccount)
|
||||
r.HandleFunc("/invite/send_code", handleInviteSendCode)
|
||||
|
@ -241,7 +226,17 @@ func checkLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
|
|||
login_info.DN,
|
||||
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
||||
requestKind,
|
||||
[]string{"dn", "displayname", "givenname", "sn", "mail", "memberof"},
|
||||
[]string{
|
||||
"dn",
|
||||
"displayname",
|
||||
"givenname",
|
||||
"sn",
|
||||
"mail",
|
||||
"memberof",
|
||||
"description",
|
||||
FIELD_NAME_DIRECTORY_VISIBILITY,
|
||||
FIELD_NAME_PROFILE_PICTURE,
|
||||
},
|
||||
nil)
|
||||
|
||||
sr, err := l.Search(searchRequest)
|
||||
|
|
184
picture.go
Normal file
184
picture.go
Normal file
|
@ -0,0 +1,184 @@
|
|||
package main
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
32
profile.go
32
profile.go
|
@ -16,6 +16,9 @@ type ProfileTplData struct {
|
|||
DisplayName string
|
||||
GivenName string
|
||||
Surname string
|
||||
Visibility string
|
||||
Description string
|
||||
ProfilePicture string
|
||||
}
|
||||
|
||||
func handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -36,25 +39,50 @@ func handleProfile(w http.ResponseWriter, r *http.Request) {
|
|||
data.DisplayName = login.UserEntry.GetAttributeValue("displayname")
|
||||
data.GivenName = login.UserEntry.GetAttributeValue("givenname")
|
||||
data.Surname = login.UserEntry.GetAttributeValue("sn")
|
||||
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" {
|
||||
r.ParseForm()
|
||||
//5MB maximum size files
|
||||
r.ParseMultipartForm(5 << 20)
|
||||
|
||||
data.DisplayName = strings.TrimSpace(strings.Join(r.Form["display_name"], ""))
|
||||
data.GivenName = strings.TrimSpace(strings.Join(r.Form["given_name"], ""))
|
||||
data.Surname = strings.TrimSpace(strings.Join(r.Form["surname"], ""))
|
||||
data.Description = strings.Trim(strings.Join(r.Form["description"], ""), "")
|
||||
visible := strings.TrimSpace(strings.Join(r.Form["visibility"], ""))
|
||||
if visible != "" {
|
||||
visible = "on"
|
||||
}
|
||||
data.Visibility = visible
|
||||
|
||||
profilePicture, err := uploadProfilePicture(w, r, login)
|
||||
if err != nil {
|
||||
data.ErrorMessage = err.Error()
|
||||
}
|
||||
|
||||
if profilePicture != "" {
|
||||
data.ProfilePicture = profilePicture
|
||||
}
|
||||
|
||||
modify_request := ldap.NewModifyRequest(login.Info.DN, nil)
|
||||
modify_request.Replace("displayname", []string{data.DisplayName})
|
||||
modify_request.Replace("givenname", []string{data.GivenName})
|
||||
modify_request.Replace("sn", []string{data.Surname})
|
||||
modify_request.Replace("description", []string{data.Description})
|
||||
modify_request.Replace(FIELD_NAME_DIRECTORY_VISIBILITY, []string{data.Visibility})
|
||||
if data.ProfilePicture != "" {
|
||||
modify_request.Replace(FIELD_NAME_PROFILE_PICTURE, []string{data.ProfilePicture})
|
||||
}
|
||||
|
||||
err := login.conn.Modify(modify_request)
|
||||
err = login.conn.Modify(modify_request)
|
||||
if err != nil {
|
||||
data.ErrorMessage = err.Error()
|
||||
} else {
|
||||
data.Success = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
templateProfile.Execute(w, data)
|
||||
|
|
5
static/javascript/minio.js
Normal file
5
static/javascript/minio.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
document.querySelector('.custom-file-input').addEventListener('change',function(e){
|
||||
var fileName = document.getElementById("image").files[0].name;
|
||||
var nextSibling = e.target.nextElementSibling
|
||||
nextSibling.innerText = fileName
|
||||
})
|
24
static/javascript/search.js
Normal file
24
static/javascript/search.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
var last_id = 0;
|
||||
|
||||
function searchDirectory() {
|
||||
var input = document.getElementById("search").value;
|
||||
if(input){
|
||||
last_id++;
|
||||
var request_id = last_id;
|
||||
|
||||
var data = new FormData();
|
||||
data.append("query", input);
|
||||
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (request_id != last_id) return;
|
||||
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
var result_div = document.getElementById("search-results");
|
||||
result_div.innerHTML = xhttp.responseText;
|
||||
}
|
||||
};
|
||||
xhttp.open("POST", "/directory/search", true);
|
||||
xhttp.send(data);
|
||||
}
|
||||
}
|
23
templates/directory.html
Normal file
23
templates/directory.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
{{define "title"}}Annuaire |{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
<div class="d-flex">
|
||||
<h4>Annuaire</h4>
|
||||
<a class="ml-auto btn btn-info" href="/">Menu principal</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<form>
|
||||
<div class="form-group form-row">
|
||||
<div class="col-sm-2"> </div>
|
||||
<label for="search" class="col-sm-2 col-form-label">Rechercher :</label>
|
||||
<input class="form-control col-sm-4" id="search" name="search" type="text" onkeyup="searchDirectory()" size="20">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="search-results"></div>
|
||||
|
||||
<script src="/static/javascript/search.js"></script>
|
||||
|
||||
{{end}}
|
23
templates/directory_results.html
Normal file
23
templates/directory_results.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
{{if .Results}}
|
||||
{{range .Results}}
|
||||
<div class="card mt-4">
|
||||
<div class="card-body">
|
||||
<div class="float-right">
|
||||
{{if .ProfilePicture}}
|
||||
<a href="/picture/{{.ProfilePicture}}">
|
||||
<img src="/picture/{{.ProfilePicture}}-thumb"/>
|
||||
</a>
|
||||
{{else}}
|
||||
{{end}}
|
||||
</div>
|
||||
<h5 class="card-title">
|
||||
{{.DisplayName}}
|
||||
<code>{{.Id}}@</code>
|
||||
</h5>
|
||||
<p class="card-text">{{.Description}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else}}
|
||||
Aucun résultat.
|
||||
{{end}}
|
|
@ -16,6 +16,7 @@
|
|||
<div class="list-group list-group-flush">
|
||||
<a class="list-group-item list-group-item-action" href="/profile">Modifier mon profil</a>
|
||||
<a class="list-group-item list-group-item-action" href="/passwd">Modifier mon mot de passe</a>
|
||||
<a class="list-group-item list-group-item-action" href="/directory">Annuaire</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -16,27 +16,64 @@
|
|||
Profil enregistré.
|
||||
</div>
|
||||
{{end}}
|
||||
<form method="POST" class="mt-4">
|
||||
<div class="form-group">
|
||||
<form method="POST" class="mt-4" enctype="multipart/form-data">
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-6">
|
||||
<label>Nom d'utilisateur:</label>
|
||||
<input type="text" disabled="true" class="form-control" value="{{ .Status.Info.Username }}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="mail">Adresse e-mail:</label>
|
||||
<input type="text" id="mail" disabled="true" name="mail" class="form-control" value="{{ .Mail }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="display_name">Nom complet:</label>
|
||||
<input type="text" id="display_name" name="display_name" class="form-control" value="{{ .DisplayName }}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
<h4>Informations complémentaires</h4>
|
||||
{{if .ProfilePicture}}
|
||||
<div class="float-right">
|
||||
<a href="/picture/{{.ProfilePicture}}">
|
||||
<img src="/picture/{{.ProfilePicture}}-thumb" />
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="form-group form-check">
|
||||
{{if .Visibility}}
|
||||
<input class="form-check-input" name="visibility" type="checkbox" id="visibility" value="on" checked>
|
||||
{{else}}
|
||||
<input class="form-check-input" name="visibility" type="checkbox" id="visibility">
|
||||
{{end}}
|
||||
<label class="form-check-label" for="visibility">Apparaître sur l'annuaire</label>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-8 input-group mb-3 custom-file">
|
||||
<label for="image">Photo de profil:</label>
|
||||
<input type="file" name="image" class="custom-file-input" id="image">
|
||||
<label class="custom-file-label" for="image">Photo de profil (jpeg, jpg or png)</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="given_name">Prénom:</label>
|
||||
<input type="text" id="given_name" name="given_name" class="form-control" value="{{ .GivenName }}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="surname">Nom de famille:</label>
|
||||
<input type="text" id="surname" name="surname" class="form-control" value="{{ .Surname }}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description">Description</label>
|
||||
<textarea id="description" name="description" class="form-control">{{ .Description }}</textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Enregistrer les modifications</button>
|
||||
</form>
|
||||
<script src="/static/javascript/minio.js"></script>
|
||||
{{end}}
|
||||
|
|
Loading…
Reference in a new issue