package main import ( "errors" "fmt" "os/exec" "strings" "slices" garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang" "github.com/go-ldap/ldap/v3" ) const ( FIELD_AEROGRAMME_CRYPTOROOT = "aero_cryptoroot" FIELD_AEROGRAMME_BUCKET_ID = "aero_bucket_id" FIELD_AEROGRAMME_BUCKET_NAME = "aero_bucket" LOCAL_ALIAS_NAME = "aerogramme" ) var ( ErrPimBuilderDirty = fmt.Errorf("builder is dirty.") ErrPimBucketLocalAliasNotFound = fmt.Errorf("local alias does not exist in garage or points to the wrong bucket.") ErrPimBucketIdEmpty = fmt.Errorf("missing bucket ID in LDAP.") ErrPimBucketNameEmpty = fmt.Errorf("missing bucket local garage alias in LDAP.") ErrPimBucketInfoNotFetched = fmt.Errorf("bucket info has not been fetched.") ErrPimCryptoRootEmpty = fmt.Errorf("missing cryptoroot in LDAP.") ErrPimCantCreateBucket = fmt.Errorf("unable to create PIM bucket.") ) type PimBuilder struct { user *LoggedUser cryptoroot string bucketId string bucketName string bucketInfo *garage.BucketInfo dirty bool errors []error } func NewPimBuilder(user *LoggedUser) *PimBuilder { return &PimBuilder { user: user, cryptoroot: user.Entry.GetAttributeValue(FIELD_AEROGRAMME_CRYPTOROOT), bucketId: user.Entry.GetAttributeValue(FIELD_AEROGRAMME_BUCKET_ID), bucketName: user.Entry.GetAttributeValue(FIELD_AEROGRAMME_BUCKET_NAME), bucketInfo: nil, dirty: false, errors: make([]error, 0), } } func (pm *PimBuilder) CheckCryptoRoot() *PimBuilder { if pm.cryptoroot == "" { cmd := exec.Command("./aerogramme", "tools", "crypto-root", "new-clear-text") var out strings.Builder cmd.Stdout = &out err := cmd.Run() if err != nil { pm.errors = append(pm.errors, err) return pm } pm.cryptoroot = out.String() pm.dirty = true } return pm } func (pm *PimBuilder) CheckBucket() *PimBuilder { keyInfo, err := pm.user.S3KeyInfo() if err != nil { pm.errors = append(pm.errors, err) return pm } if pm.bucketId == "" { candidateName := LOCAL_ALIAS_NAME var bInfo *garage.BucketInfo var err error err = nil for _, ext := range []string{"", "-1", "-2", "-3", "-4", "-5"} { candidateName = LOCAL_ALIAS_NAME + ext bInfo, err = grgCreateLocalBucket(candidateName, *keyInfo.AccessKeyId) if err == nil { break } } if err != nil { pm.errors = append(pm.errors, ErrPimCantCreateBucket) return pm } qr := pm.user.Quota.DefaultPimQuota() ur := garage.NewUpdateBucketRequest() ur.SetQuotas(*qr) bInfo, err = grgUpdateBucket(*bInfo.Id, ur) if err != nil { pm.errors = append(pm.errors, err) return pm } pm.bucketId = *bInfo.Id pm.bucketName = candidateName pm.bucketInfo = bInfo pm.dirty = true } else { binfo, err := grgGetBucket(pm.bucketId) if err != nil { pm.errors = append(pm.errors, err) return pm } pm.bucketInfo = binfo //@TODO find my key, check that pm.bucketName exists in bucketLocalAliases nameFound := false for _, k := range binfo.Keys { if *k.AccessKeyId != *keyInfo.AccessKeyId { // not my key continue } if slices.Contains(k.BucketLocalAliases, pm.bucketName) { nameFound = true break } } if !nameFound { pm.errors = append(pm.errors, ErrPimBucketLocalAliasNotFound) return pm } } return pm } func (pm *PimBuilder) LdapUpdate() *PimBuilder { if len(pm.errors) > 0 { return pm } modify_request := ldap.NewModifyRequest(pm.user.Login.Info.DN(), nil) modify_request.Replace(FIELD_AEROGRAMME_CRYPTOROOT, []string{pm.cryptoroot}) modify_request.Replace(FIELD_AEROGRAMME_BUCKET_NAME, []string{pm.bucketName}) modify_request.Replace(FIELD_AEROGRAMME_BUCKET_ID, []string{pm.bucketId}) err := pm.user.Login.conn.Modify(modify_request) if err != nil { pm.errors = append(pm.errors, err) return pm } pm.dirty = false return pm } func (pm *PimBuilder) Build() (*PimController, error) { // checks if pm.dirty { pm.errors = append(pm.errors, ErrPimBuilderDirty) } if pm.bucketId == "" { pm.errors = append(pm.errors, ErrPimBucketIdEmpty) } if pm.bucketName == "" { pm.errors = append(pm.errors, ErrPimBucketNameEmpty) } if pm.bucketInfo == nil { pm.errors = append(pm.errors, ErrPimBucketInfoNotFetched) } if pm.cryptoroot == "" { pm.errors = append(pm.errors, ErrPimCryptoRootEmpty) } if len(pm.errors) > 0 { err := errors.New("PIM Builder failed") for _, iterErr := range pm.errors { err = errors.Join(err, iterErr) } return nil, err } // quotas q := pm.bucketInfo.GetQuotas() size := NewQuotaStat(*pm.bucketInfo.Bytes, (&q).GetMaxSize(), true) objects := NewQuotaStat(*pm.bucketInfo.Objects, (&q).GetMaxObjects(), false) // final object pim_ctl := &PimController { BucketId: pm.bucketId, BucketName: pm.bucketName, Size: size, Files: objects, user: pm.user, bucketInfo: pm.bucketInfo, cryptoroot: pm.cryptoroot, } return pim_ctl, nil } // --- Controller --- type PimController struct { BucketId string `json:"bucket_id"` BucketName string `json:"bucket_name"` Size QuotaStat `json:"quota_size"` Files QuotaStat `json:"quota_files"` user *LoggedUser bucketInfo *garage.BucketInfo cryptoroot string } //@FIXME Implement quota bursting