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