Compare commits
3 commits
main
...
feat/aerog
Author | SHA1 | Date | |
---|---|---|---|
aff7efd726 | |||
1a9d750de7 | |||
5dd6419d67 |
11 changed files with 445 additions and 26 deletions
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"http_bind_addr": ":9991",
|
"http_bind_addr": "[::]:9991",
|
||||||
"ldap_server_addr": "ldap://127.0.0.1:389",
|
"ldap_server_addr": "ldap://localhost:389",
|
||||||
|
|
||||||
"base_dn": "dc=bottin,dc=eu",
|
"base_dn": "dc=bottin,dc=eu",
|
||||||
"user_base_dn": "ou=users,dc=bottin,dc=eu",
|
"user_base_dn": "ou=users,dc=bottin,dc=eu",
|
||||||
|
@ -10,28 +10,27 @@
|
||||||
|
|
||||||
"invitation_base_dn": "ou=invitations,dc=bottin,dc=eu",
|
"invitation_base_dn": "ou=invitations,dc=bottin,dc=eu",
|
||||||
"invitation_name_attr": "cn",
|
"invitation_name_attr": "cn",
|
||||||
"invited_mail_format": "{}@example.com",
|
"invited_mail_format": "{}@bottin.eu",
|
||||||
"invited_auto_groups": [
|
"invited_auto_groups": [
|
||||||
"cn=email,ou=groups,dc=bottin,dc=eu"
|
"cn=email,ou=groups,dc=bottin,dc=eu"
|
||||||
],
|
],
|
||||||
|
|
||||||
"web_address": "http://guichet.localhost:9991",
|
"web_address": "https://guichet.bottin.eu",
|
||||||
"mail_from": "welcome@example.com",
|
"mail_from": "welcome@bottin.eu",
|
||||||
"smtp_server": "smtp.example.com",
|
"smtp_server": "smtp.bottin.eu",
|
||||||
"smtp_username": "guichet",
|
"smtp_username": "guichet",
|
||||||
"smtp_password": "",
|
"smtp_password": "",
|
||||||
|
|
||||||
"admin_account": "cn=admin,dc=bottin,dc=eu",
|
"admin_account": "cn=admin,dc=bottin,dc=eu",
|
||||||
"group_can_admin": "gid=admin,ou=groups,dc=bottin,dc=eu",
|
"group_can_admin": "cn=admin,ou=groups,dc=bottin,dc=eu",
|
||||||
"group_can_invite": "",
|
"group_can_invite": "cn=admin,ou=groups,dc=bottin,dc=eu",
|
||||||
|
|
||||||
"s3_admin_endpoint": "localhost:3903",
|
"s3_admin_endpoint": "localhost:3903",
|
||||||
"s3_admin_token": "GlXP43PWH3LuvEGSNxKYzZCyUss8VqZmarBU+HUlrxw=",
|
"s3_admin_token": "<change me>",
|
||||||
|
|
||||||
"s3_endpoint": "localhost",
|
"s3_endpoint": "localhost:3900",
|
||||||
"s3_access_key": "",
|
"s3_access_key": "<change me>",
|
||||||
"s3_secret_key": "",
|
"s3_secret_key": "<change me>",
|
||||||
"s3_region": "garage",
|
"s3_region": "garage",
|
||||||
"s3_bucket": "bottin-pictures"
|
"s3_bucket": "bottin-pictures"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
27
garage.go
27
garage.go
|
@ -61,6 +61,33 @@ func grgCreateBucket(bucket string) (*garage.BucketInfo, error) {
|
||||||
return binfo, nil
|
return binfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func grgCreateLocalBucket(bucket, gkey string) (*garage.BucketInfo, error) {
|
||||||
|
client, ctx := gadmin()
|
||||||
|
|
||||||
|
is_true := true
|
||||||
|
is_false := false
|
||||||
|
|
||||||
|
la := garage.CreateBucketRequestLocalAlias {
|
||||||
|
AccessKeyId: &gkey,
|
||||||
|
Alias: &bucket,
|
||||||
|
Allow: &garage.CreateBucketRequestLocalAliasAllow {
|
||||||
|
Read: &is_true,
|
||||||
|
Write: &is_true,
|
||||||
|
Owner: &is_false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
br := garage.NewCreateBucketRequest()
|
||||||
|
br.SetLocalAlias(la)
|
||||||
|
|
||||||
|
binfo, _, err := client.BucketApi.CreateBucket(ctx).CreateBucketRequest(*br).Execute()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%+v\n", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return binfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
func grgAllowKeyOnBucket(bid, gkey string) (*garage.BucketInfo, error) {
|
func grgAllowKeyOnBucket(bid, gkey string) (*garage.BucketInfo, error) {
|
||||||
client, ctx := gadmin()
|
client, ctx := gadmin()
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
"acl": [
|
"acl": [
|
||||||
"ANONYMOUS::bind:*,ou=users,dc=bottin,dc=eu:",
|
"ANONYMOUS::bind:*,ou=users,dc=bottin,dc=eu:",
|
||||||
"ANONYMOUS::bind:cn=admin,dc=bottin,dc=eu:",
|
"ANONYMOUS::bind:cn=admin,dc=bottin,dc=eu:",
|
||||||
|
"cn=admin,dc=bottin,dc=eu::bind read add modify delete:*:*",
|
||||||
"*,dc=bottin,dc=eu::read:*:* !userpassword",
|
"*,dc=bottin,dc=eu::read:*:* !userpassword",
|
||||||
"cn=admin,dc=bottin,dc=eu::read add modify delete:*:*",
|
"*:cn=admin,ou=groups,dc=bottin,dc=eu:bind read add modify delete:*:*",
|
||||||
"*:cn=admin,ou=groups,dc=bottin,dc=eu:read add modify delete:*:*",
|
|
||||||
|
|
||||||
"ANONYMOUS::bind:*,ou=invitations,dc=bottin,dc=eu:",
|
"ANONYMOUS::bind:*,ou=invitations,dc=bottin,dc=eu:",
|
||||||
"*,ou=invitations,dc=bottin,dc=eu::delete:SELF:*",
|
"*,ou=invitations,dc=bottin,dc=eu::delete:SELF:*",
|
||||||
|
|
|
@ -15,7 +15,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- "./config/bottin.json:/config.json"
|
- "./config/bottin.json:/config.json"
|
||||||
garage:
|
garage:
|
||||||
image: dxflrs/garage:v0.8.2
|
image: dxflrs/garage:v0.9.1
|
||||||
ports:
|
ports:
|
||||||
- "3900:3900"
|
- "3900:3900"
|
||||||
- "3902:3902"
|
- "3902:3902"
|
||||||
|
|
24
login.go
24
login.go
|
@ -111,12 +111,19 @@ func NewLdapCon() (*ldap.Conn, error) {
|
||||||
type Capabilities struct {
|
type Capabilities struct {
|
||||||
CanAdmin bool
|
CanAdmin bool
|
||||||
CanInvite bool
|
CanInvite bool
|
||||||
|
CanUseEmail bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCapabilities(login *LoginStatus, entry *ldap.Entry) *Capabilities {
|
func NewCapabilities(login *LoginStatus, entry *ldap.Entry) *Capabilities {
|
||||||
// Initialize
|
// Initialize
|
||||||
canAdmin := false
|
canAdmin := false
|
||||||
canInvite := false
|
canInvite := false
|
||||||
|
canUseEmail := false
|
||||||
|
|
||||||
|
// Composable logic
|
||||||
|
hasAeroBucketId := false
|
||||||
|
hasAeroBucketName := false
|
||||||
|
hasAeroCryptoRoot := false
|
||||||
|
|
||||||
// Special case for the "admin" account that is de-facto admin
|
// Special case for the "admin" account that is de-facto admin
|
||||||
canAdmin = strings.EqualFold(login.Info.DN(), config.AdminAccount)
|
canAdmin = strings.EqualFold(login.Info.DN(), config.AdminAccount)
|
||||||
|
@ -132,12 +139,22 @@ func NewCapabilities(login *LoginStatus, entry *ldap.Entry) *Capabilities {
|
||||||
canAdmin = true
|
canAdmin = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if strings.EqualFold(attr.Name, FIELD_AEROGRAMME_CRYPTOROOT) {
|
||||||
|
hasAeroCryptoRoot = true
|
||||||
|
} else if strings.EqualFold(attr.Name, FIELD_AEROGRAMME_BUCKET_ID) {
|
||||||
|
hasAeroBucketId = true
|
||||||
|
} else if strings.EqualFold(attr.Name, FIELD_AEROGRAMME_BUCKET_NAME) {
|
||||||
|
hasAeroBucketName = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Boolean logic
|
||||||
|
canUseEmail = hasAeroBucketId && hasAeroBucketName && hasAeroCryptoRoot
|
||||||
|
|
||||||
return &Capabilities{
|
return &Capabilities{
|
||||||
CanAdmin: canAdmin,
|
CanAdmin: canAdmin,
|
||||||
CanInvite: canInvite,
|
CanInvite: canInvite,
|
||||||
|
CanUseEmail: canUseEmail,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,6 +190,10 @@ func NewLoggedUser(login *LoginStatus) (*LoggedUser, error) {
|
||||||
FIELD_NAME_PROFILE_PICTURE,
|
FIELD_NAME_PROFILE_PICTURE,
|
||||||
FIELD_QUOTA_WEBSITE_SIZE_BURSTED,
|
FIELD_QUOTA_WEBSITE_SIZE_BURSTED,
|
||||||
FIELD_QUOTA_WEBSITE_COUNT,
|
FIELD_QUOTA_WEBSITE_COUNT,
|
||||||
|
FIELD_QUOTA_PIM_SIZE_BURSTED,
|
||||||
|
FIELD_AEROGRAMME_CRYPTOROOT,
|
||||||
|
FIELD_AEROGRAMME_BUCKET_ID,
|
||||||
|
FIELD_AEROGRAMME_BUCKET_NAME,
|
||||||
},
|
},
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
|
@ -204,6 +225,9 @@ func (lu *LoggedUser) WelcomeName() string {
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
func (lu *LoggedUser) Email() string {
|
||||||
|
return lu.Entry.GetAttributeValue("mail")
|
||||||
|
}
|
||||||
func (lu *LoggedUser) S3KeyInfo() (*garage.KeyInfo, error) {
|
func (lu *LoggedUser) S3KeyInfo() (*garage.KeyInfo, error) {
|
||||||
var err error
|
var err error
|
||||||
var keyPair *garage.KeyInfo
|
var keyPair *garage.KeyInfo
|
||||||
|
|
3
main.go
3
main.go
|
@ -163,6 +163,9 @@ func server(args []string) {
|
||||||
r.HandleFunc("/website/inspect/{bucket}", handleWebsiteInspect)
|
r.HandleFunc("/website/inspect/{bucket}", handleWebsiteInspect)
|
||||||
r.HandleFunc("/website/vhost/{bucket}", handleWebsiteVhost)
|
r.HandleFunc("/website/vhost/{bucket}", handleWebsiteVhost)
|
||||||
|
|
||||||
|
r.HandleFunc("/pim/setup", handlePimSetup)
|
||||||
|
r.HandleFunc("/pim/inspect", handlePimInspect)
|
||||||
|
|
||||||
r.HandleFunc("/invite/new_account", handleInviteNewAccount)
|
r.HandleFunc("/invite/new_account", handleInviteNewAccount)
|
||||||
r.HandleFunc("/invite/send_code", handleInviteSendCode)
|
r.HandleFunc("/invite/send_code", handleInviteSendCode)
|
||||||
r.HandleFunc("/invitation/{code}", handleInvitationCode)
|
r.HandleFunc("/invitation/{code}", handleInvitationCode)
|
||||||
|
|
211
pim_ctrl.go
Normal file
211
pim_ctrl.go
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
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
|
56
pim_http.go
Normal file
56
pim_http.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PimInspectView struct {
|
||||||
|
User *LoggedUser
|
||||||
|
Debug string
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlePimInspect(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user := RequireUserHtml(w, r)
|
||||||
|
if user == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pim_ctl, err := NewPimBuilder(user).CheckCryptoRoot().CheckBucket().Build()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pim_json, err := json.MarshalIndent(pim_ctl, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
view := PimInspectView {
|
||||||
|
User: user,
|
||||||
|
Debug: string(pim_json),
|
||||||
|
}
|
||||||
|
|
||||||
|
tKey := getTemplate("pim_inspect.html")
|
||||||
|
tKey.Execute(w, view)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlePimSetup(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user := RequireUserHtml(w, r)
|
||||||
|
if user == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := NewPimBuilder(user).CheckCryptoRoot().CheckBucket().LdapUpdate().Build()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.Capabilities.CanUseEmail = true
|
||||||
|
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/pim/inspect", http.StatusFound)
|
||||||
|
}
|
54
quotas.go
54
quotas.go
|
@ -9,16 +9,23 @@ import (
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Note: PIM = Personal Information Manager
|
||||||
const (
|
const (
|
||||||
// --- Default Quota Values ---
|
// --- Default Quota Values Websites ---
|
||||||
QUOTA_WEBSITE_SIZE_DEFAULT = 1024 * 1024 * 50 // 50MB
|
QUOTA_WEBSITE_SIZE_DEFAULT = 1024 * 1024 * 50 // 50MB
|
||||||
QUOTA_WEBSITE_SIZE_BURSTED = 1024 * 1024 * 200 // 200MB
|
QUOTA_WEBSITE_SIZE_BURSTED = 1024 * 1024 * 200 // 200MB
|
||||||
QUOTA_WEBSITE_OBJECTS = 10000 // 10k objects
|
QUOTA_WEBSITE_OBJECTS = 10_000 // 10k objects
|
||||||
QUOTA_WEBSITE_COUNT = 5 // 5 buckets
|
QUOTA_WEBSITE_COUNT = 5 // 5 buckets
|
||||||
|
|
||||||
|
// --- Default Quota Values PIM ---
|
||||||
|
QUOTA_PIM_SIZE_DEFAULT = 1024 * 1024 * 100 // 100MB
|
||||||
|
QUOTA_PIM_SIZE_BURSTED = 1024 * 1024 * 500 // 500MB
|
||||||
|
QUOTA_PIM_OBJECTS = 100_000 // 100k objects
|
||||||
|
|
||||||
// --- Per-user overridable fields ---
|
// --- Per-user overridable fields ---
|
||||||
FIELD_QUOTA_WEBSITE_SIZE_BURSTED = "quota_website_size_bursted"
|
FIELD_QUOTA_WEBSITE_SIZE_BURSTED = "quota_website_size_bursted"
|
||||||
FIELD_QUOTA_WEBSITE_COUNT = "quota_website_count"
|
FIELD_QUOTA_WEBSITE_COUNT = "quota_website_count"
|
||||||
|
FIELD_QUOTA_PIM_SIZE_BURSTED = "quota_pim_size_bursted"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserQuota struct {
|
type UserQuota struct {
|
||||||
|
@ -26,6 +33,9 @@ type UserQuota struct {
|
||||||
WebsiteSizeDefault int64
|
WebsiteSizeDefault int64
|
||||||
WebsiteSizeBursted int64
|
WebsiteSizeBursted int64
|
||||||
WebsiteObjects int64
|
WebsiteObjects int64
|
||||||
|
PimSizeDefault int64
|
||||||
|
PimSizeBursted int64
|
||||||
|
PimObjects int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserQuota() *UserQuota {
|
func NewUserQuota() *UserQuota {
|
||||||
|
@ -34,6 +44,9 @@ func NewUserQuota() *UserQuota {
|
||||||
WebsiteSizeDefault: QUOTA_WEBSITE_SIZE_DEFAULT,
|
WebsiteSizeDefault: QUOTA_WEBSITE_SIZE_DEFAULT,
|
||||||
WebsiteSizeBursted: QUOTA_WEBSITE_SIZE_BURSTED,
|
WebsiteSizeBursted: QUOTA_WEBSITE_SIZE_BURSTED,
|
||||||
WebsiteObjects: QUOTA_WEBSITE_OBJECTS,
|
WebsiteObjects: QUOTA_WEBSITE_OBJECTS,
|
||||||
|
PimSizeDefault: QUOTA_PIM_SIZE_DEFAULT,
|
||||||
|
PimSizeBursted: QUOTA_PIM_SIZE_BURSTED,
|
||||||
|
PimObjects: QUOTA_PIM_OBJECTS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +79,10 @@ func NewUserQuotaFromEntry(entry *ldap.Entry) *UserQuota {
|
||||||
quotas.WebsiteSizeBursted = q
|
quotas.WebsiteSizeBursted = q
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if q, err := entryToQuota(entry, FIELD_QUOTA_PIM_SIZE_BURSTED); err == nil {
|
||||||
|
quotas.PimSizeBursted = q
|
||||||
|
}
|
||||||
|
|
||||||
return quotas
|
return quotas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +95,16 @@ func (q *UserQuota) DefaultWebsiteQuota() *garage.UpdateBucketRequestQuotas {
|
||||||
return qr
|
return qr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *UserQuota) DefaultPimQuota() *garage.UpdateBucketRequestQuotas {
|
||||||
|
qr := garage.NewUpdateBucketRequestQuotas()
|
||||||
|
|
||||||
|
qr.SetMaxSize(q.PimSizeDefault)
|
||||||
|
qr.SetMaxObjects(q.PimObjects)
|
||||||
|
|
||||||
|
return qr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Website getters/setters
|
||||||
func (q *UserQuota) WebsiteSizeAdjust(sz int64) int64 {
|
func (q *UserQuota) WebsiteSizeAdjust(sz int64) int64 {
|
||||||
if sz < q.WebsiteSizeDefault {
|
if sz < q.WebsiteSizeDefault {
|
||||||
return q.WebsiteSizeDefault
|
return q.WebsiteSizeDefault
|
||||||
|
@ -100,6 +127,29 @@ func (q *UserQuota) WebsiteSizeBurstedPretty() string {
|
||||||
return prettyValue(q.WebsiteSizeBursted)
|
return prettyValue(q.WebsiteSizeBursted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PIM getters/setters
|
||||||
|
func (q *UserQuota) PimSizeAdjust(sz int64) int64 {
|
||||||
|
if sz < q.PimSizeDefault {
|
||||||
|
return q.PimSizeDefault
|
||||||
|
} else if sz > q.PimSizeBursted {
|
||||||
|
return q.PimSizeBursted
|
||||||
|
} else {
|
||||||
|
return sz
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *UserQuota) PimObjectAdjust(objs int64) int64 {
|
||||||
|
if objs > q.PimObjects || objs <= 0 {
|
||||||
|
return q.PimObjects
|
||||||
|
} else {
|
||||||
|
return objs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *UserQuota) PimSizeBurstedPretty() string {
|
||||||
|
return prettyValue(q.PimSizeBursted)
|
||||||
|
}
|
||||||
|
|
||||||
// --- A quota stat we can use
|
// --- A quota stat we can use
|
||||||
type QuotaStat struct {
|
type QuotaStat struct {
|
||||||
Current int64 `json:"current"`
|
Current int64 `json:"current"`
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Mon espace sur la toile
|
Mes publications sur la toile
|
||||||
</div>
|
</div>
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
<a class="list-group-item list-group-item-action" href="/website/configure">Mes identifiants</a>
|
<a class="list-group-item list-group-item-action" href="/website/configure">Mes identifiants</a>
|
||||||
|
@ -33,6 +33,22 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Mon espace personnel (email, calendrier, contacts, etc.)
|
||||||
|
</div>
|
||||||
|
<div class="list-group list-group-flush">
|
||||||
|
{{if .User.Capabilities.CanUseEmail}}
|
||||||
|
<a class="list-group-item list-group-item-action disabled" href="#">Accéder à l'interface web</a>
|
||||||
|
<a class="list-group-item list-group-item-action" href="/pim/inspect">Voir les détails</a>
|
||||||
|
{{ else }}
|
||||||
|
<a class="list-group-item list-group-item-action" href="/pim/setup">Créer mon espace</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{if .User.Capabilities.CanInvite}}
|
{{if .User.Capabilities.CanInvite}}
|
||||||
<div class="card mt-3">
|
<div class="card mt-3">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
|
33
templates/pim_inspect.html
Normal file
33
templates/pim_inspect.html
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{{define "title"}}Configurer mConfigurer mon compte email |{{end}}
|
||||||
|
|
||||||
|
{{define "body"}}
|
||||||
|
<div class="d-flex">
|
||||||
|
<h4>Mon adresse email</h4>
|
||||||
|
<a class="ml-auto btn btn-info" href="/">Menu principal</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 mt-3">
|
||||||
|
<div class="alert alert-danger">PAGE DE DEBUG, NON CONFORME POUR UNE MISE EN PRODUCTION</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
--- login info ---
|
||||||
|
email: {{ .User.Email }}
|
||||||
|
username: {{ .User.Login.Info.Username }}
|
||||||
|
password: ********
|
||||||
|
|
||||||
|
--- autodiscovery ---
|
||||||
|
Implemented: RFC6186 DNS SRV autodiscovery + Thunderbird Autoconfig
|
||||||
|
Not implemented: Microsoft Autodiscover + Apple Mobileconfig
|
||||||
|
|
||||||
|
--- manual configuration ---
|
||||||
|
IMAP: imap.saint-ex.deuxfleurs.org:993 (TLS)
|
||||||
|
SMTP: smtp.saint-ex.deuxfleurs.org:465 (TLS)
|
||||||
|
|
||||||
|
--- dump PIM controller ---
|
||||||
|
{{ .Debug }}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{end}}
|
Loading…
Reference in a new issue