aboutsummaryrefslogblamecommitdiff
path: root/pim_ctrl.go
blob: 8589536cc2f299b2f42f7527eeb78908dd2276a0 (plain) (tree)


















































































































































































































                                                                                                                           
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