diff --git a/.env b/.env index c3ad56e5a0eaf479bbc8dc936237d18f33027263..136c084dc864439919807fa41d03818fa2605f87 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ -HOSTNAME=sdk-go.127.0.0.1.nip.io +HOSTNAME=cookies.127.0.0.1.nip.io ADMIN_GROUP=ADMINS CLIENT_GROUP=CLIENTS diff --git a/.vscode/launch.json b/.vscode/launch.json index 675315aee8464d6565297f5e27935e32d72ae7f2..2440ecd5e625e8ebd533766c7cf026146c1d7c23 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "name": "Debug SDK-GO with Mock OAuth2", + "name": "Debug cookies with Mock OAuth2", "type": "go", "request": "launch", "mode": "debug", @@ -14,7 +14,7 @@ "host": "127.0.0.1", "program": "${workspaceFolder}/main.go", "env": { - "REDIRECT_URL": "https://sdk-go.127.0.0.1.nip.io:1443/OAuth2Callback", + "REDIRECT_URL": "https://cookies.127.0.0.1.nip.io:1443/OAuth2Callback", "CLIENT_ID": "foo", "CLIENT_SECRET": "bar", "AUTH_URL": "http://localhost:8090/auth", @@ -22,14 +22,14 @@ "USERINFO_URL": "http://localhost:8090/admininfo", "LOGOUT_URL": "/", "ADMIN_GROUP": "ADMINS", - "CLIENT_GROUP": "CLIENTS", - "HOSTNAME": "sdk-go.127.0.0.1.nip.io" + "CLIENT_GROUP": "SELLERS", + "HOSTNAME": "cookies.127.0.0.1.nip.io" }, "args": ["-debug", "-https_port=1443"], "showLog": true }, { - "name": "Debug SDK-GO with Sign&Go", + "name": "Debug cookies with Sign&Go", "type": "go", "request": "launch", "mode": "debug", diff --git a/dev_certificates/domains.ext b/dev_certificates/domains.ext index 5dd16fe513dab498d46d4313df2030e9c8f8ff5f..8005765b851c49bae8810ebc5757c1f22ba1ba15 100644 --- a/dev_certificates/domains.ext +++ b/dev_certificates/domains.ext @@ -6,4 +6,4 @@ subjectAltName = @alt_names DNS.1 = localhost DNS.2 = *.127.0.0.1.nip.io DNS.3 = *.127.0.0.1.nip.io:1443 -DNS.4 = sdk-go.127.0.0.1.nip.io:1443 \ No newline at end of file +DNS.4 = cookies.127.0.0.1.nip.io:1443 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 704a92a1756dcefe34e9381ddc5bec9c1c03cd66..1ca6f4e74cb85dac427202aeec01d913b5bd0109 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,8 @@ version: "2.4" services: - sdk-go-container: - image: sdk-go + cookies-container: + image: cookies # command: -debug restart: unless-stopped volumes: diff --git a/go.mod b/go.mod index 56b55fc33876678f7475ede29ecb39cd313cf7b2..51cafcaee7859e5ff613a2cce1d0c8bb3fa1ef42 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,10 @@ -module forge.grandlyon.com/systemes-dinformation/project-template/sdk-go +module forge.grandlyon.com/apoyen2/cookies go 1.16 require ( - github.com/golang/protobuf v1.5.2 // indirect + forge.grandlyon.com/systemes-dinformation/project-template/sdk-go v0.0.0-20210423090737-6a5da2a2e760 github.com/jinzhu/gorm v1.9.16 - github.com/mattn/go-sqlite3 v1.14.7 // indirect - github.com/oschwald/maxminddb-golang v1.8.0 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b - golang.org/x/net v0.0.0-20210421230115-4e50805a0758 // indirect golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78 - golang.org/x/sys v0.0.0-20210421221651-33663a62ff08 // indirect - google.golang.org/appengine v1.6.7 // indirect ) diff --git a/go.sum b/go.sum index 9152525a42350ab9a5f207c3cfb9e9742735e13d..f03ff7da7ffa9a5d584b57824fda9d31a83609df 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +forge.grandlyon.com/systemes-dinformation/project-template/sdk-go v0.0.0-20210423090737-6a5da2a2e760 h1:BtwKfUq/2XUMNriTBfTTaqlyIZbFWT2iFn0ak/BfM4A= +forge.grandlyon.com/systemes-dinformation/project-template/sdk-go v0.0.0-20210423090737-6a5da2a2e760/go.mod h1:CauoLzJ+7J9NCwoWeKwsKfAbJik9P2w2qmZA3dS/cYU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= diff --git a/internal/models/bankAccounts.go b/internal/models/bankAccounts.go deleted file mode 100644 index 4ccba66e46b2caf0808bd231bf412342e384735d..0000000000000000000000000000000000000000 --- a/internal/models/bankAccounts.go +++ /dev/null @@ -1,231 +0,0 @@ -package models - -import ( - "encoding/json" - "net/http" - "strconv" - "strings" - - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/internal/auth" -) - -// HandleBankAccounts handle API call on BankAccounts -func (d *DataHandler) HandleBankAccounts(w http.ResponseWriter, r *http.Request) { - id, _ := strconv.Atoi(strings.TrimPrefix(r.URL.Path, "/api/BankAccounts/")) - switch method := r.Method; method { - case "GET": - switch auth.GetLoggedUserTechnical(w, r).Role { - case "ADMIN": - d.getBankAccountAdmin(w, r, id) - case "BANKER": - d.getBankAccountBanker(w, r, id) - case "CLIENT": - d.getBankAccountClient(w, r, id) - default: - http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) - } - case "POST": - switch auth.GetLoggedUserTechnical(w, r).Role { - case "ADMIN": - d.postBankAccountAdmin(w, r, id) - case "BANKER": - d.postBankAccountBanker(w, r, id) - case "CLIENT": - http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) - default: - http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) - } - case "PUT": - switch auth.GetLoggedUserTechnical(w, r).Role { - case "ADMIN": - d.putBankAccountAdmin(w, r, id) - case "BANKER": - d.putBankAccountBanker(w, r, id) - case "CLIENT": - http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) - default: - http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) - } - case "DELETE": - switch auth.GetLoggedUserTechnical(w, r).Role { - case "ADMIN": - d.deleteBankAccountAdmin(w, r, id) - case "BANKER": - d.deleteBankAccountBanker(w, r, id) - case "CLIENT": - http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) - default: - http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) - } - default: - http.Error(w, "method not allowed", 400) - } -} - -func (d *DataHandler) getBankAccountAdmin(w http.ResponseWriter, r *http.Request, id int) { - if id != 0 { - var o BankAccount - if err := d.db.Preload("Operations").First(&o, id).Error; err != nil { - http.Error(w, ErrorIDDoesNotExist, http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(o) - } else { - var o []BankAccount - d.db.Preload("Operations").Find(&o) - json.NewEncoder(w).Encode(o) - } -} - -func (d *DataHandler) getBankAccountBanker(w http.ResponseWriter, r *http.Request, id int) { - user := d.getLoggedUser(w, r).(UserBanker) - if id != 0 { - var o BankAccount - if err := d.db.Preload("Operations").First(&o, id).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - // Check that the bank account belong to a one of the banker's client - var userClient UserClient - if err := d.db.Where(reqIDAndBankerID, o.UserClientID, user.ID).First(&userClient).Error; err != nil { - http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) - return - } - json.NewEncoder(w).Encode(o) - } else { - var o []BankAccount - d.db.Preload("Operations").Where("user_client_id IN (?)", d.db.Table("user_clients").Select("id").Where("user_banker_id = ?", user.ID).QueryExpr()).Find(&o) - json.NewEncoder(w).Encode(o) - } -} - -func (d *DataHandler) getBankAccountClient(w http.ResponseWriter, r *http.Request, id int) { - user := d.getLoggedUser(w, r).(UserClient) - if id != 0 { - var o BankAccount - if err := d.db.Preload("Operations").Where("id = ? AND user_client_id = ?", id, user.ID).First(&o).Error; err != nil { - http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) - return - } - json.NewEncoder(w).Encode(o) - } else { - var o []BankAccount - d.db.Preload("Operations").Where("user_client_id = ?", user.ID).Find(&o) - json.NewEncoder(w).Encode(o) - } -} - -func (d *DataHandler) postBankAccountAdmin(w http.ResponseWriter, r *http.Request, id int) { - var o BankAccount - err := json.NewDecoder(r.Body).Decode(&o) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - if o.UserClientID == 0 { - http.Error(w, ErrorUserIDIsMissing, http.StatusInternalServerError) - } - d.db.Create(&o) - d.db.Last(&o) - json.NewEncoder(w).Encode(o) -} - -func (d *DataHandler) postBankAccountBanker(w http.ResponseWriter, r *http.Request, id int) { - user := d.getLoggedUser(w, r).(UserBanker) - var o BankAccount - err := json.NewDecoder(r.Body).Decode(&o) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - if o.UserClientID == 0 { - http.Error(w, ErrorUserIDIsMissing, http.StatusInternalServerError) - - } - var userClient UserClient - if err := d.db.Where(reqIDAndBankerID, o.UserClientID, user.ID).First(&userClient).Error; err != nil { - http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) - return - } - - d.db.Create(&o) - d.db.Last(&o) - json.NewEncoder(w).Encode(o) -} - -func (d *DataHandler) putBankAccountAdmin(w http.ResponseWriter, r *http.Request, id int) { - var o BankAccount - if err := d.db.Preload("Operations").First(&o, id).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - var bankAccount BankAccount - err := json.NewDecoder(r.Body).Decode(&bankAccount) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - o.BankOverdraft = bankAccount.BankOverdraft - o.Type = bankAccount.Type - if o.UserClientID == 0 { - http.Error(w, ErrorUserIDIsMissing, http.StatusInternalServerError) - } - d.db.Save(&o) - json.NewEncoder(w).Encode(o) -} - -func (d *DataHandler) putBankAccountBanker(w http.ResponseWriter, r *http.Request, id int) { - user := d.getLoggedUser(w, r).(UserBanker) - var o BankAccount - if err := d.db.Preload("Operations").First(&o, id).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - var bankAccount BankAccount - err := json.NewDecoder(r.Body).Decode(&bankAccount) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - o.BankOverdraft = bankAccount.BankOverdraft - o.Type = bankAccount.Type - if o.UserClientID == 0 { - http.Error(w, ErrorUserIDIsMissing, http.StatusInternalServerError) - } - var userClient UserClient - if err := d.db.Where(reqIDAndBankerID, o.UserClientID, user.ID).First(&userClient).Error; err != nil { - http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) - return - } - d.db.Save(&o) - json.NewEncoder(w).Encode(o) -} - -func (d *DataHandler) deleteBankAccountAdmin(w http.ResponseWriter, r *http.Request, id int) { - if id != 0 { - var o BankAccount - if err := d.db.First(&o, id).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - d.db.Delete(&o) - } else { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - } -} - -func (d *DataHandler) deleteBankAccountBanker(w http.ResponseWriter, r *http.Request, id int) { - user := d.getLoggedUser(w, r).(UserBanker) - if id != 0 { - var o BankAccount - if err := d.db.First(&o, id).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - var userClient UserClient - if err := d.db.Where(reqIDAndBankerID, o.UserClientID, user.ID).First(&userClient).Error; err != nil { - http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) - return - } - d.db.Delete(&o) - } else { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - } -} diff --git a/internal/models/bankers.go b/internal/models/bankers.go deleted file mode 100644 index 71efa328456cbcbb91825bcd07e31bad6f6c8a23..0000000000000000000000000000000000000000 --- a/internal/models/bankers.go +++ /dev/null @@ -1,162 +0,0 @@ -package models - -import ( - "encoding/json" - "net/http" - "strconv" - "strings" - - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/internal/auth" -) - -// HandleBankers handle API calls on Bankers -func (d *DataHandler) HandleBankers(w http.ResponseWriter, r *http.Request) { - id, _ := strconv.Atoi(strings.TrimPrefix(r.URL.Path, "/api/UserBankers/")) - switch method := r.Method; method { - case "GET": - switch auth.GetLoggedUserTechnical(w, r).Role { - case "ADMIN": - d.getBankerAdmin(w, r, id) - case "BANKER": - d.getBankerBanker(w, r, id) - case "CLIENT": - d.getBankerClient(w, r, id) - default: - http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) - } - case "POST": - switch auth.GetLoggedUserTechnical(w, r).Role { - case "ADMIN": - d.postBankerAdmin(w, r, id) - case "BANKER", "CLIENT": - http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) - default: - http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) - } - - case "PUT": - switch auth.GetLoggedUserTechnical(w, r).Role { - case "ADMIN": - d.putBankerAdmin(w, r, id) - case "BANKER", "CLIENT": - http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) - default: - http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) - } - case "DELETE": - switch auth.GetLoggedUserTechnical(w, r).Role { - case "ADMIN": - d.deleteBankerAdmin(w, r, id) - case "BANKER", "CLIENT": - http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) - default: - http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) - } - - default: - http.Error(w, "method not allowed", 400) - } -} - -func (d *DataHandler) getBankerAdmin(w http.ResponseWriter, r *http.Request, id int) { - if id != 0 { - var o UserBanker - if err := d.db.Preload("UserClients").Where(reqID, id).First(&o).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(o) - } else { - var o []UserBanker - d.db.Preload("UserClients").Find(&o) - json.NewEncoder(w).Encode(o) - } -} - -func (d *DataHandler) getBankerBanker(w http.ResponseWriter, r *http.Request, id int) { - user := d.getLoggedUser(w, r).(UserBanker) - if id != 0 { - var o UserBanker - if err := d.db.Preload("UserClients").Where(reqID, id).First(&o).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - if o.ID != user.ID { - http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) - return - } - json.NewEncoder(w).Encode(o) - } else { - http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) - } -} - -func (d *DataHandler) getBankerClient(w http.ResponseWriter, r *http.Request, id int) { - user := d.getLoggedUser(w, r).(UserClient) - if id != 0 && int(user.ID) == id { - var userClient UserClient - if err := d.db.Where(reqUserID, user.ID).First(&userClient).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - var o UserBanker - if err := d.db.Where(reqID, userClient.UserBankerID).First(&o).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(o) - } else { - http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) - } -} - -func (d *DataHandler) postBankerAdmin(w http.ResponseWriter, r *http.Request, id int) { - var o UserBanker - err := json.NewDecoder(r.Body).Decode(&o) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - // check userID is not already present in DB - var client UserClient - if err := d.db.Where(reqUserID, o.UserID).First(&client).Error; err == nil { - http.Error(w, "UserID is already bind to a Client", http.StatusInternalServerError) - return - } - var banker UserBanker - if err := d.db.Where(reqUserID, o.UserID).First(&banker).Error; err == nil { - http.Error(w, "UserID is already bind to a Banker", http.StatusInternalServerError) - return - } - d.db.Create(&o) - d.db.Last(&o) - json.NewEncoder(w).Encode(o) -} - -func (d *DataHandler) putBankerAdmin(w http.ResponseWriter, r *http.Request, id int) { - var o UserBanker - if err := d.db.Preload("UserClients").Where(reqID, id).First(&o).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - var updatedBanker UserBanker - err := json.NewDecoder(r.Body).Decode(&updatedBanker) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - o.Name = updatedBanker.Name - d.db.Save(&o) - json.NewEncoder(w).Encode(o) -} - -func (d *DataHandler) deleteBankerAdmin(w http.ResponseWriter, r *http.Request, id int) { - if id != 0 { - var o UserBanker - if err := d.db.First(&o, id).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - d.db.Delete(&o) - } else { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - } -} diff --git a/internal/models/clients.go b/internal/models/clients.go deleted file mode 100644 index 98ac1ff0474c1b5d3bde3979f4976ae4c030c826..0000000000000000000000000000000000000000 --- a/internal/models/clients.go +++ /dev/null @@ -1,225 +0,0 @@ -package models - -import ( - "encoding/json" - "net/http" - "strconv" - "strings" - - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/internal/auth" -) - -// HandleClients expose the UserClients API -func (d *DataHandler) HandleClients(w http.ResponseWriter, r *http.Request) { - id, _ := strconv.Atoi(strings.TrimPrefix(r.URL.Path, "/api/UserClients/")) - switch method := r.Method; method { - case "GET": - - switch auth.GetLoggedUserTechnical(w, r).Role { - case "ADMIN": - d.getClientAdmin(w, r, id) - case "BANKER": - d.getClientBanker(w, r, id) - case "CLIENT": - d.getClientClient(w, r, id) - default: - http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) - } - case "POST": - switch auth.GetLoggedUserTechnical(w, r).Role { - case "ADMIN": - d.postClientAdmin(w, r, id) - case "BANKER": - d.postClientBanker(w, r, id) - case "CLIENT": - http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) - default: - http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) - } - case "PUT": - switch auth.GetLoggedUserTechnical(w, r).Role { - case "ADMIN": - d.putClientAdmin(w, r, id) - case "BANKER": - d.putClientBanker(w, r, id) - case "CLIENT": - http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) - default: - http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) - } - case "DELETE": - switch auth.GetLoggedUserTechnical(w, r).Role { - case "ADMIN": - d.deleteClientAdmin(w, r, id) - case "BANKER": - d.deleteClientBanker(w, r, id) - case "CLIENT": - http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) - default: - http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) - } - default: - http.Error(w, "method not allowed", 400) - } -} - -func (d *DataHandler) getClientAdmin(w http.ResponseWriter, r *http.Request, id int) { - if id != 0 { - var o UserClient - if err := d.db.Preload("BankAccounts").Where(reqID, id).First(&o).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(o) - } else { - var o []UserClient - d.db.Preload("BankAccounts").Find(&o) - json.NewEncoder(w).Encode(o) - } -} - -func (d *DataHandler) getClientBanker(w http.ResponseWriter, r *http.Request, id int) { - user := d.getLoggedUser(w, r).(UserBanker) - if id != 0 { - var o UserClient - if err := d.db.Preload("BankAccounts").Where(reqID, id).First(&o).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - if o.UserBankerID != user.ID { - http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) - return - } - json.NewEncoder(w).Encode(o) - } else { - var o []UserClient - d.db.Preload("BankAccounts").Where("user_banker_id = ?", user.ID).Find(&o) - json.NewEncoder(w).Encode(o) - } -} - -func (d *DataHandler) getClientClient(w http.ResponseWriter, r *http.Request, id int) { - user := d.getLoggedUser(w, r).(UserClient) - if id != 0 && int(user.ID) == id { - var o UserClient - if err := d.db.Preload("BankAccounts").Where(reqID, id).First(&o).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(o) - } else if id == 0 { - var o []UserClient - d.db.Preload("BankAccounts").Where(reqID, user.ID).Find(&o) - json.NewEncoder(w).Encode(o) - } else { - http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) - return - } -} - -func (d *DataHandler) postClientAdmin(w http.ResponseWriter, r *http.Request, id int) { - var o UserClient - err := json.NewDecoder(r.Body).Decode(&o) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - // check userID is not already present in DB - var client UserClient - if err := d.db.Where(reqUserID, o.UserID).First(&client).Error; err == nil { - http.Error(w, "UserID is already bind to a Client", http.StatusInternalServerError) - return - } - var banker UserBanker - if err := d.db.Where(reqUserID, o.UserID).First(&banker).Error; err == nil { - http.Error(w, "UserID is already bind to a Banker", http.StatusInternalServerError) - return - } - d.db.Create(&o) - d.db.Last(&o) - json.NewEncoder(w).Encode(o) -} - -func (d *DataHandler) postClientBanker(w http.ResponseWriter, r *http.Request, id int) { - user := d.getLoggedUser(w, r).(UserBanker) - var o UserClient - err := json.NewDecoder(r.Body).Decode(&o) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - o.UserBankerID = user.ID - d.db.Create(&o) - d.db.Last(&o) - json.NewEncoder(w).Encode(o) -} - -func (d *DataHandler) putClientAdmin(w http.ResponseWriter, r *http.Request, id int) { - var o UserClient - if err := d.db.Preload("BankAccounts").Where(reqID, id).First(&o).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - var updatedUser UserClient - err := json.NewDecoder(r.Body).Decode(&updatedUser) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - o.Name = updatedUser.Name - o.UserBankerID = updatedUser.UserBankerID - d.db.Save(&o) - json.NewEncoder(w).Encode(o) -} - -func (d *DataHandler) putClientBanker(w http.ResponseWriter, r *http.Request, id int) { - user := d.getLoggedUser(w, r).(UserBanker) - var o UserClient - if err := d.db.Preload("BankAccounts").Where(reqID, id).First(&o).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - var updatedUser UserClient - err := json.NewDecoder(r.Body).Decode(&updatedUser) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - o.Name = updatedUser.Name - o.UserBankerID = user.ID - d.db.Save(&o) - json.NewEncoder(w).Encode(o) -} - -func (d *DataHandler) deleteClientAdmin(w http.ResponseWriter, r *http.Request, id int) { - if id != 0 { - var o UserClient - if err := d.db.Where(reqID, id).First(&o).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - // Delete bank accounts of user - d.db.Where("user_client_id = ?", o.ID).Delete(&BankAccount{}) - - d.db.Delete(&o) - } else { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - } -} - -func (d *DataHandler) deleteClientBanker(w http.ResponseWriter, r *http.Request, id int) { - user := d.getLoggedUser(w, r).(UserBanker) - if id != 0 { - var o UserClient - if err := d.db.Where(reqID, id).First(&o).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - if o.UserBankerID != user.ID { - http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) - return - } - // Delete bank accounts of user - d.db.Where("user_client_id = ?", o.ID).Delete(&BankAccount{}) - - d.db.Delete(&o) - } else { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - } -} diff --git a/internal/models/models.go b/internal/models/models.go index 3691ee9f7395221612b99fcb85c8106a211c6b54..64a02fd04590130a6df42458e5df218a673ff693 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -3,9 +3,7 @@ package models import ( "net/http" "strings" - "time" - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/internal/auth" "github.com/jinzhu/gorm" // Needed for sqlite @@ -48,10 +46,6 @@ func NewDataHandler() *DataHandler { db.LogMode(true) // Migrate the schema - db.AutoMigrate(&UserClient{}) - db.AutoMigrate(&UserBanker{}) - db.AutoMigrate(&BankAccount{}) - db.AutoMigrate(&Operation{}) return &DataHandler{db: db} } @@ -59,88 +53,32 @@ func NewDataHandler() *DataHandler { func (d *DataHandler) ProcessAPI(w http.ResponseWriter, r *http.Request) { api := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/"), "/")[0] switch api { - case "UserClients": - d.HandleClients(w, r) - case "UserBankers": - d.HandleBankers(w, r) - case "BankAccounts": - d.HandleBankAccounts(w, r) - case "Operations": - d.HandleOperations(w, r) } -} - -// UserBanker has many UserClient -type UserBanker struct { - ID uint `gorm:"primary_key"` - CreatedAt time.Time `json:"-"` - UpdatedAt time.Time `json:"-"` - DeletedAt *time.Time `json:"-"` - UserID int `gorm:"not null;unique"` - Name string - UserClients []UserClient -} - -// UserClient has many BankAccounts and one UserBanker -type UserClient struct { - ID uint `gorm:"primary_key"` - CreatedAt time.Time `json:"-"` - UpdatedAt time.Time `json:"-"` - DeletedAt *time.Time `json:"-"` - UserID int `gorm:"not null;unique"` - Name string - UserBankerID uint - BankAccounts []BankAccount -} -// BankAccount belongs to an UserClient -type BankAccount struct { - ID uint `gorm:"primary_key"` - CreatedAt time.Time `json:"-"` - UpdatedAt time.Time `json:"-"` - DeletedAt *time.Time `json:"-"` - Number string - UserClientID uint - Type string - Amount int - BankOverdraft int - Operations []Operation `gorm:"foreignkey:Debtor"` } -// Operation reprensent a bank transaction registered in a BankAccount -type Operation struct { - ID uint `gorm:"primary_key"` - Debtor uint - CreatedAt time.Time `json:"-"` - UpdatedAt time.Time `json:"-"` - DeletedAt *time.Time `json:"-"` - Amount int - Date time.Time - Creditor uint -} - -func (d *DataHandler) getLoggedUser(w http.ResponseWriter, r *http.Request) interface{} { - user := auth.GetLoggedUserTechnical(w, r) - if user.Role != "" && (user.Role == "BANKER") { - var o UserBanker - if err := d.db.Where(reqUserID, user.ID).First(&o).Error; err != nil { - o := UserBanker{UserID: user.ID, Name: user.Login} - d.db.Create(&o) - d.db.Where(reqUserID, user.ID).First(&o) - return o - } - return o - } else if user.Role != "" && (user.Role == "CLIENT") { - var o UserClient - if err := d.db.Where(reqUserID, user.ID).First(&o).Error; err != nil { - o := UserClient{UserID: user.ID, Name: user.Login} - d.db.Create(&o) - d.db.Where(reqUserID, user.ID).First(&o) - return o - } - - return o - } - - return nil -} +// func (d *DataHandler) getLoggedUser(w http.ResponseWriter, r *http.Request) interface{} { +// user := auth.GetLoggedUserTechnical(w, r) +// if user.Role != "" && (user.Role == "BAKER") { +// var o UserBaker +// if err := d.db.Where(reqUserID, user.ID).First(&o).Error; err != nil { +// o := UserBaker{UserID: user.ID, Name: user.Login} +// d.db.Create(&o) +// d.db.Where(reqUserID, user.ID).First(&o) +// return o +// } +// return o +// } else if user.Role != "" && (user.Role == "SELLER") { +// var o UserSeller +// if err := d.db.Where(reqUserID, user.ID).First(&o).Error; err != nil { +// o := UserSeller{UserID: user.ID, Name: user.Login} +// d.db.Create(&o) +// d.db.Where(reqUserID, user.ID).First(&o) +// return o +// } + +// return o +// } + +// return nil +// } diff --git a/internal/models/operations.go b/internal/models/operations.go deleted file mode 100644 index bab4aa35c24e18334ff44efb5bd5bd84b907dad1..0000000000000000000000000000000000000000 --- a/internal/models/operations.go +++ /dev/null @@ -1,135 +0,0 @@ -package models - -import ( - "encoding/json" - "net/http" - "strconv" - "strings" - "time" - - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/internal/auth" -) - -// HandleOperations handle API calls on Operations -func (d *DataHandler) HandleOperations(w http.ResponseWriter, r *http.Request) { - id, _ := strconv.Atoi(strings.TrimPrefix(r.URL.Path, "/api/Operations/")) - switch method := r.Method; method { - case "GET": - switch auth.GetLoggedUserTechnical(w, r).Role { - case "ADMIN", "BANKER", "CLIENT": - d.getOperationClient(w, r, id) - default: - http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) - } - case "POST": - switch auth.GetLoggedUserTechnical(w, r).Role { - case "ADMIN", "BANKER", "CLIENT": - d.postOperationClient(w, r, id) - default: - http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) - } - - case "DELETE": - switch auth.GetLoggedUserTechnical(w, r).Role { - case "ADMIN", "BANKER": - d.deleteOperationBanker(w, r, id) - case "CLIENT": - http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) - default: - http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) - } - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } -} - -func (d *DataHandler) getOperationClient(w http.ResponseWriter, r *http.Request, id int) { - if id != 0 { - var o Operation - if err := d.db.First(&o, id).Error; err != nil { - http.Error(w, ErrorIDDoesNotExist, http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(o) - } else { - var o []Operation - d.db.Find(&o) - json.NewEncoder(w).Encode(o) - } -} - -func (d *DataHandler) postOperationClient(w http.ResponseWriter, r *http.Request, id int) { - var o Operation - err := json.NewDecoder(r.Body).Decode(&o) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - var debtor BankAccount - var creditor BankAccount - if err := d.db.Where(reqID, o.Debtor).First(&debtor).Error; err != nil { - http.Error(w, "Can not find debtor account", http.StatusInternalServerError) - return - } - if err := d.db.First(&creditor, o.Creditor).Error; err != nil { - http.Error(w, "Can not find creditor account", http.StatusInternalServerError) - return - } - if (debtor.Amount + o.Amount) <= debtor.BankOverdraft { - http.Error(w, "Not enough money", http.StatusExpectationFailed) - return - } - // Update BankAccounts - debtor.Amount += o.Amount - creditor.Amount -= o.Amount - d.db.Save(&debtor) - d.db.Save(&creditor) - - now := time.Now() - o.Date = now - d.db.Create(&o) - - // Add the operation to creditor - op := Operation{ - Debtor: o.Creditor, - Amount: -o.Amount, - Date: now, - Creditor: o.Debtor, - } - d.db.Create(&op) -} - -func (d *DataHandler) deleteOperationBanker(w http.ResponseWriter, r *http.Request, id int) { - if id != 0 { - var o Operation - if err := d.db.First(&o, id).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - // update BankAccounts - var debtor BankAccount - var creditor BankAccount - if err := d.db.First(&debtor, o.Debtor).Error; err == nil { - if err := d.db.First(&creditor, o.Creditor).Error; err == nil { - // Update BankAccounts - debtor.Amount -= o.Amount - creditor.Amount += o.Amount - d.db.Save(&debtor) - d.db.Save(&creditor) - - // Get the operation of the creditor - var op Operation - if err := d.db.First(&op, id+1).Error; err != nil { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - return - } - - // Delete the operations - d.db.Delete(&o) - d.db.Delete(&op) - } - } - } else { - http.Error(w, ErrorIDIsMissing, http.StatusNotFound) - } -} diff --git a/internal/models/sellers.go b/internal/models/sellers.go new file mode 100644 index 0000000000000000000000000000000000000000..b58a77d9c0384abc87fbccbf7a89c59dd0dcb7d5 --- /dev/null +++ b/internal/models/sellers.go @@ -0,0 +1,44 @@ +package models + +import ( + "net/http" + + "forge.grandlyon.com/apoyen2/cookies/internal/auth" +) + +// HandleSellers expose the UserClients API +func (d *DataHandler) HandleSellers(w http.ResponseWriter, r *http.Request) { + // id, _ := strconv.Atoi(strings.TrimPrefix(r.URL.Path, "/api/UserClients/")) + switch method := r.Method; method { + case "GET": + switch auth.GetLoggedUserTechnical(w, r).Role { + case "ADMIN": + // TODO + default: + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) + } + case "POST": + switch auth.GetLoggedUserTechnical(w, r).Role { + case "ADMIN": + // TODO + default: + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) + } + case "PUT": + switch auth.GetLoggedUserTechnical(w, r).Role { + case "ADMIN": + //TODO + default: + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) + } + case "DELETE": + switch auth.GetLoggedUserTechnical(w, r).Role { + case "ADMIN": + // TODO + default: + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) + } + default: + http.Error(w, "method not allowed", 400) + } +} diff --git a/internal/rootmux/admin_test.go b/internal/rootmux/admin_test.go index 1e9ee5e540b86c9c763219720d23182e54b23fed..2d2f3c0f2228408465fb8ff8a605cc3305402ba7 100644 --- a/internal/rootmux/admin_test.go +++ b/internal/rootmux/admin_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/internal/auth" + "forge.grandlyon.com/apoyen2/cookies/internal/auth" ) /** @@ -19,63 +19,12 @@ func AdminTests(t *testing.T) { response := do("GET", "/api/common/WhoAmI", noH, "", 200, "") token := auth.TokenData{} json.Unmarshal([]byte(response), &token) - xsrfHeader := map[string]string{"XSRF-TOKEN": token.XSRFToken} + //xsrfHeader := map[string]string{"XSRF-TOKEN": token.XSRFToken} - // Create a banker - do("POST", "/api/UserBankers", xsrfHeader, `{"UserID":8,"Name":"Banker 2"}`, 200, ``) - // Get a banker - do("GET", "/api/UserBankers/1", xsrfHeader, "", 200, `{"ID":1,"UserID":3,"Name":"Banker","UserClients":[{"ID":1,"UserID":1,"Name":"Dupond","UserBankerID":1,"BankAccounts":null}]}`) - // Try to gel all bankers - do("GET", "/api/UserBankers/", xsrfHeader, "", 200, `[{"ID":1,"UserID":3,"Name":"Banker","UserClients":[{"ID":1,"UserID":1,"Name":"Dupond","UserBankerID":1,"BankAccounts":null}]},{"ID":2,"UserID":6,"Name":"Banker 2","UserClients":[{"ID":2,"UserID":2,"Name":"Boulangerie","UserBankerID":2,"BankAccounts":null}]},{"ID":3,"UserID":8,"Name":"Banker 2","UserClients":[]}]`) - // Try to delete a banker - do("DELETE", "/api/UserBankers/3", xsrfHeader, ``, 200, ``) - - // Try to create a client - do("POST", "/api/UserClients", xsrfHeader, `{"UserID":7,"Name":"Dupond","UserBankerID":1}`, 200, "") - // Try to get one of the banker's client - do("GET", "/api/UserClients/1", xsrfHeader, "", 200, `{"ID":1,"UserID":1,"Name":"Dupond","UserBankerID":1,"BankAccounts":[{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100,"Operations":null}]}`) - // Try to get all the clients of the banker - do("GET", "/api/UserClients", xsrfHeader, "", 200, `[{"ID":1,"UserID":1,"Name":"Dupond","UserBankerID":1,"BankAccounts":[{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100,"Operations":null}]},{"ID":2,"UserID":2,"Name":"Boulangerie","UserBankerID":2,"BankAccounts":[{"ID":2,"Number":"02-01","UserClientID":2,"Type":"checking-account","Amount":4745,"BankOverdraft":-500,"Operations":null}]},{"ID":3,"UserID":7,"Name":"Dupond","UserBankerID":1,"BankAccounts":[]}]`) - // Try to delete a client - do("DELETE", "/api/UserClients/3", xsrfHeader, ``, 200, ``) - - // Add an other bank account to client - do("POST", "/api/BankAccounts", xsrfHeader, `{"Number":"01-02","UserClientID":1,"Type":"saving-account","Amount":1287,"BankOverdraft":0}`, 200, ``) - // Get account where id=1 - do("GET", "/api/BankAccounts/1", xsrfHeader, "", 200, `{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100,"Operations":[]}`) - // Get all Bank accounts - do("GET", "/api/BankAccounts/", xsrfHeader, "", 200, `[{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100,"Operations":[]},{"ID":2,"Number":"02-01","UserClientID":2,"Type":"checking-account","Amount":4745,"BankOverdraft":-500,"Operations":[]},{"ID":3,"Number":"01-02","UserClientID":1,"Type":"saving-account","Amount":1287,"BankOverdraft":0,"Operations":[]}]`) - // Try to delete the saving account of Dupond - do("DELETE", "/api/BankAccounts/3", xsrfHeader, ``, 200, "") - - // Add operation between client and Bakery - do("POST", "/api/Operations", xsrfHeader, `{"Debtor":1,"Amount":-100,"Creditor":2}`, 200, "") - // Get operation where id=1 - do("GET", "/api/Operations/1", xsrfHeader, "", 200, `{"ID":1,"Debtor":1,"Amount":-100`) - // Try to delete the first operation - do("DELETE", "/api/Operations/1", xsrfHeader, ``, 200, "") - } - const apiAdminUsers = "/api/admin/users/" - userTests := func() { - response := do("GET", "/api/common/WhoAmI", noH, "", 200, "") - token := auth.TokenData{} - json.Unmarshal([]byte(response), &token) - xsrfHeader := map[string]string{"XSRF-TOKEN": token.XSRFToken} - - // Create a Client - do("POST", apiAdminUsers, xsrfHeader, `{"login":"UserTest","password": "password","role":"CLIENT"}`, 200, `{"id":9,"idOAuth":"","login":"UserTest","role":"CLIENT"`) - // Create a Banker - do("POST", apiAdminUsers, xsrfHeader, `{"login":"BankerTest","password": "password","role":"BANKER"}`, 200, `{"id":10,"idOAuth":"","login":"BankerTest","role":"BANKER"`) - // Get all users - do("GET", apiAdminUsers, xsrfHeader, ``, 200, `[{"id":1,"idOAuth":"","login":"Dupond"`) - // Delete created users - do("DELETE", "/api/admin/users/9", xsrfHeader, ``, 200, ``) - do("DELETE", "/api/admin/users/10", xsrfHeader, ``, 200, ``) } // Do an OAuth2 login with an known admin do("GET", "/OAuth2Login", noH, "", 200, "<!DOCTYPE html>") tests() - userTests() // Try to logout (must pass) do("GET", "/Logout", noH, "", 200, "Logout OK") diff --git a/internal/rootmux/banker_test.go b/internal/rootmux/banker_test.go deleted file mode 100644 index bd3b10f76ae86c53ca1eac554bad8128a5b971ad..0000000000000000000000000000000000000000 --- a/internal/rootmux/banker_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package rootmux - -import ( - "encoding/json" - "testing" - - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/internal/auth" -) - -/** -Banker TESTS (those tests are to check the bankers rights) -**/ -func BankerTests(t *testing.T) { - // Create the tester - ts, do, _ := createTester(t) - defer ts.Close() // Close the tester - tests := func() { - // Get the XSRF Token - response := do("GET", "/api/common/WhoAmI", noH, "", 200, "") - token := auth.TokenData{} - json.Unmarshal([]byte(response), &token) - xsrfHeader := map[string]string{"XSRF-TOKEN": token.XSRFToken} - - // Create a banker should fail with 405 - do("POST", "/api/UserBankers", xsrfHeader, `{"UserID":8,"Name":"Banker 2"}`, 405, `You're not authorize to execute this method on this ressource.`) - // Get the banker connected - do("GET", "/api/UserBankers/1", xsrfHeader, "", 200, `{"ID":1,"UserID":3,"Name":"Banker","UserClients":[{"ID":1,"UserID":1,"Name":"Dupond","UserBankerID":1,"BankAccounts":null}]}`) - // Try to get an other banker - do("GET", "/api/UserBankers/2", xsrfHeader, "", 403, `You can not access this ressource`) - // Try to gel all bankers - do("GET", "/api/UserBankers/", xsrfHeader, "", 403, `You can not access this ressource`) - // Try to delete an other banker should fail with 405 - do("DELETE", "/api/UserBankers/2", xsrfHeader, ``, 405, `You're not authorize to execute this method on this ressource.`) - - // Try to create a client - do("POST", "/api/UserClients", xsrfHeader, `{"UserID":7,"Name":"Dupond","UserBankerID":1}`, 200, "") - // Try to get one of the banker's client - do("GET", "/api/UserClients/1", xsrfHeader, "", 200, `{"ID":1,"UserID":1,"Name":"Dupond","UserBankerID":1,"BankAccounts":[{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100,"Operations":null}]}`) - // Try to get another banker's client should fail with 405 - do("GET", "/api/UserClients/2", xsrfHeader, "", 403, `You can not access this ressource`) - // Try to get all the clients of the banker - do("GET", "/api/UserClients", xsrfHeader, "", 200, `[{"ID":1,"UserID":1,"Name":"Dupond","UserBankerID":1,"BankAccounts":[{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100,"Operations":null}]},{"ID":3,"UserID":7,"Name":"Dupond","UserBankerID":1,"BankAccounts":[]}]`) - // Try to delete a banker client - do("DELETE", "/api/UserClients/3", xsrfHeader, ``, 200, ``) - // Try to delete the bakery client should fail with 405 - do("DELETE", "/api/UserClients/2", xsrfHeader, ``, 405, `You're not authorize to execute this method on this ressource.`) - - // Add an other bank account to client - do("POST", "/api/BankAccounts", xsrfHeader, `{"Number":"01-02","UserClientID":1,"Type":"saving-account","Amount":1287,"BankOverdraft":0}`, 200, ``) - // Add a bank account to another banker's client should fail with 405 - do("POST", "/api/BankAccounts", xsrfHeader, `{"Number":"02-02","UserClientID":2,"Type":"saving-account","Amount":3978,"BankOverdraft":0}`, 403, `You can not access this ressource`) - // Get account where id=1 - do("GET", "/api/BankAccounts/1", xsrfHeader, "", 200, `{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100,"Operations":[]}`) - // Get account where id=2 should fail as it is another banker's client's account - do("GET", "/api/BankAccounts/2", xsrfHeader, "", 403, `You can not access this ressource`) - // Get all Bank account should return only the bank accounts of the bankers'client - do("GET", "/api/BankAccounts/", xsrfHeader, "", 200, `[{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100,"Operations":[]},{"ID":3,"Number":"01-02","UserClientID":1,"Type":"saving-account","Amount":1287,"BankOverdraft":0,"Operations":[]}]`) - // Try to delete the saving account of Dupond - do("DELETE", "/api/BankAccounts/3", xsrfHeader, ``, 200, "") - // Try to delete the saving account of Bakery should fail with 405 - do("DELETE", "/api/BankAccounts/2", xsrfHeader, ``, 403, "You can not access this ressource") - - // Add operation between client and Bakery - do("POST", "/api/Operations", xsrfHeader, `{"Debtor":1,"Amount":-100,"Creditor":2}`, 200, "") - // Get operation where id=1 - do("GET", "/api/Operations/1", xsrfHeader, "", 200, `{"ID":1,"Debtor":1,"Amount":-100`) - // Try to delete the first operation - do("DELETE", "/api/Operations/1", xsrfHeader, ``, 200, "") - } - // Do a in memory login with an known admin - do("POST", "/Login", noH, `{"login": "banker","password": "password"}`, 200, "") - tests() - // Try to logout (must pass) - do("GET", "/Logout", noH, "", 200, "Logout OK") -} diff --git a/internal/rootmux/client_test.go b/internal/rootmux/client_test.go deleted file mode 100644 index 1c97c5a3e836c9ffdaeb53a5194b093ea0194d46..0000000000000000000000000000000000000000 --- a/internal/rootmux/client_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package rootmux - -import ( - "encoding/json" - "testing" - - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/internal/auth" - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/internal/models" -) - -/** -CLIENT TESTS (this tests are to check that a normally logged user can access the apps that is allowed to and only that) -**/ -func ClientTests(t *testing.T) { - // Create the tester - ts, do, _ := createTester(t) - defer ts.Close() // Close the tester - tests := func() { - // Get the XSRF Token - response := do("GET", "/api/common/WhoAmI", noH, "", 200, "") - token := auth.TokenData{} - json.Unmarshal([]byte(response), &token) - xsrfHeader := map[string]string{"XSRF-TOKEN": token.XSRFToken} - - // Try to create a client should fail with 405 - do("POST", "/api/UserClients", xsrfHeader, `{"ID":11,"UserID":"11","Name":"Dupont"}`, 405, models.ErrorNotAuthorizeMethodOnRessource) - // Try to create a banker should fail with 405 - do("POST", "/api/UserBankers", xsrfHeader, `{"ID":11,"UserID":"11","Name":"Banker"}`, 405, models.ErrorNotAuthorizeMethodOnRessource) - // Try to create a BankAccount should fail with 405 - do("POST", "/api/BankAccounts", xsrfHeader, `{"Number":"01-02","UserClientID":1,"Type":"saving-account","Amount":1287,"BankOverdraft":0}`, 405, models.ErrorNotAuthorizeMethodOnRessource) - // Client should be able to create an operation - do("POST", "/api/Operations", xsrfHeader, `{"Debtor":1,"Amount":-100,"Creditor":2}`, 200, ``) - - // Get the previous operation between Dupond and Bakery - do("GET", "/api/Operations/1", xsrfHeader, "", 200, `{"ID":1,"Debtor":1,"Amount":-100`) - // Get all operations - do("GET", "/api/Operations/", xsrfHeader, "", 200, `[{"ID":1,"Debtor":1,"Amount":-100`) - // Get Dupond Bank account - do("GET", "/api/BankAccounts/1", xsrfHeader, "", 200, `{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":358,"BankOverdraft":-100,"Operations":[{"ID":1,"Debtor":1,"Amount":-100`) - // Get all accounts of the user client and only those ones - do("GET", "/api/BankAccounts/", xsrfHeader, "", 200, `[{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":358,"BankOverdraft":-100,"Operations":[{"ID":1,"Debtor":1,"Amount":-100`) - // Get client Dupond by id - do("GET", "/api/UserClients/1", xsrfHeader, "", 200, `{"ID":1,"UserID":1,"Name":"Dupond","UserBankerID":1,"BankAccounts":[{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":358,"BankOverdraft":-100,"Operations":null}]}`) - // Get client Bakery should fail with 403 - do("GET", "/api/UserClients/2", xsrfHeader, "", 403, `You can not access this ressource`) - // Try to get all the clients return only Dupond - do("GET", "/api/UserClients", xsrfHeader, "", 200, `[{"ID":1,"UserID":1,"Name":"Dupond","UserBankerID":1,"BankAccounts":[{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":358,"BankOverdraft":-100,"Operations":null}]}]`) - - // Try to delete the first operation should fail - do("DELETE", "/api/Operations/1", xsrfHeader, ``, 405, models.ErrorNotAuthorizeMethodOnRessource) - // Try to delete the saving account of Dupond should fail - do("DELETE", "/api/BankAccounts/2", xsrfHeader, ``, 405, models.ErrorNotAuthorizeMethodOnRessource) - // Try to delete the client Dupond should fail - do("DELETE", "/api/UserClients/2", xsrfHeader, ``, 405, models.ErrorNotAuthorizeMethodOnRessource) - } - // Do a in memory login with an known user - do("POST", "/Login", noH, `{"login": "Dupond","password": "password"}`, 200, "") - // Run the tests - tests() - // Try to logout (must pass) - do("GET", "/Logout", noH, "", 200, "Logout OK") - -} diff --git a/internal/rootmux/rootmux.go b/internal/rootmux/rootmux.go index 2cc76e7dc7f8d8eb7a89e1ae775f8ca0df26a45c..0a208584d09eb530f2e9bc0183dcb225c1cfe2ed 100644 --- a/internal/rootmux/rootmux.go +++ b/internal/rootmux/rootmux.go @@ -4,8 +4,8 @@ import ( "net/http" "os" - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/internal/auth" - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/internal/models" + "forge.grandlyon.com/apoyen2/cookies/internal/auth" + "forge.grandlyon.com/apoyen2/cookies/internal/models" "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/pkg/common" "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/pkg/middlewares" ) diff --git a/internal/rootmux/rootmux_test.go b/internal/rootmux/rootmux_test.go index 6a2c1bb8b1acc1e821a3855284ce291e49fb9733..7d9d898713baa36366d4ad5d1de33ceee58b8eae 100644 --- a/internal/rootmux/rootmux_test.go +++ b/internal/rootmux/rootmux_test.go @@ -11,11 +11,11 @@ import ( "regexp" "testing" - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/internal/auth" + "forge.grandlyon.com/apoyen2/cookies/internal/auth" "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/pkg/tester" "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/pkg/tokens" - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/internal/mocks" + "forge.grandlyon.com/apoyen2/cookies/internal/mocks" ) var ( @@ -56,10 +56,6 @@ func TestAll(t *testing.T) { resetData(t) UnLoggedUserTests(t) resetData(t) - ClientTests(t) - resetData(t) - BankerTests(t) - resetData(t) AdminTests(t) resetData(t) appTests(t) @@ -87,29 +83,7 @@ func appTests(t *testing.T) { response := do("GET", "/api/common/WhoAmI", noH, "", 200, "") token := auth.TokenData{} json.Unmarshal([]byte(response), &token) - xsrfHeader := map[string]string{"XSRF-TOKEN": token.XSRFToken} - - // Add invalid operation between client and Bakery must be refused with 417 (Expectation failed) - do("POST", "/api/Operations", xsrfHeader, `{"Debtor":1,"Amount":-1789,"Creditor":2}`, 417, "Not enough money") - - // Add an operation between Dupond and Bakery and verify that bank accounts are updated and opposite operation is created - do("POST", "/api/Operations", xsrfHeader, `{"Debtor":1,"Amount":-100,"Creditor":2}`, 200, "") - do("GET", "/api/Operations/1", xsrfHeader, "", 200, `{"ID":1,"Debtor":1,"Amount":-100`) - do("GET", "/api/Operations/2", xsrfHeader, "", 200, `{"ID":2,"Debtor":2,"Amount":100`) - do("GET", "/api/BankAccounts/1", xsrfHeader, "", 200, `{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":358,"BankOverdraft":-100,"Operations":[{"ID":1,"Debtor":1,"Amount":-100,"Date":"`) - do("GET", "/api/BankAccounts/2", xsrfHeader, "", 200, `{"ID":2,"Number":"02-01","UserClientID":2,"Type":"checking-account","Amount":4845,"BankOverdraft":-500,"Operations":[{"ID":2,"Debtor":2,"Amount":100,"Date":`) - - // Try to delete the first operation, the opposite operation should also have been deleted and bank accounts updated - do("DELETE", "/api/Operations/1", xsrfHeader, ``, 200, "") - do("GET", "/api/Operations/1", xsrfHeader, "", 404, `id does not exist`) - do("GET", "/api/Operations/2", xsrfHeader, "", 404, `id does not exist`) - do("GET", "/api/BankAccounts/1", xsrfHeader, "", 200, `{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100,"Operations":[]}`) - do("GET", "/api/BankAccounts/2", xsrfHeader, "", 200, `{"ID":2,"Number":"02-01","UserClientID":2,"Type":"checking-account","Amount":4745,"BankOverdraft":-500,"Operations":[]}`) - - // Delete a client should also delete his banks accounts - do("DELETE", "/api/UserClients/1", xsrfHeader, ``, 200, "") - do("GET", "/api/BankAccounts/1", xsrfHeader, "", 404, `id does not exist`) - + // xsrfHeader := map[string]string{"XSRF-TOKEN": token.XSRFToken} } // Do an OAuth2 login with an known admin do("GET", "/OAuth2Login", noH, "", 200, "<!DOCTYPE html>") diff --git a/internal/rootmux/unlogged_test.go b/internal/rootmux/unlogged_test.go index 33447053b86dc0b0b0d5a40fd8fa5f9606ddbd8b..f739168eb01faceec2280c2de684672f46a78eb8 100644 --- a/internal/rootmux/unlogged_test.go +++ b/internal/rootmux/unlogged_test.go @@ -25,38 +25,4 @@ func UnLoggedUserTests(t *testing.T) { do("POST", "/Login", noH, `{"login": "admin","password": "badpassword"}`, http.StatusForbidden, `user not found`) // Without authent API calls must fails with 401 error extracting token - // Try to get the first client should fail - do("GET", "/api/UserClients/1", noH, "", 401, errorExtractingToken) - // Try to get all clients should fail - do("GET", "/api/UserClients", noH, "", 401, errorExtractingToken) - // Try to get the first operation should fail - do("GET", "/api/UserBankers/1", noH, "", 401, errorExtractingToken) - // Try to get all clients should fail - do("GET", "/api/UserBankers", noH, "", 401, errorExtractingToken) - // Try to get the first operation should fail - do("GET", "/api/Operations/1", noH, "", 401, errorExtractingToken) - // Try to get all operations should fail - do("GET", "/api/Operations", noH, "", 401, errorExtractingToken) - // Try to get the first BankAccount should fail - do("GET", "/api/BankAccounts/1", noH, "", 401, errorExtractingToken) - // Try to get all BankAccounts should fail - do("GET", "/api/BankAccounts", noH, "", 401, errorExtractingToken) - - // Unlogged user should not be able to create a Client - do("POST", "/api/UserClients", noH, `{"Name":"Dupond"}`, 401, errorExtractingToken) - // Unlogged user should not be able to create a Banker - do("POST", "/api/UserBankers", noH, `{"Name":"Dupond"}`, 401, errorExtractingToken) - // Unlogged user should not be able to create a Bank Account - do("POST", "/api/BankAccounts", noH, `{"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100}`, 401, errorExtractingToken) - // Unlogged user should not be able to create an Operation - do("POST", "/api/Operations", noH, `{"Debtor":1,"Amount":-100,"Creditor":3}`, 401, errorExtractingToken) - - // Unlogged user should not be able to delete an Operation - do("DELETE", "/api/Operations/1", noH, ``, 401, errorExtractingToken) - // Unlogged user should not be able to delete a BankAccount - do("DELETE", "/api/BankAccounts/2", noH, ``, 401, errorExtractingToken) - // Unlogged user should not be able to delete a Client - do("DELETE", "/api/UserClients/2", noH, ``, 401, errorExtractingToken) - // Unlogged user should not be able to delete a Banker - do("DELETE", "/api/UserBankers/2", noH, ``, 401, errorExtractingToken) } diff --git a/main.go b/main.go index 58cbcf8c40efb1243c399bb3d1e430a0ba8bce12..ed6bba2f6c744a7a99961fd8fe8261f30fc10628 100644 --- a/main.go +++ b/main.go @@ -14,8 +14,8 @@ import ( "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/pkg/middlewares" "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/pkg/tokens" - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/internal/mocks" - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/internal/rootmux" + "forge.grandlyon.com/apoyen2/cookies/internal/mocks" + "forge.grandlyon.com/apoyen2/cookies/internal/rootmux" "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/pkg/log" "golang.org/x/crypto/acme/autocert" diff --git a/pkg/common/common.go b/pkg/common/common.go deleted file mode 100644 index 0cbe4fef5a9ccb02b2973555bc42bf5c855cda80..0000000000000000000000000000000000000000 --- a/pkg/common/common.go +++ /dev/null @@ -1,109 +0,0 @@ -package common - -import ( - "bytes" - "crypto/rand" - "encoding/base64" - "encoding/json" - "io" - "net/http" - "os" - "path" - "sync" -) - -// Mutex used to lock file writing -var lock sync.Mutex - -// Save saves a representation of v to the file at path. -func Save(path string, v interface{}) error { - lock.Lock() - defer lock.Unlock() - f, err := os.Create(path) - if err != nil { - return err - } - defer f.Close() - r, err := Marshal(v) - if err != nil { - return err - } - _, err = io.Copy(f, r) - return err -} - -// Load loads the file at path into v. Use os.IsNotExist() to see if the returned error is due to the file being missing. -func Load(path string, v interface{}) error { - lock.Lock() - defer lock.Unlock() - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - return Unmarshal(f, v) -} - -// Marshal is a function that marshals the object into an io.Reader. By default, it uses the JSON marshaller. -var Marshal = func(v interface{}) (io.Reader, error) { - b, err := json.MarshalIndent(v, "", "\t") - if err != nil { - return nil, err - } - return bytes.NewReader(b), nil -} - -// Unmarshal is a function that unmarshals the data from the reader into the specified value. By default, it uses the JSON unmarshaller. -var Unmarshal = func(r io.Reader, v interface{}) error { - return json.NewDecoder(r).Decode(v) -} - -// GenerateRandomBytes returns securely generated random bytes. -// It will return an error if the system's secure random -// number generator fails to function correctly, in which -// case the caller should not continue. -func GenerateRandomBytes(n int) ([]byte, error) { - b := make([]byte, n) - _, err := rand.Read(b) - // Note that err == nil only if we read len(b) bytes. - if err != nil { - return nil, err - } - return b, nil -} - -// GenerateRandomString returns a URL-safe, base64 encoded -// securely generated random string. -// It will return an error if the system's secure random -// number generator fails to function correctly, in which -// case the caller should not continue. -func GenerateRandomString(s int) (string, error) { - b, err := GenerateRandomBytes(s) - return base64.URLEncoding.EncodeToString(b), err -} - -// FallBackWrapper serves a file if found and else default to index.html -type FallBackWrapper struct { - Assets http.FileSystem -} - -// Open serves a file if found and else default to index.html -func (i *FallBackWrapper) Open(name string) (http.File, error) { - file, err := i.Assets.Open(name) - // If the file is found but there is another error or the asked for file has an extension : return the file or error - if !os.IsNotExist(err) || path.Ext(name) != "" { - return file, err - } - // Else fall back to index.html - return i.Assets.Open("index.html") -} - -// Contains works out if a string slice contains a given string element -func Contains(a []string, x string) bool { - for _, n := range a { - if x == n { - return true - } - } - return false -} diff --git a/pkg/glob/LICENSE b/pkg/glob/LICENSE deleted file mode 100644 index bdfbd951497618c8cd39a592d40ec642ee7cb428..0000000000000000000000000000000000000000 --- a/pkg/glob/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Ryan Uber - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/pkg/glob/glob.go b/pkg/glob/glob.go deleted file mode 100644 index e67db3be183f75c09bda284b21123a1c6d0138f3..0000000000000000000000000000000000000000 --- a/pkg/glob/glob.go +++ /dev/null @@ -1,56 +0,0 @@ -package glob - -import "strings" - -// The character which is treated like a glob -const GLOB = "*" - -// Glob will test a string pattern, potentially containing globs, against a -// subject string. The result is a simple true/false, determining whether or -// not the glob pattern matched the subject text. -func Glob(pattern, subj string) bool { - // Empty pattern can only match empty subject - if pattern == "" { - return subj == pattern - } - - // If the pattern _is_ a glob, it matches everything - if pattern == GLOB { - return true - } - - parts := strings.Split(pattern, GLOB) - - if len(parts) == 1 { - // No globs in pattern, so test for equality - return subj == pattern - } - - leadingGlob := strings.HasPrefix(pattern, GLOB) - trailingGlob := strings.HasSuffix(pattern, GLOB) - end := len(parts) - 1 - - // Go over the leading parts and ensure they match. - for i := 0; i < end; i++ { - idx := strings.Index(subj, parts[i]) - - switch i { - case 0: - // Check the first section. Requires special handling. - if !leadingGlob && idx != 0 { - return false - } - default: - // Check that the middle parts match. - if idx < 0 { - return false - } - } - - // Trim evaluated text from subj as we loop over the pattern. - subj = subj[idx+len(parts[i]):] - } - - // Reached the last section. Requires special handling. - return trailingGlob || strings.HasSuffix(subj, parts[end]) -} diff --git a/pkg/glob/glob_test.go b/pkg/glob/glob_test.go deleted file mode 100644 index fa4edee2bb8565dcdf6d61efa3a5762d854c038c..0000000000000000000000000000000000000000 --- a/pkg/glob/glob_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package glob - -import ( - "strings" - "testing" -) - -func testGlobMatch(t *testing.T, pattern, subj string) { - if !Glob(pattern, subj) { - t.Fatalf("%s should match %s", pattern, subj) - } -} - -func testGlobNoMatch(t *testing.T, pattern, subj string) { - if Glob(pattern, subj) { - t.Fatalf("%s should not match %s", pattern, subj) - } -} - -func TestEmptyPattern(t *testing.T) { - testGlobMatch(t, "", "") - testGlobNoMatch(t, "", "test") -} - -func TestEmptySubject(t *testing.T) { - for _, pattern := range []string{ - "", - "*", - "**", - "***", - "****************", - strings.Repeat("*", 1000000), - } { - testGlobMatch(t, pattern, "") - } - - for _, pattern := range []string{ - // No globs/non-glob characters - "test", - "*test*", - - // Trailing characters - "*x", - "*****************x", - strings.Repeat("*", 1000000) + "x", - - // Leading characters - "x*", - "x*****************", - "x" + strings.Repeat("*", 1000000), - - // Mixed leading/trailing characters - "x*x", - "x****************x", - "x" + strings.Repeat("*", 1000000) + "x", - } { - testGlobNoMatch(t, pattern, "") - } -} - -func TestPatternWithoutGlobs(t *testing.T) { - testGlobMatch(t, "test", "test") -} - -func TestGlob(t *testing.T) { - // Matches - for _, pattern := range []string{ - "*test", // Leading glob - "this*", // Trailing glob - "this*test", // Middle glob - "*is *", // String in between two globs - "*is*a*", // Lots of globs - "**test**", // Double glob characters - "**is**a***test*", // Varying number of globs - "* *", // White space between globs - "*", // Lone glob - "**********", // Nothing but globs - "*Ѿ*", // Unicode with globs - "*is a ϗѾ *", // Mixed ASCII/unicode - } { - testGlobMatch(t, pattern, "this is a ϗѾ test") - } - - // Non-matches - for _, pattern := range []string{ - "test*", // Implicit substring match - "*is", // Partial match - "*no*", // Globs without a match between them - " ", // Plain white space - "* ", // Trailing white space - " *", // Leading white space - "*ʤ*", // Non-matching unicode - "this*this is a test", // Repeated prefix - } { - testGlobNoMatch(t, pattern, "this is a test") - } -} - -func BenchmarkGlob(b *testing.B) { - for i := 0; i < b.N; i++ { - if !Glob("*quick*fox*dog", "The quick brown fox jumped over the lazy dog") { - b.Fatalf("should match") - } - } -} diff --git a/pkg/log/log.go b/pkg/log/log.go deleted file mode 100644 index def58c67b407b7165dab80c1b5de847215566691..0000000000000000000000000000000000000000 --- a/pkg/log/log.go +++ /dev/null @@ -1,114 +0,0 @@ -package log - -import ( - "fmt" - "io" - "log" - "net" - "net/http" - "os" - "strings" - "sync" - "time" - - maxminddb "github.com/oschwald/maxminddb-golang" -) - -type cache struct { - mux sync.Mutex - last time.Time - content map[string]string -} - -// Logger represents a standard logger sets up for this application usage -var ( - Logger *log.Logger - ipcache = cache{ - last: time.Now(), - content: make(map[string]string), - } - ipDbLocation = "./configs/ipgeodatabase/GeoLite2-City.mmdb" - f *os.File -) - -func init() { - // Initialize logger - Logger = log.New(os.Stdout, "", log.LstdFlags) -} - -// SetFile set a file to log to instead of standard output -func SetFile(file string) { - var err error - f, err = os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) - if err != nil { - log.Fatalf("error opening file: %v", err) - } - wrt := io.MultiWriter(os.Stdout, f) - Logger.SetOutput(wrt) -} - -// CloseFile close the log file on exit -func CloseFile() { - f.Close() -} - -// GetCityAndCountryFromRequest returns a string containing the city and the contry where the request is from -// If the city is fetched from the built-in memory cache, it is NOT suffixed by a dot (.) -func GetCityAndCountryFromRequest(req *http.Request) string { - // If the request remote adress is local return "localhost" - if req.RemoteAddr == "" || strings.HasPrefix(req.RemoteAddr, "[::1]") || strings.HasPrefix(req.RemoteAddr, "127.0.0.1") { - return "localhost" - } - - // Lock the cache - ipcache.mux.Lock() - defer ipcache.mux.Unlock() - // Check if the cache is to old or to big - if time.Now().After(ipcache.last.Add(time.Hour*24)) || len(ipcache.content) > 1000 { - // If so reset the cache - ipcache.last = time.Now() - ipcache.content = make(map[string]string) - } - - // Get ip from remote address - address := strings.Split(req.RemoteAddr, ":")[0] - - // First check if the ip is in memory cache - locFromCache, ok := ipcache.content[address] - if ok { - return locFromCache - } - - // If not open the maxmind database, search the ip and update the cache - db, err := maxminddb.Open(ipDbLocation) - if err != nil { - Logger.Fatal(err) - } - defer db.Close() - - ip := net.ParseIP(address) - - if ip == nil { - return "ip could not be parsed" - } - - var record struct { - City struct { - Names map[string]string `maxminddb:"names"` - } `maxminddb:"city"` - Country struct { - Names map[string]string `maxminddb:"names"` - } `maxminddb:"country"` - } - - err = db.Lookup(ip, &record) - if err != nil { - Logger.Fatal(err) - } - if record.Country.Names["fr"] == "" { - return "ip not found" - } - ipFromDB := fmt.Sprintf("%v, %v", record.City.Names["fr"], record.Country.Names["fr"]) - ipcache.content[address] = ipFromDB - return ipFromDB + "." -} diff --git a/pkg/log/log_test.go b/pkg/log/log_test.go deleted file mode 100644 index 54311c7738e542398bfb6cae3b3406f10f5c2315..0000000000000000000000000000000000000000 --- a/pkg/log/log_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package log - -import ( - "net/http" - "net/http/httptest" - "strings" - "testing" -) - -const londonIPAdress = "81.2.69.142:1234" - -func TestGetCityAndCountryFromRequest(t *testing.T) { - - ipDbLocation = "../../configs/ipgeodatabase/GeoLite2-City.mmdb" - - requestFromLocalHost := httptest.NewRequest("GET", "/test", strings.NewReader("")) - requestFromLocalHost.RemoteAddr = "[::1]:1234" - - requestFromLondon := httptest.NewRequest("GET", "/test", strings.NewReader("")) - requestFromLondon.RemoteAddr = londonIPAdress - - requestWithLocalIP := httptest.NewRequest("GET", "/test", strings.NewReader("")) - - type args struct { - req *http.Request - } - tests := []struct { - name string - args args - want string - }{ - { - name: "Request from localhost", - args: args{ - req: requestFromLocalHost, - }, - want: "localhost", - }, - { - name: "Request from london", - args: args{ - req: requestFromLondon, - }, - want: "Londres, Royaume-Uni.", - }, - { - name: "Request from london, again", - args: args{ - req: requestFromLondon, - }, - want: "Londres, Royaume-Uni", - }, - { - name: "Request with empty ip", - args: args{ - req: requestWithLocalIP, - }, - want: "ip not found", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GetCityAndCountryFromRequest(tt.args.req); got != tt.want { - t.Errorf("GetCityAndCountryFromRequest() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/log/middlewares.go b/pkg/log/middlewares.go deleted file mode 100644 index e1ae506ce3d7632fc4b09a02177a0865dc70ecf4..0000000000000000000000000000000000000000 --- a/pkg/log/middlewares.go +++ /dev/null @@ -1,26 +0,0 @@ -package log - -import ( - "bytes" - "io/ioutil" - "net/http" -) - -// Middleware allow extensive logging of requests for debug and development purposes only -func Middleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - readBody, err := ioutil.ReadAll(r.Body) - if err != nil { - Logger.Print("Body error : ", err.Error()) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - newBody := ioutil.NopCloser(bytes.NewBuffer(readBody)) - r.Body = newBody - Logger.Println(r.Method, r.URL.Path, r.RemoteAddr, r.UserAgent()) - if string(readBody) != "" { - Logger.Printf("BODY : %q", readBody) - } - next.ServeHTTP(w, r) - }) -} diff --git a/pkg/middlewares/middlewares.go b/pkg/middlewares/middlewares.go deleted file mode 100644 index 0694f69f7291fb73e3d93f74cd6dc0a05d466c3f..0000000000000000000000000000000000000000 --- a/pkg/middlewares/middlewares.go +++ /dev/null @@ -1,51 +0,0 @@ -package middlewares - -import ( - "fmt" - "net/http" - "strconv" -) - -// Cors enables CORS Request on server (for development purposes) -func Cors(next http.Handler, allowedOrigin string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", allowedOrigin) - w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, PROPFIND, MKCOL, MOVE, COPY") - w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, XSRF-TOKEN, Authorization, Depth, Destination") - w.Header().Set("Access-Control-Allow-Credentials", "true") - next.ServeHTTP(w, req) - }) -} - -// WebSecurity adds good practices security headers on http responses -func WebSecurity(next http.Handler, source string, allowEvalInlineScript bool) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Strict-Transport-Security", "max-age=63072000") - var inline string - if allowEvalInlineScript { - inline = "'unsafe-inline' 'unsafe-eval'" - } - w.Header().Set("Content-Security-Policy", fmt.Sprintf("default-src %[1]v 'self'; img-src %[1]v blob: 'self'; script-src 'self' %[1]v %[2]v; style-src 'self' 'unsafe-inline'; frame-src %[1]v; frame-ancestors %[1]v", source, inline)) - //w.Header().Set("X-Frame-Options", "SAMEORIGIN") // Works fine with chrome but is not obsoleted by frame-src in firefox 72.0.2 - w.Header().Set("X-XSS-Protection", "1; mode=block") - w.Header().Set("Referrer-Policy", "strict-origin") - w.Header().Set("X-Content-Type-Options", "nosniff") - next.ServeHTTP(w, req) - }) -} - -// NoCache disable caching -func NoCache(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Cache-Control", "no-store, must-revalidate") - next.ServeHTTP(w, req) - }) -} - -// GetFullHostname returns the full hostname of the server -func GetFullHostname(hostname string, port int) string { - if port == 80 || port == 443 { - return "https://" + hostname - } - return "https://" + hostname + ":" + strconv.Itoa(port) -} diff --git a/pkg/tester/tester.go b/pkg/tester/tester.go deleted file mode 100644 index 3b0f0b26c7d9d840a192e7174cba5698b79a53a7..0000000000000000000000000000000000000000 --- a/pkg/tester/tester.go +++ /dev/null @@ -1,96 +0,0 @@ -package tester - -import ( - "context" - "io/ioutil" - "net" - "net/http" - "net/http/cookiejar" - "net/http/httptest" - "net/url" - "strings" - "testing" - "time" -) - -//Header is a http header -type Header struct { - Key string - Value string -} - -// DoRequestOnHandler does a request on a router (or handler) and check the response -func DoRequestOnHandler(t *testing.T, router http.Handler, method string, route string, header Header, payload string, expectedStatus int, expectedBody string) string { - req, err := http.NewRequest(method, route, strings.NewReader(payload)) - if err != nil { - t.Fatal(err) - } - if header.Key != "" { - req.Header.Set(header.Key, header.Value) - } - rr := httptest.NewRecorder() - router.ServeHTTP(rr, req) - if status := rr.Code; status != expectedStatus { - t.Errorf("Tested %v %v %v ; handler returned wrong status code: got %v want %v", method, route, payload, status, expectedStatus) - } - if !strings.HasPrefix(rr.Body.String(), expectedBody) { - t.Errorf("Tested %v %v %v ; handler returned unexpected body: got %v want %v", method, route, payload, rr.Body.String(), expectedBody) - } - return string(rr.Body.String()) -} - -// doRequestOnServer does a request on listening server -func doRequestOnServer(t *testing.T, hostname string, port string, jar *cookiejar.Jar, method string, testURL string, headers map[string]string, payload string, expectedStatus int, expectedBody string) string { - dialer := &net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - DualStack: true, - } - // or create your own transport, there's an example on godoc. - http.DefaultTransport.(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { - addrAndPort := strings.Split(addr, ":") - if strings.HasSuffix(addrAndPort[0], "sdk-go.io") { - addr = "127.0.0.1:" + addrAndPort[1] - } - return dialer.DialContext(ctx, network, addr) - } - if strings.HasPrefix(testURL, "/") { - testURL = "http://" + hostname + ":" + port + testURL - } else { - u, _ := url.Parse("http://" + testURL) - testURL = "http://" + u.Host + ":" + port + u.Path + "?" + u.RawQuery - } - req, err := http.NewRequest(method, testURL, strings.NewReader(payload)) - if err != nil { - t.Fatal(err) - } - for key, element := range headers { - req.Header.Set(key, element) - } - var client *http.Client - if jar != nil { - client = &http.Client{Jar: jar} - } else { - client = &http.Client{} - } - res, err := client.Do(req) - if err != nil { - t.Fatal(err) - } - body, _ := ioutil.ReadAll(res.Body) - bodyString := string(body) - if status := res.StatusCode; status != expectedStatus { - t.Errorf("Tested %v %v %v ; handler returned wrong status code: got %v want %v", method, testURL, payload, status, expectedStatus) - } - if !strings.HasPrefix(bodyString, expectedBody) { - t.Errorf("Tested %v %v %v ; handler returned unexpected body: got %v want %v", method, testURL, payload, bodyString, expectedBody) - } - return bodyString -} - -// CreateServerTester wraps doRequestOnServer to factorize t, port and jar -func CreateServerTester(t *testing.T, hostname string, port string, jar *cookiejar.Jar) func(method string, url string, header map[string]string, payload string, expectedStatus int, expectedBody string) string { - return func(method string, url string, headers map[string]string, payload string, expectedStatus int, expectedBody string) string { - return doRequestOnServer(t, port, hostname, jar, method, url, headers, payload, expectedStatus, expectedBody) - } -} diff --git a/pkg/tokens/tokens.go b/pkg/tokens/tokens.go deleted file mode 100644 index 8cbe31510b7f4d30ada921fc02173259f2ec5d73..0000000000000000000000000000000000000000 --- a/pkg/tokens/tokens.go +++ /dev/null @@ -1,223 +0,0 @@ -package tokens - -import ( - "bytes" - "compress/flate" - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "net/http" - "strings" - "time" - - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/pkg/common" - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/pkg/log" -) - -var ( - now = time.Now - // Manager is the current token manager - Manager manager -) - -// manager manages tokens -type manager struct { - key []byte - debugMode bool -} - -// Init inits the main token manager -func Init(keyfile string, debug bool) { - Manager = newManager(keyfile, debug) -} - -// newManager creates a manager -func newManager(keyfile string, debug bool) manager { - var keyConfig struct { - Key []byte - } - err := common.Load(keyfile, &keyConfig) - if err != nil { - keyConfig.Key, err = common.GenerateRandomBytes(32) - if err != nil { - log.Logger.Fatal(err) - } - err := common.Save(keyfile, keyConfig) - if err != nil { - log.Logger.Println("Token signing key could not be saved") - } - } - log.Logger.Println("Token signing key set") - return manager{ - debugMode: debug, - key: keyConfig.Key, - } -} - -// Token represents a token containting data -type Token struct { - ExpiresAt int64 - IssuedAt int64 `json:"iat,omitempty"` - Data []byte -} - -// StoreData creates a token with the given data and returns it in a cookie -func (m manager) StoreData(data interface{}, hostName string, cookieName string, duration time.Duration, w http.ResponseWriter) { - expiration := now().Add(duration) - value, err := m.CreateToken(data, expiration) - if err != nil { - http.Error(w, err.Error(), 500) - return - } - cookie := http.Cookie{Name: cookieName, Domain: hostName, Value: value, Expires: expiration, Secure: !m.debugMode, HttpOnly: true, SameSite: http.SameSiteLaxMode} - http.SetCookie(w, &cookie) -} - -// CreateToken creates a token with the given data -func (m manager) CreateToken(data interface{}, expiration time.Time) (string, error) { - // Marshall the data - d, err := json.Marshal(data) - if err != nil { - return "", err - } - // Create the payload - token := Token{ - ExpiresAt: expiration.Unix(), - Data: d, - } - // Serialize the payload - sToken, err := json.Marshal(token) - if err != nil { - return "", err - } - // Compress with deflate - var csToken bytes.Buffer - c, err := flate.NewWriter(&csToken, flate.BestCompression) - if _, err := c.Write(sToken); err != nil { - return "", err - } - if err := c.Close(); err != nil { - return "", err - } - ecsToken, err := Encrypt(csToken.Bytes(), m.key) - if err != nil { - return "", err - } - return base64.StdEncoding.EncodeToString(ecsToken), nil -} - -// ExtractAndValidateToken extracts the token from the request, validates it, and return the data n the value pointed to by v -func (m manager) ExtractAndValidateToken(r *http.Request, cookieName string, v interface{}, checkXSRF bool) (bool, error) { - becsToken, checkXSRF, err := func(r *http.Request, checkXSRF bool) (string, bool, error) { - // Try to extract from the query - query := r.URL.Query().Get("token") - if query != "" { - return query, false, nil - } - // Try to extract from the cookie - cookie, err := r.Cookie(cookieName) - if err == nil { - return cookie.Value, checkXSRF, err - } - // Try to get an auth token from the header - authHeader := strings.Split(r.Header.Get("Authorization"), " ") - if authHeader[0] == "Bearer" && len(authHeader) == 2 { - return authHeader[1], false, nil - } - // Try to use the basic auth header instead - if authHeader[0] == "Basic" && len(authHeader) == 2 { - decoded, err := base64.StdEncoding.DecodeString(authHeader[1]) - if err == nil { - authHeader = strings.Split(string(decoded), ":") - return authHeader[1], false, nil - } - } - return "", false, errors.New("could not extract token") - }(r, checkXSRF) - - if err == nil { - return checkXSRF, m.unstoreData(becsToken, v) - } - return false, err -} - -// unstoreData decrypt, uncompress, unserialize the token, and returns the data n the value pointed to by v -func (m manager) unstoreData(becsToken string, v interface{}) error { - // Decrypt the token - ecsToken, err := base64.StdEncoding.DecodeString(becsToken) - if err != nil { - return fmt.Errorf("failed to unbase64 token") - - } - csToken, err := Decrypt(ecsToken, m.key) - if err != nil { - return fmt.Errorf("failed to decrypt token") - - } - // Uncompress the token - rdata := bytes.NewReader(csToken) - r := flate.NewReader(rdata) - sToken, err := ioutil.ReadAll(r) - if err != nil { - return fmt.Errorf("failed to uncompress token") - - } - // Unserialize the token - token := Token{} - err = json.Unmarshal(sToken, &token) - if err != nil { - return fmt.Errorf("failed to unmarshall token") - - } - // Validate the token - if token.ExpiresAt < now().Unix() { - return fmt.Errorf("token expired") - } - // Update the data - err = json.Unmarshal(token.Data, v) - // Return no error if everything is fine - return nil -} - -// Encrypt a byte array with AES -func Encrypt(data []byte, key []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return []byte{}, err - } - gcm, err := cipher.NewGCM(block) - if err != nil { - return []byte{}, err - } - nonce := make([]byte, gcm.NonceSize()) - if _, err = io.ReadFull(rand.Reader, nonce); err != nil { - return []byte{}, err - } - cipherData := gcm.Seal(nonce, nonce, data, nil) - return cipherData, nil -} - -// Decrypt a byte array with AES -func Decrypt(data []byte, key []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return []byte{}, err - } - gcm, err := cipher.NewGCM(block) - if err != nil { - return []byte{}, err - } - nonceSize := gcm.NonceSize() - nonce, cipherData := data[:nonceSize], data[nonceSize:] - plainData, err := gcm.Open(nil, nonce, cipherData, nil) - if err != nil { - return []byte{}, err - } - return plainData, nil -} diff --git a/pkg/tokens/tokens_test.go b/pkg/tokens/tokens_test.go deleted file mode 100644 index 8eaaf3125b7f7d59668d61f1ba78245091e3695a..0000000000000000000000000000000000000000 --- a/pkg/tokens/tokens_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package tokens - -import ( - "fmt" - "testing" - "time" - - "forge.grandlyon.com/systemes-dinformation/project-template/sdk-go/pkg/common" -) - -type user struct { - Login string - Password string -} - -func (u user) String() string { - return fmt.Sprintf("Login: %v, Password: %v", u.Login, u.Password) -} - -func Test_manager_CreateToken_unStoreData(t *testing.T) { - key, _ := common.GenerateRandomBytes(32) - key2, _ := common.GenerateRandomBytes(32) - type fields struct { - encryptKey []byte - decryptKey []byte - debugMode bool - } - type args struct { - data interface{} - expiration time.Time - } - tests := []struct { - name string - fields fields - args args - want bool - wantErr bool - }{ - {"future_expiration", fields{key, key, false}, args{user{"admin", "password"}, time.Now().Add(24 * time.Hour)}, true, false}, - {"past_expiration", fields{key, key, false}, args{user{"admin", "password"}, time.Now().Add(-24 * time.Hour)}, false, true}, - {"incorrect_aes_key", fields{[]byte("wrong_key_size"), []byte("wrong_key_size"), false}, args{user{"admin", "password"}, time.Now().Add(+24 * time.Hour)}, false, true}, - {"wrong_decrypt_key", fields{key, key2, false}, args{user{"admin", "password"}, time.Now().Add(+24 * time.Hour)}, false, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := manager{ - key: tt.fields.encryptKey, - debugMode: tt.fields.debugMode, - } - token, _ := m.CreateToken(tt.args.data, tt.args.expiration) - m.key = tt.fields.decryptKey - v := user{} - err := m.unstoreData(token, &v) - got := tt.args.data == v - if (err != nil) != tt.wantErr { - t.Errorf("manager.(un)storeData() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("manager.(un)storeData() inData:%v, outData:%v => equality: %v, want %v", tt.args.data, v, got, tt.want) - } - }) - } -}