diff --git a/measure-backend/measure-go/authz.go b/measure-backend/measure-go/authz.go index 734578593..eb3e60550 100644 --- a/measure-backend/measure-go/authz.go +++ b/measure-backend/measure-go/authz.go @@ -174,6 +174,14 @@ func PerformAuthz(uid string, rid string, scope scope) (bool, error) { return true, nil } return false, nil + case *ScopeTeamChangeRoleSameOrLower: + if slices.Contains(roleScope, *ScopeTeamAll) { + return true, nil + } + if slices.Contains(roleScope, *ScopeTeamChangeRoleSameOrLower) { + return true, nil + } + return false, nil case *ScopeTeamAll: if slices.Contains(roleScope, *ScopeTeamAll) { return true, nil diff --git a/measure-backend/measure-go/main.go b/measure-backend/measure-go/main.go index c91cbf8bd..5e1f2287c 100644 --- a/measure-backend/measure-go/main.go +++ b/measure-backend/measure-go/main.go @@ -39,7 +39,7 @@ func main() { r := gin.Default() cors := cors.New(cors.Config{ AllowOrigins: []string{"http://localhost:3000", "https://www.measure.sh"}, - AllowMethods: []string{"GET", "OPTIONS", "PATCH"}, + AllowMethods: []string{"GET", "OPTIONS", "PATCH", "DELETE"}, AllowHeaders: []string{"Authorization"}, AllowCredentials: true, MaxAge: 12 * time.Hour, @@ -66,6 +66,7 @@ func main() { r.Use(cors).PATCH("/teams/:id/rename", validateAccessToken(), renameTeam) r.Use(cors).GET("/teams/:id/authz", validateAccessToken(), getAuthzRoles) r.Use(cors).GET("/teams/:id/members", validateAccessToken(), getTeamMembers) + r.Use(cors).DELETE("/teams/:id/members/:memberId", validateAccessToken(), removeTeamMember) r.Run(":8080") // listen and serve on 0.0.0.0:8080 } diff --git a/measure-backend/measure-go/teams.go b/measure-backend/measure-go/teams.go index f7ba7af53..3e8a07ba4 100644 --- a/measure-backend/measure-go/teams.go +++ b/measure-backend/measure-go/teams.go @@ -238,6 +238,23 @@ func (t *Team) rename() error { return nil } +func (t *Team) removeMember(memberId *uuid.UUID) error { + stmt := sqlf.PostgreSQL.DeleteFrom("team_membership"). + Where("team_id = ?", nil). + Where("user_id = ?", nil) + defer stmt.Close() + + ctx := context.Background() + + _, err := server.PgPool.Exec(ctx, stmt.String(), t.ID, memberId) + + if err != nil { + return err + } + + return nil +} + func getTeams(c *gin.Context) { userId := c.GetString("userId") u := &User{ @@ -602,3 +619,67 @@ func getTeamMembers(c *gin.Context) { c.JSON(http.StatusOK, members) } + +func removeTeamMember(c *gin.Context) { + userId := c.GetString("userId") + teamId, err := uuid.Parse(c.Param("id")) + if err != nil { + msg := `team id invalid or missing` + fmt.Println(msg, err) + c.JSON(http.StatusBadRequest, gin.H{"error": msg}) + return + } + + memberId, err := uuid.Parse(c.Param("memberId")) + if err != nil { + msg := `member id invalid or missing` + fmt.Println(msg, err) + c.JSON(http.StatusBadRequest, gin.H{"error": msg}) + return + } + + user := &User{ + id: userId, + } + + userRole, err := user.getRole(teamId.String()) + if err != nil { + msg := `couldn't perform authorization checks` + fmt.Println(msg, err) + c.JSON(http.StatusInternalServerError, gin.H{"error": msg}) + return + } + + if err != nil || userRole == unknown { + msg := `couldn't perform authorization checks` + fmt.Println(msg, err) + c.JSON(http.StatusInternalServerError, gin.H{"error": msg}) + return + } + + ok, err := PerformAuthz(userId, teamId.String(), *ScopeTeamChangeRoleSameOrLower) + if err != nil { + msg := `couldn't perform authorization checks` + fmt.Println(msg, err) + c.JSON(http.StatusInternalServerError, gin.H{"error": msg}) + return + } + if !ok { + msg := fmt.Sprintf(`you don't have modify permissions to team [%s]`, teamId) + c.JSON(http.StatusForbidden, gin.H{"error": msg}) + return + } + + team := &Team{ + ID: &teamId, + } + + if err = team.removeMember(&memberId); err != nil { + msg := fmt.Sprintf("couldn't remove member [%s]", memberId) + fmt.Println(msg, err) + c.JSON(http.StatusInternalServerError, gin.H{"error": msg}) + return + } + + c.JSON(http.StatusOK, gin.H{"ok": fmt.Sprintf("removed member [%s] from team [%s]", memberId, teamId)}) +}