package middleware import ( "context" "testing" _ "github.com/gogf/gf/contrib/drivers/sqlite/v2" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" "git.5iot.com/iotlcm-public/iotlcm-openapi/internal/library/contexts" "git.5iot.com/iotlcm-public/iotlcm-openapi/internal/model" ) func TestOpenAPIPermission(t *testing.T) { ctx := contexts.Set(context.Background(), &model.Context{ AgentUser: &model.BaseClaimsApi{ApiId: 1, AppKey: "test-key"}, }) if err := gdb.SetConfig(gdb.Config{"default": gdb.ConfigGroup{{Type: "sqlite", Link: "sqlite::@file(:memory:)", MaxOpenConnCount: 1}}}); err != nil { t.Fatal(err) } t.Cleanup(func() { _ = g.DB().Close(ctx) }) if err := g.DB().GetCore().ClearTableFieldsAll(ctx); err != nil { t.Fatal(err) } exec := func(sql string) { t.Helper() if _, err := g.DB().Exec(ctx, sql); err != nil { t.Fatal(err) } } exec(`CREATE TABLE t_api_resource (id INTEGER PRIMARY KEY, method TEXT, path TEXT, status INTEGER)`) exec(`CREATE TABLE t_api (id INTEGER PRIMARY KEY, app_key TEXT, group_id INTEGER, status INTEGER)`) exec(`CREATE TABLE t_api_group_resource (group_id INTEGER, api_id INTEGER)`) exec(`INSERT INTO t_api VALUES (1, 'test-key', 7, 0)`) exec(`INSERT INTO t_api_resource VALUES (1, '1', '/openapi/v3/change/sim-subscribe', 0), (2, 'POST', '/openapi/v3/query/sim-info', 0), (3, '1', '/openapi/v3/query/sim-status', 1), (4, '1', '/openapi/v3/query/sim-imei-batch', 0)`) exec(`INSERT INTO t_api_group_resource VALUES (7, 1), (7, 2), (7, 3)`) middleware := New() check := func(t *testing.T, method, path string) (bool, error) { t.Helper() return middleware.hasOpenApiPermission(ctx, method, path) } for _, tc := range []struct { name, method, path string want bool }{ {"authorized numeric method", "POST", "/openapi/v3/change/sim-subscribe", true}, {"authorized text method", "POST", "/openapi/v3/query/sim-info", true}, {"normalized path", "post", "/openapi/v3/query/sim-info/", true}, {"unregistered resource", "POST", "/openapi/v3/query/batch-status", false}, {"legacy alias path never inherits grants", "POST", "/openapi/v3/common/sim-subscribe", false}, {"wrong method", "GET", "/openapi/v3/change/sim-subscribe", false}, {"disabled resource", "POST", "/openapi/v3/query/sim-status", false}, {"missing group binding", "POST", "/openapi/v3/query/sim-imei-batch", false}, } { t.Run(tc.name, func(t *testing.T) { got, err := check(t, tc.method, tc.path) if err != nil { t.Fatalf("business denial must not report a system failure: %v", err) } if got != tc.want { t.Fatalf("permission = %v, want %v", got, tc.want) } }) } // Token endpoints are exempted by router.exceptLogin only; the middleware must not // carry its own whitelist, otherwise the two lists drift apart. if got, err := check(t, "POST", "/openapi/v3/query/token"); err != nil || got { t.Fatalf("token path must not be whitelisted inside middleware, got=%v err=%v", got, err) } // The /common alias prefix has been removed from the router (router/common.go deleted). // Keep the path-level guard anyway: if an alias is ever re-introduced, it must still be // registered and granted on its own full path instead of inheriting /change permissions. exec(`INSERT INTO t_api_resource VALUES (5, '1', '/openapi/v3/common/sim-subscribe', 0)`) if got, err := check(t, "POST", "/openapi/v3/common/sim-subscribe"); err != nil || got { t.Fatalf("registered compatibility route without grant must be denied, got=%v err=%v", got, err) } exec(`INSERT INTO t_api_group_resource VALUES (7, 5)`) if got, err := check(t, "POST", "/openapi/v3/common/sim-subscribe"); err != nil || !got { t.Fatalf("explicitly granted compatibility route must be allowed, got=%v err=%v", got, err) } // Unusable accounts are a business denial, not a system failure. for _, sql := range []string{ `UPDATE t_api SET status = 1`, `UPDATE t_api SET status = 0, group_id = 0`, `DELETE FROM t_api`, } { exec(sql) got, err := check(t, "POST", "/openapi/v3/query/sim-info") if err != nil { t.Fatalf("account rejection must not be reported as a system failure after %s: %v", sql, err) } if got { t.Fatalf("invalid account allowed after %s", sql) } } // A broken permission lookup is a system failure: it must deny AND surface the error, // so callers can answer 10016 instead of misreporting it as 10014 no-permission. exec(`DROP TABLE t_api_resource`) got, err := check(t, "POST", "/openapi/v3/query/sim-info") if err == nil { t.Fatal("resource lookup failure must return an error") } if got { t.Fatal("resource lookup failure must deny access") } }