Manage Garage Websites from Guichet #19
2 changed files with 182 additions and 182 deletions
336
garage.go
336
garage.go
|
@ -1,116 +1,114 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"context"
|
||||||
"log"
|
"errors"
|
||||||
"net/http"
|
"fmt"
|
||||||
"context"
|
garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func gadmin() (*garage.APIClient, context.Context) {
|
func gadmin() (*garage.APIClient, context.Context) {
|
||||||
// Set Host and other parameters
|
// Set Host and other parameters
|
||||||
configuration := garage.NewConfiguration()
|
configuration := garage.NewConfiguration()
|
||||||
configuration.Host = config.S3AdminEndpoint
|
configuration.Host = config.S3AdminEndpoint
|
||||||
|
|
||||||
// We can now generate a client
|
// We can now generate a client
|
||||||
client := garage.NewAPIClient(configuration)
|
client := garage.NewAPIClient(configuration)
|
||||||
|
|
||||||
// Authentication is handled through the context pattern
|
// Authentication is handled through the context pattern
|
||||||
ctx := context.WithValue(context.Background(), garage.ContextAccessToken, config.S3AdminToken)
|
ctx := context.WithValue(context.Background(), garage.ContextAccessToken, config.S3AdminToken)
|
||||||
return client, ctx
|
return client, ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func grgCreateKey(name string) (*garage.KeyInfo, error) {
|
func grgCreateKey(name string) (*garage.KeyInfo, error) {
|
||||||
client, ctx := gadmin()
|
client, ctx := gadmin()
|
||||||
|
|
||||||
kr := garage.AddKeyRequest{Name: &name}
|
kr := garage.AddKeyRequest{Name: &name}
|
||||||
resp, _, err := client.KeyApi.AddKey(ctx).AddKeyRequest(kr).Execute()
|
resp, _, err := client.KeyApi.AddKey(ctx).AddKeyRequest(kr).Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%+v\n", err)
|
fmt.Printf("%+v\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func grgGetKey(accessKey string) (*garage.KeyInfo, error) {
|
func grgGetKey(accessKey string) (*garage.KeyInfo, error) {
|
||||||
client, ctx := gadmin()
|
client, ctx := gadmin()
|
||||||
|
|
||||||
resp, _, err := client.KeyApi.GetKey(ctx, accessKey).Execute()
|
resp, _, err := client.KeyApi.GetKey(ctx, accessKey).Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%+v\n", err)
|
fmt.Printf("%+v\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func grgCreateWebsite(gkey, bucket string) (*garage.BucketInfo, error) {
|
func grgCreateWebsite(gkey, bucket string) (*garage.BucketInfo, error) {
|
||||||
client, ctx := gadmin()
|
client, ctx := gadmin()
|
||||||
|
|
||||||
br := garage.NewCreateBucketRequest()
|
br := garage.NewCreateBucketRequest()
|
||||||
br.SetGlobalAlias(bucket)
|
br.SetGlobalAlias(bucket)
|
||||||
|
|
||||||
// Create Bucket
|
// Create Bucket
|
||||||
binfo, _, err := client.BucketApi.CreateBucket(ctx).CreateBucketRequest(*br).Execute()
|
binfo, _, err := client.BucketApi.CreateBucket(ctx).CreateBucketRequest(*br).Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%+v\n", err)
|
fmt.Printf("%+v\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow user's key
|
// Allow user's key
|
||||||
ar := garage.AllowBucketKeyRequest{
|
ar := garage.AllowBucketKeyRequest{
|
||||||
BucketId: *binfo.Id,
|
BucketId: *binfo.Id,
|
||||||
AccessKeyId: gkey,
|
AccessKeyId: gkey,
|
||||||
Permissions: *garage.NewAllowBucketKeyRequestPermissions(true, true, true),
|
Permissions: *garage.NewAllowBucketKeyRequestPermissions(true, true, true),
|
||||||
}
|
}
|
||||||
binfo, _, err = client.BucketApi.AllowBucketKey(ctx).AllowBucketKeyRequest(ar).Execute()
|
binfo, _, err = client.BucketApi.AllowBucketKey(ctx).AllowBucketKeyRequest(ar).Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%+v\n", err)
|
fmt.Printf("%+v\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose website and set quota
|
// Expose website and set quota
|
||||||
wr := garage.NewUpdateBucketRequestWebsiteAccess()
|
wr := garage.NewUpdateBucketRequestWebsiteAccess()
|
||||||
wr.SetEnabled(true)
|
wr.SetEnabled(true)
|
||||||
wr.SetIndexDocument("index.html")
|
wr.SetIndexDocument("index.html")
|
||||||
wr.SetErrorDocument("error.html")
|
wr.SetErrorDocument("error.html")
|
||||||
|
|
||||||
qr := garage.NewUpdateBucketRequestQuotas()
|
qr := garage.NewUpdateBucketRequestQuotas()
|
||||||
qr.SetMaxSize(1024 * 1024 * 50) // 50MB
|
qr.SetMaxSize(1024 * 1024 * 50) // 50MB
|
||||||
qr.SetMaxObjects(10000) //10k objects
|
qr.SetMaxObjects(10000) //10k objects
|
||||||
|
|
||||||
ur := garage.NewUpdateBucketRequest()
|
ur := garage.NewUpdateBucketRequest()
|
||||||
ur.SetWebsiteAccess(*wr)
|
ur.SetWebsiteAccess(*wr)
|
||||||
ur.SetQuotas(*qr)
|
ur.SetQuotas(*qr)
|
||||||
|
|
||||||
binfo, _, err = client.BucketApi.UpdateBucket(ctx, *binfo.Id).UpdateBucketRequest(*ur).Execute()
|
binfo, _, err = client.BucketApi.UpdateBucket(ctx, *binfo.Id).UpdateBucketRequest(*ur).Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%+v\n", err)
|
fmt.Printf("%+v\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return updated binfo
|
// Return updated binfo
|
||||||
return binfo, nil
|
return binfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func grgGetBucket(bid string) (*garage.BucketInfo, error) {
|
func grgGetBucket(bid string) (*garage.BucketInfo, error) {
|
||||||
client, ctx := gadmin()
|
client, ctx := gadmin()
|
||||||
|
|
||||||
resp, _, err := client.BucketApi.GetBucketInfo(ctx, bid).Execute()
|
resp, _, err := client.BucketApi.GetBucketInfo(ctx, bid).Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%+v\n", err)
|
fmt.Printf("%+v\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func checkLoginAndS3(w http.ResponseWriter, r *http.Request) (*LoginStatus, *garage.KeyInfo, error) {
|
func checkLoginAndS3(w http.ResponseWriter, r *http.Request) (*LoginStatus, *garage.KeyInfo, error) {
|
||||||
login := checkLogin(w, r)
|
login := checkLogin(w, r)
|
||||||
if login == nil {
|
if login == nil {
|
||||||
|
@ -118,134 +116,136 @@ func checkLoginAndS3(w http.ResponseWriter, r *http.Request) (*LoginStatus, *gar
|
||||||
}
|
}
|
||||||
|
|
||||||
keyID := login.UserEntry.GetAttributeValue("garage_s3_access_key")
|
keyID := login.UserEntry.GetAttributeValue("garage_s3_access_key")
|
||||||
if keyID == "" {
|
if keyID == "" {
|
||||||
keyPair, err := grgCreateKey(login.Info.Username)
|
keyPair, err := grgCreateKey(login.Info.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return login, nil, err
|
return login, nil, err
|
||||||
}
|
}
|
||||||
modify_request := ldap.NewModifyRequest(login.Info.DN, nil)
|
modify_request := ldap.NewModifyRequest(login.Info.DN, nil)
|
||||||
modify_request.Replace("garage_s3_access_key", []string{*keyPair.AccessKeyId})
|
modify_request.Replace("garage_s3_access_key", []string{*keyPair.AccessKeyId})
|
||||||
// @FIXME compatibility feature for bagage (SFTP+webdav)
|
// @FIXME compatibility feature for bagage (SFTP+webdav)
|
||||||
// you can remove it once bagage will be updated to fetch the key from garage directly
|
// 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.
|
// or when bottin will be able to dynamically fetch it.
|
||||||
modify_request.Replace("garage_s3_secret_key", []string{*keyPair.SecretAccessKey})
|
modify_request.Replace("garage_s3_secret_key", []string{*keyPair.SecretAccessKey})
|
||||||
err = login.conn.Modify(modify_request)
|
err = login.conn.Modify(modify_request)
|
||||||
return login, keyPair, err
|
return login, keyPair, err
|
||||||
}
|
}
|
||||||
// Note: we could simply return the login info, but LX asked we do not
|
// Note: we could simply return the login info, but LX asked we do not
|
||||||
// store the secrets in LDAP in the future.
|
// store the secrets in LDAP in the future.
|
||||||
keyPair, err := grgGetKey(keyID)
|
keyPair, err := grgGetKey(keyID)
|
||||||
return login, keyPair, err
|
return login, keyPair, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type keyView struct {
|
type keyView struct {
|
||||||
Status *LoginStatus
|
Status *LoginStatus
|
||||||
Key *garage.KeyInfo
|
Key *garage.KeyInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGarageKey(w http.ResponseWriter, r *http.Request) {
|
func handleGarageKey(w http.ResponseWriter, r *http.Request) {
|
||||||
login, s3key, err := checkLoginAndS3(w, r)
|
login, s3key, err := checkLoginAndS3(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
view := keyView{Status: login, Key: s3key}
|
view := keyView{Status: login, Key: s3key}
|
||||||
|
|
||||||
tKey := getTemplate("garage_key.html")
|
tKey := getTemplate("garage_key.html")
|
||||||
tKey.Execute(w, &view)
|
tKey.Execute(w, &view)
|
||||||
}
|
}
|
||||||
|
|
||||||
type webListView struct {
|
type webListView struct {
|
||||||
Status *LoginStatus
|
Status *LoginStatus
|
||||||
Key *garage.KeyInfo
|
Key *garage.KeyInfo
|
||||||
}
|
}
|
||||||
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")
|
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)
|
tWebsiteList.Execute(w, &view)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGarageWebsiteNew(w http.ResponseWriter, r *http.Request) {
|
func handleGarageWebsiteNew(w http.ResponseWriter, r *http.Request) {
|
||||||
_, s3key, err := checkLoginAndS3(w, r)
|
_, s3key, err := checkLoginAndS3(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tWebsiteNew := getTemplate("garage_website_new.html")
|
tWebsiteNew := getTemplate("garage_website_new.html")
|
||||||
if r.Method == "POST" {
|
if r.Method == "POST" {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
log.Println(r.Form)
|
log.Println(r.Form)
|
||||||
|
|
||||||
bucket := strings.Join(r.Form["bucket"], "")
|
bucket := strings.Join(r.Form["bucket"], "")
|
||||||
if bucket == "" {
|
if bucket == "" {
|
||||||
bucket = strings.Join(r.Form["bucket2"], "")
|
bucket = strings.Join(r.Form["bucket2"], "")
|
||||||
}
|
}
|
||||||
if bucket == "" {
|
if bucket == "" {
|
||||||
log.Println("Form empty")
|
log.Println("Form empty")
|
||||||
// @FIXME we need to return the error to the user
|
// @FIXME we need to return the error to the user
|
||||||
tWebsiteNew.Execute(w, nil)
|
tWebsiteNew.Execute(w, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
binfo, err := grgCreateWebsite(*s3key.AccessKeyId, bucket)
|
binfo, err := grgCreateWebsite(*s3key.AccessKeyId, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
// @FIXME we need to return the error to the user
|
// @FIXME we need to return the error to the user
|
||||||
tWebsiteNew.Execute(w, nil)
|
tWebsiteNew.Execute(w, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/garage/website/b/" + *binfo.Id, http.StatusFound)
|
http.Redirect(w, r, "/garage/website/b/"+*binfo.Id, http.StatusFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tWebsiteNew.Execute(w, nil)
|
tWebsiteNew.Execute(w, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
type webInspectView struct {
|
type webInspectView struct {
|
||||||
Status *LoginStatus
|
Status *LoginStatus
|
||||||
Key *garage.KeyInfo
|
Key *garage.KeyInfo
|
||||||
Bucket *garage.BucketInfo
|
Bucket *garage.BucketInfo
|
||||||
IndexDoc string
|
IndexDoc string
|
||||||
ErrorDoc string
|
ErrorDoc string
|
||||||
MaxObjects int64
|
MaxObjects int64
|
||||||
MaxSize int64
|
MaxSize int64
|
||||||
UsedSizePct float64
|
UsedSizePct float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGarageWebsiteInspect(w http.ResponseWriter, r *http.Request) {
|
func handleGarageWebsiteInspect(w http.ResponseWriter, r *http.Request) {
|
||||||
login, s3key, err := checkLoginAndS3(w, r)
|
login, s3key, err := checkLoginAndS3(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketId := mux.Vars(r)["bucket"]
|
bucketId := mux.Vars(r)["bucket"]
|
||||||
binfo, err := grgGetBucket(bucketId)
|
binfo, err := grgGetBucket(bucketId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wc := binfo.GetWebsiteConfig()
|
wc := binfo.GetWebsiteConfig()
|
||||||
q := binfo.GetQuotas()
|
q := binfo.GetQuotas()
|
||||||
|
|
||||||
view := webInspectView {
|
view := webInspectView{
|
||||||
Status: login,
|
Status: login,
|
||||||
Key: s3key,
|
Key: s3key,
|
||||||
Bucket: binfo,
|
Bucket: binfo,
|
||||||
IndexDoc: (&wc).GetIndexDocument(),
|
IndexDoc: (&wc).GetIndexDocument(),
|
||||||
ErrorDoc: (&wc).GetErrorDocument(),
|
ErrorDoc: (&wc).GetErrorDocument(),
|
||||||
MaxObjects: (&q).GetMaxObjects(),
|
MaxObjects: (&q).GetMaxObjects(),
|
||||||
MaxSize: (&q).GetMaxSize(),
|
MaxSize: (&q).GetMaxSize(),
|
||||||
}
|
}
|
||||||
|
|
||||||
tWebsiteInspect := getTemplate("garage_website_inspect.html")
|
tWebsiteInspect := getTemplate("garage_website_inspect.html")
|
||||||
tWebsiteInspect.Execute(w, &view)
|
tWebsiteInspect.Execute(w, &view)
|
||||||
}
|
}
|
||||||
|
|
26
main.go
26
main.go
|
@ -48,8 +48,8 @@ type ConfigFile struct {
|
||||||
GroupCanInvite string `json:"group_can_invite"`
|
GroupCanInvite string `json:"group_can_invite"`
|
||||||
GroupCanAdmin string `json:"group_can_admin"`
|
GroupCanAdmin string `json:"group_can_admin"`
|
||||||
|
|
||||||
S3AdminEndpoint string `json:"s3_admin_endpoint"`
|
S3AdminEndpoint string `json:"s3_admin_endpoint"`
|
||||||
S3AdminToken string `json:"s3_admin_token"`
|
S3AdminToken string `json:"s3_admin_token"`
|
||||||
|
|
||||||
S3Endpoint string `json:"s3_endpoint"`
|
S3Endpoint string `json:"s3_endpoint"`
|
||||||
S3AccessKey string `json:"s3_access_key"`
|
S3AccessKey string `json:"s3_access_key"`
|
||||||
|
@ -105,12 +105,12 @@ func readConfig() ConfigFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTemplate(name string) *template.Template {
|
func getTemplate(name string) *template.Template {
|
||||||
return template.Must(template.New("layout.html").Funcs(template.FuncMap {
|
return template.Must(template.New("layout.html").Funcs(template.FuncMap{
|
||||||
"contains": strings.Contains,
|
"contains": strings.Contains,
|
||||||
}).ParseFiles(
|
}).ParseFiles(
|
||||||
templatePath+"/layout.html",
|
templatePath+"/layout.html",
|
||||||
templatePath+"/"+name,
|
templatePath+"/"+name,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -137,10 +137,10 @@ func main() {
|
||||||
r.HandleFunc("/directory/search", handleDirectorySearch)
|
r.HandleFunc("/directory/search", handleDirectorySearch)
|
||||||
r.HandleFunc("/directory", handleDirectory)
|
r.HandleFunc("/directory", handleDirectory)
|
||||||
|
|
||||||
r.HandleFunc("/garage/key", handleGarageKey)
|
r.HandleFunc("/garage/key", handleGarageKey)
|
||||||
r.HandleFunc("/garage/website", handleGarageWebsiteList)
|
r.HandleFunc("/garage/website", handleGarageWebsiteList)
|
||||||
r.HandleFunc("/garage/website/new", handleGarageWebsiteNew)
|
r.HandleFunc("/garage/website/new", handleGarageWebsiteNew)
|
||||||
r.HandleFunc("/garage/website/b/{bucket}", handleGarageWebsiteInspect)
|
r.HandleFunc("/garage/website/b/{bucket}", handleGarageWebsiteInspect)
|
||||||
|
|
||||||
r.HandleFunc("/invite/new_account", handleInviteNewAccount)
|
r.HandleFunc("/invite/new_account", handleInviteNewAccount)
|
||||||
r.HandleFunc("/invite/send_code", handleInviteSendCode)
|
r.HandleFunc("/invite/send_code", handleInviteSendCode)
|
||||||
|
@ -260,7 +260,7 @@ func checkLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
|
||||||
"mail",
|
"mail",
|
||||||
"memberof",
|
"memberof",
|
||||||
"description",
|
"description",
|
||||||
"garage_s3_access_key",
|
"garage_s3_access_key",
|
||||||
FIELD_NAME_DIRECTORY_VISIBILITY,
|
FIELD_NAME_DIRECTORY_VISIBILITY,
|
||||||
FIELD_NAME_PROFILE_PICTURE,
|
FIELD_NAME_PROFILE_PICTURE,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue