diff --git a/api.go b/api.go index 73fd322..e99fce5 100644 --- a/api.go +++ b/api.go @@ -1,8 +1,8 @@ package main import ( - "errors" "encoding/json" + "errors" "fmt" "github.com/gorilla/mux" "net/http" @@ -12,7 +12,7 @@ func handleAPIWebsiteList(w http.ResponseWriter, r *http.Request) { user := RequireUserApi(w, r) if user == nil { - return + return } ctrl, err := NewWebsiteController(user) @@ -40,7 +40,7 @@ func handleAPIWebsiteInspect(w http.ResponseWriter, r *http.Request) { user := RequireUserApi(w, r) if user == nil { - return + return } bucketName := mux.Vars(r)["bucket"] @@ -86,7 +86,7 @@ func handleAPIWebsiteInspect(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPatch { var patch WebsitePatch err := json.NewDecoder(r.Body).Decode(&patch) - if err != nil { + if err != nil { http.Error(w, errors.Join(fmt.Errorf("Can't parse the request body as a website patch JSON"), err).Error(), http.StatusBadRequest) return } @@ -98,7 +98,7 @@ func handleAPIWebsiteInspect(w http.ResponseWriter, r *http.Request) { } else if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return - } + } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(view) @@ -119,7 +119,7 @@ func handleAPIWebsiteInspect(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusNoContent) return - + } http.Error(w, "This method is not implemented for this endpoint", http.StatusNotImplemented) diff --git a/cli.go b/cli.go index df11460..2d45a4c 100644 --- a/cli.go +++ b/cli.go @@ -3,9 +3,9 @@ package main import ( "flag" "fmt" - "os" - "syscall" "golang.org/x/term" + "os" + "syscall" ) var fsCli = flag.NewFlagSet("cli", flag.ContinueOnError) @@ -27,18 +27,18 @@ func cliMain(args []string) { func cliPasswd() { fmt.Print("Password: ") - bytepw, err := term.ReadPassword(int(syscall.Stdin)) - if err != nil { + bytepw, err := term.ReadPassword(int(syscall.Stdin)) + if err != nil { fmt.Println(err) - os.Exit(1) - } - pass := string(bytepw) + os.Exit(1) + } + pass := string(bytepw) hash, err := SSHAEncode(pass) - if err != nil { + if err != nil { fmt.Println(err) - os.Exit(1) - } + os.Exit(1) + } fmt.Println(hash) } diff --git a/garage.go b/garage.go index 236dcbd..c43fd5f 100644 --- a/garage.go +++ b/garage.go @@ -10,7 +10,6 @@ import ( "strings" ) - func gadmin() (*garage.APIClient, context.Context) { // Set Host and other parameters configuration := garage.NewConfiguration() @@ -47,8 +46,6 @@ func grgGetKey(accessKey string) (*garage.KeyInfo, error) { return resp, nil } - - func grgCreateBucket(bucket string) (*garage.BucketInfo, error) { client, ctx := gadmin() @@ -64,7 +61,7 @@ func grgCreateBucket(bucket string) (*garage.BucketInfo, error) { return binfo, nil } -func grgAllowKeyOnBucket(bid, gkey string) (*garage.BucketInfo, error) { +func grgAllowKeyOnBucket(bid, gkey string) (*garage.BucketInfo, error) { client, ctx := gadmin() // Allow user's key @@ -163,10 +160,10 @@ func grgGetBucket(bid string) (*garage.BucketInfo, error) { func grgDeleteBucket(bid string) error { client, ctx := gadmin() - _, err := client.BucketApi.DeleteBucket(ctx, bid).Execute() - if err != nil { + _, err := client.BucketApi.DeleteBucket(ctx, bid).Execute() + if err != nil { log.Println(err) - } + } return err } @@ -189,9 +186,9 @@ func handleWebsiteList(w http.ResponseWriter, r *http.Request) { } ctrl, err := NewWebsiteController(user) - if err != nil { + if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) - return + return } if len(ctrl.PrettyList) > 0 { @@ -203,7 +200,7 @@ func handleWebsiteList(w http.ResponseWriter, r *http.Request) { type WebsiteNewTpl struct { Ctrl *WebsiteController - Err error + Err error } func handleWebsiteNew(w http.ResponseWriter, r *http.Request) { @@ -213,9 +210,9 @@ func handleWebsiteNew(w http.ResponseWriter, r *http.Request) { } ctrl, err := NewWebsiteController(user) - if err != nil { + if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) - return + return } tpl := &WebsiteNewTpl{ctrl, nil} @@ -245,8 +242,8 @@ func handleWebsiteNew(w http.ResponseWriter, r *http.Request) { type WebsiteInspectTpl struct { Describe *WebsiteDescribe - View *WebsiteView - Err error + View *WebsiteView + Err error } func handleWebsiteInspect(w http.ResponseWriter, r *http.Request) { @@ -258,42 +255,42 @@ func handleWebsiteInspect(w http.ResponseWriter, r *http.Request) { } ctrl, err := NewWebsiteController(user) - if err != nil { + if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) - return + return } bucketName := mux.Vars(r)["bucket"] if r.Method == "POST" { r.ParseForm() - action := strings.Join(r.Form["action"],"") + action := strings.Join(r.Form["action"], "") switch action { - case "increase_quota": - _, processErr = ctrl.Patch(bucketName, &WebsitePatch { Size: &user.Quota.WebsiteSizeBursted }) - case "delete_bucket": - processErr = ctrl.Delete(bucketName) - http.Redirect(w, r, "/website", http.StatusFound) - return - default: - processErr = fmt.Errorf("Unknown action") + case "increase_quota": + _, processErr = ctrl.Patch(bucketName, &WebsitePatch{Size: &user.Quota.WebsiteSizeBursted}) + case "delete_bucket": + processErr = ctrl.Delete(bucketName) + http.Redirect(w, r, "/website", http.StatusFound) + return + default: + processErr = fmt.Errorf("Unknown action") } } - + view, err := ctrl.Inspect(bucketName) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) - return + return } - + describe, err := ctrl.Describe() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) - return + return } - tpl := &WebsiteInspectTpl{ describe, view, processErr } + tpl := &WebsiteInspectTpl{describe, view, processErr} tWebsiteInspect := getTemplate("garage_website_inspect.html") tWebsiteInspect.Execute(w, &tpl) diff --git a/login.go b/login.go index 87b7a67..277e3ae 100644 --- a/login.go +++ b/login.go @@ -13,12 +13,12 @@ import ( var ( ErrNotAuthenticatedSession = fmt.Errorf("User has no session") - ErrNotAuthenticatedBasic = fmt.Errorf("User has not sent Authentication Basic information") - ErrNotAuthenticated = fmt.Errorf("User is not authenticated") - ErrWrongLDAPCredentials = fmt.Errorf("LDAP credentials are wrong") - ErrLDAPServerUnreachable = fmt.Errorf("Unable to open the LDAP server") + ErrNotAuthenticatedBasic = fmt.Errorf("User has not sent Authentication Basic information") + ErrNotAuthenticated = fmt.Errorf("User is not authenticated") + ErrWrongLDAPCredentials = fmt.Errorf("LDAP credentials are wrong") + ErrLDAPServerUnreachable = fmt.Errorf("Unable to open the LDAP server") ErrLDAPSearchInternalError = fmt.Errorf("LDAP Search of this user failed with an internal error") - ErrLDAPSearchNotFound = fmt.Errorf("User is authenticated but its associated data can not be found during search") + ErrLDAPSearchNotFound = fmt.Errorf("User is authenticated but its associated data can not be found during search") ) // --- Login Info --- @@ -48,10 +48,10 @@ func NewLoginInfoFromSession(r *http.Request) (*LoginInfo, error) { func NewLoginInfoFromBasicAuth(r *http.Request) (*LoginInfo, error) { username, password, ok := r.BasicAuth() if ok { - login_info := &LoginInfo{ - Username: username, - Password: password, - } + login_info := &LoginInfo{ + Username: username, + Password: password, + } return login_info, nil } @@ -69,8 +69,8 @@ func (li *LoginInfo) DN() string { // --- Login Status --- type LoginStatus struct { - Info *LoginInfo - conn *ldap.Conn + Info *LoginInfo + conn *ldap.Conn } func NewLoginStatus(r *http.Request, login_info *LoginInfo) (*LoginStatus, error) { @@ -109,12 +109,13 @@ func NewLdapCon() (*ldap.Conn, error) { // --- Capabilities --- type Capabilities struct { - CanAdmin bool + CanAdmin bool CanInvite bool } + func NewCapabilities(login *LoginStatus, entry *ldap.Entry) *Capabilities { // Initialize - canAdmin := false + canAdmin := false canInvite := false // Special case for the "admin" account that is de-facto admin @@ -135,19 +136,20 @@ func NewCapabilities(login *LoginStatus, entry *ldap.Entry) *Capabilities { } return &Capabilities{ - CanAdmin: canAdmin, + CanAdmin: canAdmin, CanInvite: canInvite, } } // --- Logged User --- type LoggedUser struct { - Login *LoginStatus + Login *LoginStatus Entry *ldap.Entry Capabilities *Capabilities - Quota *UserQuota - s3key *garage.KeyInfo + Quota *UserQuota + s3key *garage.KeyInfo } + func NewLoggedUser(login *LoginStatus) (*LoggedUser, error) { requestKind := "(objectClass=organizationalPerson)" if strings.EqualFold(login.Info.DN(), config.AdminAccount) { @@ -184,11 +186,11 @@ func NewLoggedUser(login *LoginStatus) (*LoggedUser, error) { } entry := sr.Entries[0] - lu := &LoggedUser { - Login: login, - Entry: entry, + lu := &LoggedUser{ + Login: login, + Entry: entry, Capabilities: NewCapabilities(login, entry), - Quota: NewUserQuotaFromEntry(entry), + Quota: NewUserQuotaFromEntry(entry), } return lu, nil } @@ -251,7 +253,6 @@ func RequireUser(r *http.Request) (*LoggedUser, error) { return nil, ErrNotAuthenticated } - loginStatus, err := NewLoginStatus(r, login_info) if err != nil { return nil, err diff --git a/main.go b/main.go index 9763f53..6553bef 100644 --- a/main.go +++ b/main.go @@ -119,13 +119,13 @@ func main() { } switch os.Args[1] { - case "cli": - cliMain(os.Args[2:]) - case "server": - server(os.Args[2:]) - default: - log.Println("Usage: guichet [server|cli] --help") - os.Exit(1) + case "cli": + cliMain(os.Args[2:]) + case "server": + server(os.Args[2:]) + default: + log.Println("Usage: guichet [server|cli] --help") + os.Exit(1) } } @@ -192,10 +192,9 @@ func logRequest(handler http.Handler) http.Handler { // Page handlers ---- - // --- Home Controller type HomePageData struct { - User *LoggedUser + User *LoggedUser BaseDN string } @@ -208,7 +207,7 @@ func handleHome(w http.ResponseWriter, r *http.Request) { } data := &HomePageData{ - User: user, + User: user, BaseDN: config.BaseDN, } @@ -235,7 +234,7 @@ func handleLogout(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/login", http.StatusFound) } -// --- Login Controller --- +// --- Login Controller --- type LoginFormData struct { Username string WrongUser bool @@ -254,7 +253,7 @@ func handleLogin(w http.ResponseWriter, r *http.Request) { username := strings.Join(r.Form["username"], "") password := strings.Join(r.Form["password"], "") - loginInfo := LoginInfo { username, password } + loginInfo := LoginInfo{username, password} l, err := NewLdapCon() if err != nil { diff --git a/profile.go b/profile.go index 58e7d96..bd7e299 100644 --- a/profile.go +++ b/profile.go @@ -8,7 +8,7 @@ import ( ) type ProfileTplData struct { - User *LoggedUser + User *LoggedUser ErrorMessage string Success bool Mail string @@ -29,7 +29,7 @@ func handleProfile(w http.ResponseWriter, r *http.Request) { } data := &ProfileTplData{ - User: user, + User: user, ErrorMessage: "", Success: false, } diff --git a/quotas.go b/quotas.go index e520f5c..894ea3c 100644 --- a/quotas.go +++ b/quotas.go @@ -5,40 +5,40 @@ import ( "fmt" "strconv" - "github.com/go-ldap/ldap/v3" garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang" + "github.com/go-ldap/ldap/v3" ) const ( // --- Default Quota Values --- - 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_OBJECTS = 10000 // 10k objects - QUOTA_WEBSITE_COUNT = 5 // 5 buckets + QUOTA_WEBSITE_OBJECTS = 10000 // 10k objects + QUOTA_WEBSITE_COUNT = 5 // 5 buckets // --- Per-user overridable fields --- FIELD_QUOTA_WEBSITE_SIZE_BURSTED = "quota_website_size_bursted" - FIELD_QUOTA_WEBSITE_COUNT = "quota_website_count" + FIELD_QUOTA_WEBSITE_COUNT = "quota_website_count" ) type UserQuota struct { - WebsiteCount int64 + WebsiteCount int64 WebsiteSizeDefault int64 WebsiteSizeBursted int64 - WebsiteObjects int64 + WebsiteObjects int64 } func NewUserQuota() *UserQuota { - return &UserQuota { - WebsiteCount: QUOTA_WEBSITE_COUNT, + return &UserQuota{ + WebsiteCount: QUOTA_WEBSITE_COUNT, WebsiteSizeDefault: QUOTA_WEBSITE_SIZE_DEFAULT, WebsiteSizeBursted: QUOTA_WEBSITE_SIZE_BURSTED, - WebsiteObjects: QUOTA_WEBSITE_OBJECTS, + WebsiteObjects: QUOTA_WEBSITE_OBJECTS, } } var ( - ErrQuotaEmpty = fmt.Errorf("No quota is defined for this entry") + ErrQuotaEmpty = fmt.Errorf("No quota is defined for this entry") ErrQuotaInvalid = fmt.Errorf("The defined quota can't be parsed") ) @@ -72,7 +72,7 @@ func NewUserQuotaFromEntry(entry *ldap.Entry) *UserQuota { func (q *UserQuota) DefaultWebsiteQuota() *garage.UpdateBucketRequestQuotas { qr := garage.NewUpdateBucketRequestQuotas() - qr.SetMaxSize(q.WebsiteSizeDefault) + qr.SetMaxSize(q.WebsiteSizeDefault) qr.SetMaxObjects(q.WebsiteSizeBursted) return qr @@ -80,7 +80,7 @@ func (q *UserQuota) DefaultWebsiteQuota() *garage.UpdateBucketRequestQuotas { func (q *UserQuota) WebsiteSizeAdjust(sz int64) int64 { if sz < q.WebsiteSizeDefault { - return q.WebsiteSizeDefault + return q.WebsiteSizeDefault } else if sz > q.WebsiteSizeBursted { return q.WebsiteSizeBursted } else { @@ -102,16 +102,17 @@ func (q *UserQuota) WebsiteSizeBurstedPretty() string { // --- A quota stat we can use type QuotaStat struct { - Current int64 `json:"current"` - Max int64 `json:"max"` - Ratio float64 `json:"ratio"` - Burstable bool `json:"burstable"` + Current int64 `json:"current"` + Max int64 `json:"max"` + Ratio float64 `json:"ratio"` + Burstable bool `json:"burstable"` } + func NewQuotaStat(current, max int64, burstable bool) QuotaStat { - return QuotaStat { - Current: current, - Max: max, - Ratio: float64(current) / float64(max), + return QuotaStat{ + Current: current, + Max: max, + Ratio: float64(current) / float64(max), Burstable: burstable, } } @@ -141,7 +142,7 @@ func prettyValue(v int64) string { if v < 1024 { return fmt.Sprintf("%d Mio", v) } - v = v / 1024 + v = v / 1024 if v < 1024 { return fmt.Sprintf("%d Gio", v) } diff --git a/website.go b/website.go index 6158042..ba432c5 100644 --- a/website.go +++ b/website.go @@ -2,33 +2,31 @@ package main import ( "fmt" + garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang" "sort" "strings" - garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang" ) var ( - ErrWebsiteNotFound = fmt.Errorf("Website not found") - ErrFetchBucketInfo = fmt.Errorf("Failed to fetch bucket information") - ErrWebsiteQuotaReached = fmt.Errorf("Can't create additional websites, quota reached") - ErrEmptyBucketName = fmt.Errorf("You can't create a website with an empty name") - ErrCantCreateBucket = fmt.Errorf("Can't create this bucket. Maybe another bucket already exists with this name or you have an invalid character") - ErrCantAllowKey = fmt.Errorf("Can't allow given key on the target bucket") - ErrCantConfigureBucket = fmt.Errorf("Unable to configure the bucket (activating website, adding quotas, etc.)") - ErrBucketDeleteNotEmpty = fmt.Errorf("You must remove all the files before deleting a bucket") + ErrWebsiteNotFound = fmt.Errorf("Website not found") + ErrFetchBucketInfo = fmt.Errorf("Failed to fetch bucket information") + ErrWebsiteQuotaReached = fmt.Errorf("Can't create additional websites, quota reached") + ErrEmptyBucketName = fmt.Errorf("You can't create a website with an empty name") + ErrCantCreateBucket = fmt.Errorf("Can't create this bucket. Maybe another bucket already exists with this name or you have an invalid character") + ErrCantAllowKey = fmt.Errorf("Can't allow given key on the target bucket") + ErrCantConfigureBucket = fmt.Errorf("Unable to configure the bucket (activating website, adding quotas, etc.)") + ErrBucketDeleteNotEmpty = fmt.Errorf("You must remove all the files before deleting a bucket") ErrBucketDeleteUnfinishedUpload = fmt.Errorf("You must remove all the unfinished multipart uploads before deleting a bucket") ) - - type WebsiteId struct { - Pretty string `json:"name"` - Internal string `json:"-"` - Alt []string `json:"alt_name"` - Expanded bool `json:"expanded"` - Url string `json:"domain"` - + Pretty string `json:"name"` + Internal string `json:"-"` + Alt []string `json:"alt_name"` + Expanded bool `json:"expanded"` + Url string `json:"domain"` } + func NewWebsiteId(id string, aliases []string) *WebsiteId { pretty := id var alt []string @@ -43,16 +41,16 @@ func NewWebsiteId(id string, aliases []string) *WebsiteId { url = fmt.Sprintf("%s.web.deuxfleurs.fr", pretty) } - return &WebsiteId { pretty, id, alt, expanded, url } + return &WebsiteId{pretty, id, alt, expanded, url} } func NewWebsiteIdFromBucketInfo(binfo *garage.BucketInfo) *WebsiteId { return NewWebsiteId(*binfo.Id, binfo.GlobalAliases) } type WebsiteController struct { - User *LoggedUser - WebsiteIdx map[string]*WebsiteId - PrettyList []string + User *LoggedUser + WebsiteIdx map[string]*WebsiteId + PrettyList []string WebsiteCount QuotaStat } @@ -65,7 +63,7 @@ func NewWebsiteController(user *LoggedUser) (*WebsiteController, error) { return nil, err } - for _, bckt := range(keyInfo.Buckets) { + for _, bckt := range keyInfo.Buckets { if len(bckt.GlobalAliases) > 0 { wid := NewWebsiteId(*bckt.Id, bckt.GlobalAliases) idx[wid.Pretty] = wid @@ -77,15 +75,15 @@ func NewWebsiteController(user *LoggedUser) (*WebsiteController, error) { maxW := user.Quota.WebsiteCount quota := NewQuotaStat(int64(len(wlist)), maxW, true) - return &WebsiteController { user, idx, wlist, quota }, nil + return &WebsiteController{user, idx, wlist, quota}, nil } type WebsiteDescribe struct { - AccessKeyId string `json:"access_key_id"` - SecretAccessKey string `json:"secret_access_key"` - AllowedWebsites *QuotaStat `json:"quota_website_count"` - BurstBucketQuotaSize string `json:"burst_bucket_quota_size"` - Websites []*WebsiteId `json:"vhosts"` + AccessKeyId string `json:"access_key_id"` + SecretAccessKey string `json:"secret_access_key"` + AllowedWebsites *QuotaStat `json:"quota_website_count"` + BurstBucketQuotaSize string `json:"burst_bucket_quota_size"` + Websites []*WebsiteId `json:"vhosts"` } func (w *WebsiteController) Describe() (*WebsiteDescribe, error) { @@ -96,14 +94,14 @@ func (w *WebsiteController) Describe() (*WebsiteDescribe, error) { r := make([]*WebsiteId, 0, len(w.PrettyList)) for _, k := range w.PrettyList { - r = append(r, w.WebsiteIdx[k]) + r = append(r, w.WebsiteIdx[k]) } - return &WebsiteDescribe { - *s3key.AccessKeyId, - *s3key.SecretAccessKey, - &w.WebsiteCount, + return &WebsiteDescribe{ + *s3key.AccessKeyId, + *s3key.SecretAccessKey, + &w.WebsiteCount, w.User.Quota.WebsiteSizeBurstedPretty(), - r }, nil + r}, nil } func (w *WebsiteController) Inspect(pretty string) (*WebsiteView, error) { @@ -183,7 +181,6 @@ func (w *WebsiteController) Create(pretty string) (*WebsiteView, error) { ur.SetWebsiteAccess(*wr) ur.SetQuotas(*qr) - binfo, err = grgUpdateBucket(*binfo.Id, ur) if err != nil { return nil, ErrCantConfigureBucket @@ -209,7 +206,7 @@ func (w *WebsiteController) Delete(pretty string) error { if *binfo.Objects > int64(0) { return ErrBucketDeleteNotEmpty - } + } if *binfo.UnfinishedUploads > int32(0) { return ErrBucketDeleteUnfinishedUpload @@ -219,13 +216,10 @@ func (w *WebsiteController) Delete(pretty string) error { return err } - - - type WebsiteView struct { - Name *WebsiteId `json:"identified_as"` - Size QuotaStat `json:"quota_size"` - Files QuotaStat `json:"quota_files"` + Name *WebsiteId `json:"identified_as"` + Size QuotaStat `json:"quota_size"` + Files QuotaStat `json:"quota_files"` } func NewWebsiteView(binfo *garage.BucketInfo) *WebsiteView { @@ -234,7 +228,7 @@ func NewWebsiteView(binfo *garage.BucketInfo) *WebsiteView { wid := NewWebsiteIdFromBucketInfo(binfo) size := NewQuotaStat(*binfo.Bytes, (&q).GetMaxSize(), true) objects := NewQuotaStat(*binfo.Objects, (&q).GetMaxObjects(), false) - return &WebsiteView { wid, size, objects } + return &WebsiteView{wid, size, objects} } type WebsitePatch struct {