Manage Garage Websites from Guichet #19

Merged
quentin merged 8 commits from website into main 2023-04-19 13:11:47 +00:00
2 changed files with 182 additions and 182 deletions
Showing only changes of commit 83ed187dbc - Show all commits

336
garage.go
View file

@ -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
View file

@ -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,
}, },