diff options
author | Quentin <quentin@dufour.io> | 2023-04-19 13:11:46 +0000 |
---|---|---|
committer | Quentin <quentin@dufour.io> | 2023-04-19 13:11:46 +0000 |
commit | 1e75c21b65021da0c3c5a8be9be12114a2327464 (patch) | |
tree | 978a49da113a050c60bd8a803e0d641255f82ede /garage.go | |
parent | 02670ba6a60af8db1818d6d3adaafc7810d14689 (diff) | |
parent | 83ed187dbc6d1ef5f08d74aa57a7d17e907581c9 (diff) | |
download | guichet-1e75c21b65021da0c3c5a8be9be12114a2327464.tar.gz guichet-1e75c21b65021da0c3c5a8be9be12114a2327464.zip |
Merge pull request 'Manage Garage Websites from Guichet' (#19) from website into main
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/guichet/pulls/19
Diffstat (limited to 'garage.go')
-rw-r--r-- | garage.go | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/garage.go b/garage.go new file mode 100644 index 0000000..1ae02e4 --- /dev/null +++ b/garage.go @@ -0,0 +1,251 @@ +package main + +import ( + "context" + "errors" + "fmt" + garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang" + "github.com/go-ldap/ldap/v3" + "github.com/gorilla/mux" + "log" + "net/http" + "strings" +) + +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 +} + +type keyView struct { + Status *LoginStatus + Key *garage.KeyInfo +} + +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) +} + +type webListView struct { + Status *LoginStatus + 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") + 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) +} + +type webInspectView struct { + Status *LoginStatus + Key *garage.KeyInfo + Bucket *garage.BucketInfo + IndexDoc string + ErrorDoc string + MaxObjects int64 + MaxSize int64 + UsedSizePct float64 +} + +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) +} |