Skip to content
Snippets Groups Projects
Commit c2cc1e8f authored by Martin Schmollinger's avatar Martin Schmollinger
Browse files

Added authorization to service myaktion

parent 21596d89
Branches main
No related tags found
No related merge requests found
......@@ -5,6 +5,7 @@ go 1.24.1
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/go-sql-driver/mysql v1.9.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
......
......
......@@ -5,6 +5,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
......
......
......@@ -2,9 +2,11 @@ package handler
import (
"encoding/json"
"errors"
"net/http"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
"gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/model"
"gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/service"
......@@ -26,8 +28,32 @@ func CreateCampaign(w http.ResponseWriter, r *http.Request) {
sendJson(w, campaign)
}
func GetCampaigns(w http.ResponseWriter, _ *http.Request) {
campaigns, err := service.GetCampaigns()
func GetCampaign(w http.ResponseWriter, r *http.Request) {
id, err := getId(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
campaign, err := service.GetCampaign(id, getOrganizerName(r))
if err != nil {
log.Errorf("Failure updating campaign with ID %v: %v", id, err)
if errors.Is(err, gorm.ErrRecordNotFound) {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
if campaign == nil {
http.Error(w, "401 unauthorized access", http.StatusUnauthorized)
log.Errorf("Campaign with ID %v not found for organizer %v: %v", id, getOrganizerName(r), err)
return
}
sendJson(w, campaign)
}
func GetCampaigns(w http.ResponseWriter, r *http.Request) {
campaigns, err := service.GetCampaigns(getOrganizerName(r))
if err != nil {
log.Printf("Error calling service GetCampaigns: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
......@@ -52,15 +78,19 @@ func UpdateCampaign(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusBadRequest)
}
//Call service function
campaign, err = service.UpdateCampaign(id, campaign)
campaign, err = service.UpdateCampaign(id, campaign, getOrganizerName(r))
if err != nil {
log.Errorf("Failure updating campaign with ID %v: %v", id, err)
if errors.Is(err, gorm.ErrRecordNotFound) {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
if campaign == nil {
http.Error(w, "404 campaign not found", http.StatusNotFound)
log.Errorf("Campaign with ID %v not found: %v", id, err)
http.Error(w, "401 unauthorized access", http.StatusUnauthorized)
log.Errorf("Campaign with ID %v not found for organizer %v: %v", id, getOrganizerName(r), err)
return
}
//Send result OK
......@@ -77,15 +107,19 @@ func DeleteCampaign(w http.ResponseWriter, r *http.Request) {
}
//Call service function DeleteCampaign
var campaign *model.Campaign
campaign, err = service.DeleteCampaign(id)
campaign, err = service.DeleteCampaign(id, getOrganizerName(r))
if err != nil {
log.Errorf("Failure deleting campaign with ID %v: %v", id, err)
log.Errorf("Failure updating campaign with ID %v: %v", id, err)
if errors.Is(err, gorm.ErrRecordNotFound) {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
if campaign == nil {
log.Errorf("Campaign with ID %v not found: %v", id, err)
http.Error(w, "404 campaign not found", http.StatusNotFound)
http.Error(w, "401 unauthorized access", http.StatusUnauthorized)
log.Errorf("Campaign with ID %v not found for organizer %v: %v", id, getOrganizerName(r), err)
return
}
sendJson(w, result{Success: "OK"})
......@@ -103,19 +137,23 @@ func PatchCampaign(w http.ResponseWriter, r *http.Request) {
var campaign *model.Campaign
campaign, err = getCampaign(r)
if err != nil {
log.Printf("Error calling service PatchCampaign: %v", err)
log.Printf("Error decoding request body: %v", err)
http.Error(w, err.Error(), http.StatusBadRequest)
}
//Call service function
campaign, err = service.PatchCampaign(id, campaign)
campaign, err = service.PatchCampaign(id, campaign, getOrganizerName(r))
if err != nil {
log.Errorf("Failure patching campaign with ID %v: %v", id, err)
log.Errorf("Failure updating campaign with ID %v: %v", id, err)
if errors.Is(err, gorm.ErrRecordNotFound) {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
if campaign == nil {
http.Error(w, "404 campaign not found", http.StatusNotFound)
log.Errorf("Campaign with ID %v not found: %v", id, err)
http.Error(w, "401 unauthorized access", http.StatusUnauthorized)
log.Errorf("Campaign with ID %v not found for organizer %v: %v", id, getOrganizerName(r), err)
return
}
//Send result OK
......@@ -129,5 +167,10 @@ func getCampaign(r *http.Request) (*model.Campaign, error) {
log.Errorf("Can't serialize request body to campaign struct: %v", err)
return nil, err
}
campaign.OrganizerName = getOrganizerName(r)
return &campaign, nil
}
func getOrganizerName(r *http.Request) string {
return r.Context().Value("organizerName").(string)
}
......@@ -33,6 +33,8 @@ func AddDonation(w http.ResponseWriter, r *http.Request) {
func getDonation(r *http.Request) (*model.Donation, error) {
var donation model.Donation
err := json.NewDecoder(r.Body).Decode(&donation)
//ignore status of donation request!
donation.Status = model.IN_PROCESS
if err != nil {
log.Errorf("Can't serialize request body to donation struct: %v", err)
return nil, err
......
......
package main
import (
"context"
"fmt"
"strings"
"github.com/golang-jwt/jwt/v5"
log "github.com/sirupsen/logrus"
"net/http"
......@@ -10,6 +15,8 @@ import (
"gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/handler"
)
var secretKey = []byte("myaktion-go-secret-key")
func init() {
// init logger
log.SetFormatter(&log.TextFormatter{})
......@@ -23,19 +30,76 @@ func init() {
log.SetLevel(level)
}
func main() {
func authMW(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if tokenString == "" {
w.WriteHeader(http.StatusUnauthorized)
return
}
log.Info(tokenString)
tokenString = strings.Replace(tokenString, "Bearer ", "", 1)
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method")
}
return []byte(secretKey), nil
})
if err != nil {
log.Errorf("Can' parse token string: %+v\n", err)
w.WriteHeader(http.StatusUnauthorized)
return
}
if !token.Valid {
log.Errorf("Token is not valid: %+v\n", err)
w.WriteHeader(http.StatusUnauthorized)
return
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
log.Errorf("Can' extract claims from token: %+v\n", err)
w.WriteHeader(http.StatusUnauthorized)
return
}
organizerName, ok := claims["sub"].(string)
if !ok {
log.Errorf("Can' read organizerName from token: %+v\n", err)
w.WriteHeader(http.StatusUnauthorized)
return
}
if organizerName == "" {
log.Errorf("OrganizerName is an empty string: %+v\n", err)
w.WriteHeader(http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), "organizerName", organizerName)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func main() {
log.Println("Starting My-Aktion API server")
router := mux.NewRouter()
//public
router.HandleFunc("/campaigns/{id}/donations", handler.AddDonation).Methods("POST")
router.HandleFunc("/health", handler.Health).Methods("GET")
router.HandleFunc("/campaigns", handler.CreateCampaign).Methods("POST")
router.HandleFunc("/campaigns", handler.GetCampaigns).Methods("GET")
router.HandleFunc("/campaigns/{id}", handler.UpdateCampaign).Methods("PUT")
router.HandleFunc("/campaigns/{id}", handler.PatchCampaign).Methods("PATCH")
router.HandleFunc("/campaigns/{id}", handler.DeleteCampaign).Methods("DELETE")
router.HandleFunc("/campaigns/{id}/donation", handler.AddDonation).Methods("POST")
protectedRouter := router.PathPrefix("/").Subrouter()
protectedRouter.Use(authMW)
//protected
protectedRouter.HandleFunc("/campaigns", handler.CreateCampaign).Methods("POST")
protectedRouter.HandleFunc("/campaigns", handler.GetCampaigns).Methods("GET")
protectedRouter.HandleFunc("/campaigns/{id}", handler.GetCampaign).Methods("GET")
protectedRouter.HandleFunc("/campaigns/{id}", handler.UpdateCampaign).Methods("PUT")
protectedRouter.HandleFunc("/campaigns/{id}", handler.PatchCampaign).Methods("PATCH")
protectedRouter.HandleFunc("/campaigns/{id}", handler.DeleteCampaign).Methods("DELETE")
if err := http.ListenAndServe(":8000", router); err != nil {
log.Fatal(err)
}
}
......@@ -20,13 +20,13 @@ func CreateCampaign(campaign *model.Campaign) error {
return nil
}
func UpdateCampaign(id uint, campaign *model.Campaign) (*model.Campaign, error) {
existingCampaign, err := GetCampaign(id)
func UpdateCampaign(id uint, campaign *model.Campaign, organizerName string) (*model.Campaign, error) {
existingCampaign, err := GetCampaign(id, organizerName)
if existingCampaign == nil || err != nil {
return existingCampaign, err
}
//OrganizerName is ignored!
existingCampaign.Name = campaign.Name
existingCampaign.OrganizerName = campaign.OrganizerName
existingCampaign.TargetAmount = campaign.TargetAmount
existingCampaign.DonationMinimum = campaign.DonationMinimum
existingCampaign.Account = campaign.Account
......@@ -40,8 +40,8 @@ func UpdateCampaign(id uint, campaign *model.Campaign) (*model.Campaign, error)
return existingCampaign, nil
}
func DeleteCampaign(id uint) (*model.Campaign, error) {
campaign, err := GetCampaign(id)
func DeleteCampaign(id uint, organizerName string) (*model.Campaign, error) {
campaign, err := GetCampaign(id, organizerName)
if campaign == nil || err != nil {
return campaign, err
}
......@@ -55,9 +55,9 @@ func DeleteCampaign(id uint) (*model.Campaign, error) {
return campaign, nil
}
func GetCampaigns() ([]model.Campaign, error) {
func GetCampaigns(organizerName string) ([]model.Campaign, error) {
var campaigns []model.Campaign
result := db.DB.Preload("Donations").Find(&campaigns)
result := db.DB.Preload("Donations").Where("organizer_name = ?", organizerName).Find(&campaigns)
if result.Error != nil {
return nil, result.Error
}
......@@ -65,21 +65,25 @@ func GetCampaigns() ([]model.Campaign, error) {
return campaigns, nil
}
func GetCampaign(id uint) (*model.Campaign, error) {
func GetCampaign(id uint, organizerName string) (*model.Campaign, error) {
campaign := new(model.Campaign)
//result := db.DB.Preload("Donations").Where("organizer_name = ?", organizerName).First(campaign, id)
result := db.DB.Preload("Donations").First(campaign, id)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
return nil, gorm.ErrRecordNotFound //404
}
if result.Error != nil {
if campaign.OrganizerName != organizerName {
return nil, nil //401
}
if result.Error != nil { //other problems
return nil, result.Error
}
log.Tracef("Retrieved: %v", campaign)
return campaign, nil
}
func PatchCampaign(id uint, campaign *model.Campaign) (*model.Campaign, error) {
existingCampaign, err := GetCampaign(id)
func PatchCampaign(id uint, campaign *model.Campaign, organizerName string) (*model.Campaign, error) {
existingCampaign, err := GetCampaign(id, organizerName)
if existingCampaign == nil || err != nil {
return existingCampaign, err
}
......
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment