Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ab5a96ca4 |
@@ -47,19 +47,24 @@ func (handler Handler) List(context *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func currentUserID(c *gin.Context) uint64 {
|
func currentUserID(c *gin.Context) uint64 {
|
||||||
value, ok := c.Get("userId")
|
// #333: go-admin's Authorizator runs on every request with the
|
||||||
if !ok {
|
// IdentityHandler map, which has no "user" entry, so c.Get("userId") is
|
||||||
return 0
|
// always 0 there. The JWT "identity" claim is the authenticated user id.
|
||||||
}
|
switch id := jwt.ExtractClaims(c)["identity"].(type) {
|
||||||
switch id := value.(type) {
|
case float64:
|
||||||
|
if id > 0 {
|
||||||
|
return uint64(id)
|
||||||
|
}
|
||||||
case int:
|
case int:
|
||||||
return uint64(id)
|
if id > 0 {
|
||||||
|
return uint64(id)
|
||||||
|
}
|
||||||
case int64:
|
case int64:
|
||||||
return uint64(id)
|
if id > 0 {
|
||||||
|
return uint64(id)
|
||||||
|
}
|
||||||
case uint64:
|
case uint64:
|
||||||
return id
|
return id
|
||||||
case float64:
|
|
||||||
return uint64(id)
|
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||||
|
"go-admin/app/goauto/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// #333: mirrors the real go-admin middleware, whose Authorizator sets
|
||||||
|
// userId to 0 on every request while the JWT claims carry the identity.
|
||||||
|
func listDevicesAs(t *testing.T, handler Handler, claims jwt.MapClaims) []DeviceListItem {
|
||||||
|
t.Helper()
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
engine := gin.New()
|
||||||
|
engine.GET("/devices", func(c *gin.Context) {
|
||||||
|
c.Set(jwt.JwtPayloadKey, claims)
|
||||||
|
c.Set("userId", 0)
|
||||||
|
c.Next()
|
||||||
|
}, handler.List)
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
engine.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/devices?page=1&pageSize=20", nil))
|
||||||
|
if recorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
var body struct {
|
||||||
|
Data DeviceListResponse `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil {
|
||||||
|
t.Fatalf("decode: %v", err)
|
||||||
|
}
|
||||||
|
return body.Data.Items
|
||||||
|
}
|
||||||
|
|
||||||
|
func seedOwnedDevice(t *testing.T, handler Handler, name string, owner *uint64) models.AgentDevice {
|
||||||
|
t.Helper()
|
||||||
|
device := models.AgentDevice{
|
||||||
|
InstallID: "install-" + name, Name: name, Manufacturer: "test", Model: "test",
|
||||||
|
AndroidVersion: "14", AgentVersion: "1", PDDVersion: "1", CapabilitiesJSON: "[]",
|
||||||
|
Status: models.DeviceStatusOnline, TokenDigest: fmt.Sprintf("digest-%s", name),
|
||||||
|
TokenIssuedAt: time.Now(), OwnerUserID: owner,
|
||||||
|
}
|
||||||
|
if err := handler.DB.Create(&device).Error; err != nil {
|
||||||
|
t.Fatalf("create device: %v", err)
|
||||||
|
}
|
||||||
|
return device
|
||||||
|
}
|
||||||
|
|
||||||
|
func deviceNames(items []DeviceListItem) map[string]bool {
|
||||||
|
names := map[string]bool{}
|
||||||
|
for _, item := range items {
|
||||||
|
names[item.Name] = true
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeviceListPurchaserSeesOnlyOwnDevicesFromJWTIdentity(t *testing.T) {
|
||||||
|
handler := Handler{DB: openTestDatabase(t)}
|
||||||
|
two, three := uint64(2), uint64(3)
|
||||||
|
seedOwnedDevice(t, handler, "caigou1-phone", &two)
|
||||||
|
seedOwnedDevice(t, handler, "caigou2-phone", &three)
|
||||||
|
seedOwnedDevice(t, handler, "unowned-phone", nil)
|
||||||
|
|
||||||
|
names := deviceNames(listDevicesAs(t, handler, jwt.MapClaims{"rolekey": "purchaser", "identity": float64(3)}))
|
||||||
|
if len(names) != 1 || !names["caigou2-phone"] {
|
||||||
|
t.Fatalf("purchaser 3 should see only own device, got %v", names)
|
||||||
|
}
|
||||||
|
|
||||||
|
all := deviceNames(listDevicesAs(t, handler, jwt.MapClaims{"rolekey": "admin", "identity": float64(1)}))
|
||||||
|
if len(all) != 3 {
|
||||||
|
t.Fatalf("admin should see all devices, got %v", all)
|
||||||
|
}
|
||||||
|
|
||||||
|
none := listDevicesAs(t, handler, jwt.MapClaims{"rolekey": "purchaser"})
|
||||||
|
if len(none) != 0 {
|
||||||
|
t.Fatalf("purchaser without identity must see no devices, got %d", len(none))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package shopeeproduct
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||||
|
)
|
||||||
|
|
||||||
|
// #333: the real middleware sets userId=0 on every request; the operator used
|
||||||
|
// for owned-device collection lookup and audit must come from the JWT identity.
|
||||||
|
func TestCurrentUserIDReadsJWTIdentityNotUserIDKey(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
cases := []struct {
|
||||||
|
claims jwt.MapClaims
|
||||||
|
want uint64
|
||||||
|
}{
|
||||||
|
{jwt.MapClaims{"rolekey": "purchaser", "identity": float64(3)}, 3},
|
||||||
|
{jwt.MapClaims{"rolekey": "purchaser"}, 0},
|
||||||
|
{jwt.MapClaims{"rolekey": "purchaser", "identity": float64(-1)}, 0},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||||
|
c.Set(jwt.JwtPayloadKey, tc.claims)
|
||||||
|
c.Set("userId", 0)
|
||||||
|
if got := currentUserID(c); got != tc.want {
|
||||||
|
t.Fatalf("claims %v: got %d want %d", tc.claims, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -427,22 +427,26 @@ func (handler Handler) service(c *gin.Context) (*Service, bool) {
|
|||||||
// purchasing roles per #40; role membership itself is enforced by the router
|
// purchasing roles per #40; role membership itself is enforced by the router
|
||||||
// middleware, not here.
|
// middleware, not here.
|
||||||
func currentUserID(c *gin.Context) uint64 {
|
func currentUserID(c *gin.Context) uint64 {
|
||||||
value, exists := c.Get("userId")
|
// #333: go-admin's Authorizator runs on every request with the
|
||||||
if !exists {
|
// IdentityHandler map, which has no "user" entry, so c.Get("userId") is
|
||||||
return 0
|
// always 0 there. The JWT "identity" claim is the authenticated user id.
|
||||||
}
|
switch id := jwt.ExtractClaims(c)["identity"].(type) {
|
||||||
switch id := value.(type) {
|
case float64:
|
||||||
|
if id > 0 {
|
||||||
|
return uint64(id)
|
||||||
|
}
|
||||||
case int:
|
case int:
|
||||||
return uint64(id)
|
if id > 0 {
|
||||||
|
return uint64(id)
|
||||||
|
}
|
||||||
case int64:
|
case int64:
|
||||||
return uint64(id)
|
if id > 0 {
|
||||||
|
return uint64(id)
|
||||||
|
}
|
||||||
case uint64:
|
case uint64:
|
||||||
return id
|
return id
|
||||||
case float64:
|
|
||||||
return uint64(id)
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func currentRole(c *gin.Context) string {
|
func currentRole(c *gin.Context) string {
|
||||||
|
|||||||
Reference in New Issue
Block a user