aboutsummaryrefslogblamecommitdiff
path: root/api.go
blob: 1ddb4ea86a6af2c5ddde4228e314d82365363dce (plain) (tree)
1
2
3
4
5
6
7
8
9



                   
                       
                
             
                                                                     
                                    
                                




                  
                                                                                  



                                                                                               
                                                                                        












                                                                                      
                                                                        





                                                                                               
                                                                                                          

































                                                                                      
                                                                                                              




                                                                                      
                                                                                                                                     


















                                                                                                                   









                                                                                                        

 


















                                  
                                                                    





































































                                                                                                    

                                
                                                                                 

                      
 

























                                                                                                                                                 
 
                                                      
 
                      










                                                                                                                     
                         

                                     
                           

                                         
                           

                                         
                           
 

                              
 









                                                                                               
 
package main

import (
	//"context"
	"encoding/json"
	"errors"
	"fmt"
	garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"
	"github.com/go-ldap/ldap/v3"
	"github.com/gorilla/mux"
	"log"
	"net/http"
	"strings"
)

func checkLoginAPI(w http.ResponseWriter, r *http.Request) (*LoginStatus, error) {
	username, password, ok := r.BasicAuth()
	if !ok {
		w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return nil, errors.New("Missing or invalid 'Authenticate: Basic' field")
	}
	user_dn := buildUserDN(username)

	login_info := &LoginInfo{
		DN:       user_dn,
		Username: username,
		Password: password,
	}

	l := ldapOpen(w)
	if l == nil {
		log.Println(l)
		http.Error(w, "Internal server error", http.StatusInternalServerError)
		return nil, errors.New("Unable to open LDAP connection")
	}

	err := l.Bind(login_info.DN, login_info.Password)
	if err != nil {
		w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return nil, errors.New("Unable to bind this user+password combination on the LDAP server")
	}

	loginStatus := &LoginStatus{
		Info: login_info,
		conn: l,
	}

	requestKind := "(objectClass=organizationalPerson)"

	if strings.EqualFold(login_info.DN, config.AdminAccount) {
		requestKind = "(objectclass=*)"
	}
	searchRequest := ldap.NewSearchRequest(
		login_info.DN,
		ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
		requestKind,
		[]string{
			"dn",
			"displayname",
			"givenname",
			"sn",
			"mail",
			"memberof",
			"description",
			"garage_s3_access_key",
			FIELD_NAME_DIRECTORY_VISIBILITY,
			FIELD_NAME_PROFILE_PICTURE,
		},
		nil)

	sr, err := l.Search(searchRequest)
	if err != nil {
		log.Println(err)
		http.Error(w, "Internal server error", http.StatusInternalServerError)
		return nil, errors.New("Unable to search essential information about the logged user on LDAP")
	}

	if len(sr.Entries) != 1 {
		log.Println(fmt.Sprintf("Unable to find entry for %s", login_info.DN))
		http.Error(w, "Internal server error", http.StatusInternalServerError)
		return nil, errors.New("Not enough or too many entries for this user in the LDAP directory (expect a unique result)")
	}

	loginStatus.UserEntry = sr.Entries[0]

	loginStatus.CanAdmin = strings.EqualFold(loginStatus.Info.DN, config.AdminAccount)
	loginStatus.CanInvite = false
	for _, attr := range loginStatus.UserEntry.Attributes {
		if strings.EqualFold(attr.Name, "memberof") {
			for _, group := range attr.Values {
				if config.GroupCanInvite != "" && strings.EqualFold(group, config.GroupCanInvite) {
					loginStatus.CanInvite = true
				}
				if config.GroupCanAdmin != "" && strings.EqualFold(group, config.GroupCanAdmin) {
					loginStatus.CanAdmin = true
				}
			}
		}
	}

	return loginStatus, nil
}

func checkLoginAndS3API(w http.ResponseWriter, r *http.Request) (*LoginStatus, *garage.KeyInfo, error) {
	login, err := checkLoginAPI(w, r)
	if err != nil {
		return nil, nil, err
	}
	keyPair, err := checkS3(login)
	return login, keyPair, err
}

type ApiQuotaView struct {
	files *uint64
	size  *uint64
}

type ApiBucketView struct {
	global *bool
	max    *ApiQuotaView
	used   *ApiQuotaView
}

type BucketRequest struct {
	s3key      *garage.KeyInfo
	bucketName string
	bucketId   string
	global     bool
	http       *http.Request
}

func handleAPIGarageBucket(w http.ResponseWriter, r *http.Request) {
	br, err := buildBucketRequest(w, r)
	if err != nil {
		return
	}

	if r.Method == http.MethodPatch {
		patchGarageBucket(w, br)
		return
	}

	if r.Method == http.MethodGet {
		getGarageBucket(w, br)
		return
	}

	http.Error(w, "This method is not implemented for this endpoint", http.StatusNotImplemented)
	return
}

func buildBucketRequest(w http.ResponseWriter, r *http.Request) (*BucketRequest, error) {
	_, s3key, err := checkLoginAndS3API(w, r)
	if err != nil {
		http.Error(w, "Unable to connect on LDAP", http.StatusUnauthorized)
		return nil, err
	}

	// FETCH BUCKET ID by iterating over buckets owned by this key
	bucketName := mux.Vars(r)["bucket"]
	var bucketId *string
	var global *bool

findBucketIdLoop:
	for _, bucket := range s3key.Buckets {
		for _, localAlias := range bucket.LocalAliases {
			if localAlias == bucketName {
				bucketId = bucket.Id
				*global = false
				break findBucketIdLoop
			}
		}
		for _, globalAlias := range bucket.GlobalAliases {
			if globalAlias == bucketName {
				bucketId = bucket.Id
				*global = true
				break findBucketIdLoop
			}
		}
	}

	if bucketId == nil || global == nil {
		http.Error(w, "Bucket not found in this account", http.StatusNotFound)
		return nil, errors.New("Unable to fetch bucket ID")
	}

	return &BucketRequest{
		s3key:      s3key,
		bucketName: bucketName,
		bucketId:   *bucketId,
		global:     *global,
		http:       r,
	}, nil
}

func patchGarageBucket(w http.ResponseWriter, br *BucketRequest) {
	var err error

	// DECODE BODY
	var queuedChange ApiBucketView
	decoder := json.NewDecoder(br.http.Body)
	err = decoder.Decode(&queuedChange)
	if err != nil {
		log.Println(err)
		http.Error(w, "Unable to decode the body", http.StatusBadRequest)
		return
	}

	// SET THE GLOBAL FLAG
	if queuedChange.global != nil {
		if *queuedChange.global && !br.global {
			_, err = grgAddGlobalAlias(br.bucketId, br.bucketName)
			if err != nil {
				http.Error(w, "Unable to add the requested name as global alias for this bucket", http.StatusInternalServerError)
				return
			}
			_, err = grgDelLocalAlias(br.bucketId, *br.s3key.AccessKeyId, br.bucketName)
			if err != nil {
				http.Error(w, "Unable to remove the local alias for this bucket", http.StatusInternalServerError)
				return
			}
		} else if !*queuedChange.global && br.global {
			grgAddLocalAlias(br.bucketId, *br.s3key.AccessKeyId, br.bucketName)
			if err != nil {
				http.Error(w, "Unable to add the requested name as local alias for this bucket", http.StatusInternalServerError)
				return
			}
			grgDelGlobalAlias(br.bucketId, br.bucketName)
			if err != nil {
				http.Error(w, "Unable to remove the global alias for this bucket", http.StatusInternalServerError)
				return
			}
		}
	}

	// CHECK IF QUOTA MUST BE ADDED TO THIS BUCKET

	// VALIDATE IT
	// --- global ---
	// 1. can be true, false, or nil (use pointers)
	// 2. if nil do nothing
	// 3. if false, throw "not yet implemented" (501)
	// 4. if true, check that the bucket name does not exist yet in the global namespace, throw "forbidden" (403)
	// --- quota.size ---
	// 1. if no quota on the bucket + this field is none, set to 50MB
	// 2. if lower than 50MB, set to 50MB. If higher than 200MB, set to 200MB
	// --- quota.files ---
	// 1. if no quota on the bucket + this field is none, set to 10k
	// 2. if lower than 10k, set to 10k. If higher than 40k, set to 40k
	// READ BODY JSON

	// IF BODY.GLOBAL is not NONE
	// DO: Add an alias

	// IF BODY.QUOTA.SIZE is not NONE
	// DO: Change quota

	// IF BODY.QUOTA.FILE is not NONE
	// DO: Change quota

	getGarageBucket(w, br)
}

func getGarageBucket(w http.ResponseWriter, br *BucketRequest) {
	// FETCH AN UPDATED BUCKET VIEW
	bucket, err := grgGetBucket(br.bucketId)
	if err != nil {
		http.Error(w, "Unable to fetch bucket details", http.StatusInternalServerError)
		return
	}

	// BUILD A VIEW
	log.Println(bucket)
}