backend_mode_guard_test.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. //go:build unit
  2. package middleware
  3. import (
  4. "context"
  5. "net/http"
  6. "net/http/httptest"
  7. "testing"
  8. "github.com/Wei-Shaw/sub2api/internal/config"
  9. "github.com/Wei-Shaw/sub2api/internal/service"
  10. "github.com/gin-gonic/gin"
  11. "github.com/stretchr/testify/require"
  12. )
  13. type bmSettingRepo struct {
  14. values map[string]string
  15. }
  16. func (r *bmSettingRepo) Get(_ context.Context, _ string) (*service.Setting, error) {
  17. panic("unexpected Get call")
  18. }
  19. func (r *bmSettingRepo) GetValue(_ context.Context, key string) (string, error) {
  20. v, ok := r.values[key]
  21. if !ok {
  22. return "", service.ErrSettingNotFound
  23. }
  24. return v, nil
  25. }
  26. func (r *bmSettingRepo) Set(_ context.Context, _, _ string) error {
  27. panic("unexpected Set call")
  28. }
  29. func (r *bmSettingRepo) GetMultiple(_ context.Context, _ []string) (map[string]string, error) {
  30. panic("unexpected GetMultiple call")
  31. }
  32. func (r *bmSettingRepo) SetMultiple(_ context.Context, settings map[string]string) error {
  33. if r.values == nil {
  34. r.values = make(map[string]string, len(settings))
  35. }
  36. for key, value := range settings {
  37. r.values[key] = value
  38. }
  39. return nil
  40. }
  41. func (r *bmSettingRepo) GetAll(_ context.Context) (map[string]string, error) {
  42. panic("unexpected GetAll call")
  43. }
  44. func (r *bmSettingRepo) Delete(_ context.Context, _ string) error {
  45. panic("unexpected Delete call")
  46. }
  47. func newBackendModeSettingService(t *testing.T, enabled string) *service.SettingService {
  48. t.Helper()
  49. repo := &bmSettingRepo{
  50. values: map[string]string{
  51. service.SettingKeyBackendModeEnabled: enabled,
  52. },
  53. }
  54. svc := service.NewSettingService(repo, &config.Config{})
  55. require.NoError(t, svc.UpdateSettings(context.Background(), &service.SystemSettings{
  56. BackendModeEnabled: enabled == "true",
  57. }))
  58. return svc
  59. }
  60. func stringPtr(v string) *string {
  61. return &v
  62. }
  63. func TestBackendModeUserGuard(t *testing.T) {
  64. tests := []struct {
  65. name string
  66. nilService bool
  67. enabled string
  68. role *string
  69. wantStatus int
  70. }{
  71. {
  72. name: "disabled_allows_all",
  73. enabled: "false",
  74. role: stringPtr("user"),
  75. wantStatus: http.StatusOK,
  76. },
  77. {
  78. name: "nil_service_allows_all",
  79. nilService: true,
  80. role: stringPtr("user"),
  81. wantStatus: http.StatusOK,
  82. },
  83. {
  84. name: "enabled_admin_allowed",
  85. enabled: "true",
  86. role: stringPtr("admin"),
  87. wantStatus: http.StatusOK,
  88. },
  89. {
  90. name: "enabled_user_blocked",
  91. enabled: "true",
  92. role: stringPtr("user"),
  93. wantStatus: http.StatusForbidden,
  94. },
  95. {
  96. name: "enabled_no_role_blocked",
  97. enabled: "true",
  98. wantStatus: http.StatusForbidden,
  99. },
  100. {
  101. name: "enabled_empty_role_blocked",
  102. enabled: "true",
  103. role: stringPtr(""),
  104. wantStatus: http.StatusForbidden,
  105. },
  106. }
  107. for _, tc := range tests {
  108. tc := tc
  109. t.Run(tc.name, func(t *testing.T) {
  110. gin.SetMode(gin.TestMode)
  111. r := gin.New()
  112. if tc.role != nil {
  113. role := *tc.role
  114. r.Use(func(c *gin.Context) {
  115. c.Set(string(ContextKeyUserRole), role)
  116. c.Next()
  117. })
  118. }
  119. var svc *service.SettingService
  120. if !tc.nilService {
  121. svc = newBackendModeSettingService(t, tc.enabled)
  122. }
  123. r.Use(BackendModeUserGuard(svc))
  124. r.GET("/test", func(c *gin.Context) {
  125. c.JSON(http.StatusOK, gin.H{"ok": true})
  126. })
  127. w := httptest.NewRecorder()
  128. req := httptest.NewRequest(http.MethodGet, "/test", nil)
  129. r.ServeHTTP(w, req)
  130. require.Equal(t, tc.wantStatus, w.Code)
  131. })
  132. }
  133. }
  134. func TestBackendModeAuthGuard(t *testing.T) {
  135. tests := []struct {
  136. name string
  137. nilService bool
  138. enabled string
  139. path string
  140. wantStatus int
  141. }{
  142. {
  143. name: "disabled_allows_all",
  144. enabled: "false",
  145. path: "/api/v1/auth/register",
  146. wantStatus: http.StatusOK,
  147. },
  148. {
  149. name: "nil_service_allows_all",
  150. nilService: true,
  151. path: "/api/v1/auth/register",
  152. wantStatus: http.StatusOK,
  153. },
  154. {
  155. name: "enabled_allows_login",
  156. enabled: "true",
  157. path: "/api/v1/auth/login",
  158. wantStatus: http.StatusOK,
  159. },
  160. {
  161. name: "enabled_allows_login_2fa",
  162. enabled: "true",
  163. path: "/api/v1/auth/login/2fa",
  164. wantStatus: http.StatusOK,
  165. },
  166. {
  167. name: "enabled_allows_logout",
  168. enabled: "true",
  169. path: "/api/v1/auth/logout",
  170. wantStatus: http.StatusOK,
  171. },
  172. {
  173. name: "enabled_allows_refresh",
  174. enabled: "true",
  175. path: "/api/v1/auth/refresh",
  176. wantStatus: http.StatusOK,
  177. },
  178. {
  179. name: "enabled_blocks_linuxdo_oauth_start",
  180. enabled: "true",
  181. path: "/api/v1/auth/oauth/linuxdo/start",
  182. wantStatus: http.StatusForbidden,
  183. },
  184. {
  185. name: "enabled_allows_linuxdo_oauth_callback",
  186. enabled: "true",
  187. path: "/api/v1/auth/oauth/linuxdo/callback",
  188. wantStatus: http.StatusOK,
  189. },
  190. {
  191. name: "enabled_blocks_wechat_oauth_start",
  192. enabled: "true",
  193. path: "/api/v1/auth/oauth/wechat/start",
  194. wantStatus: http.StatusForbidden,
  195. },
  196. {
  197. name: "enabled_allows_wechat_oauth_callback",
  198. enabled: "true",
  199. path: "/api/v1/auth/oauth/wechat/callback",
  200. wantStatus: http.StatusOK,
  201. },
  202. {
  203. name: "enabled_blocks_wechat_payment_oauth_start",
  204. enabled: "true",
  205. path: "/api/v1/auth/oauth/wechat/payment/start",
  206. wantStatus: http.StatusForbidden,
  207. },
  208. {
  209. name: "enabled_allows_wechat_payment_oauth_callback",
  210. enabled: "true",
  211. path: "/api/v1/auth/oauth/wechat/payment/callback",
  212. wantStatus: http.StatusOK,
  213. },
  214. {
  215. name: "enabled_blocks_oidc_oauth_start",
  216. enabled: "true",
  217. path: "/api/v1/auth/oauth/oidc/start",
  218. wantStatus: http.StatusForbidden,
  219. },
  220. {
  221. name: "enabled_allows_oidc_oauth_callback",
  222. enabled: "true",
  223. path: "/api/v1/auth/oauth/oidc/callback",
  224. wantStatus: http.StatusOK,
  225. },
  226. {
  227. name: "enabled_blocks_github_oauth_start",
  228. enabled: "true",
  229. path: "/api/v1/auth/oauth/github/start",
  230. wantStatus: http.StatusForbidden,
  231. },
  232. {
  233. name: "enabled_allows_github_oauth_callback",
  234. enabled: "true",
  235. path: "/api/v1/auth/oauth/github/callback",
  236. wantStatus: http.StatusOK,
  237. },
  238. {
  239. name: "enabled_allows_github_complete_registration",
  240. enabled: "true",
  241. path: "/api/v1/auth/oauth/github/complete-registration",
  242. wantStatus: http.StatusOK,
  243. },
  244. {
  245. name: "enabled_blocks_google_oauth_start",
  246. enabled: "true",
  247. path: "/api/v1/auth/oauth/google/start",
  248. wantStatus: http.StatusForbidden,
  249. },
  250. {
  251. name: "enabled_allows_google_oauth_callback",
  252. enabled: "true",
  253. path: "/api/v1/auth/oauth/google/callback",
  254. wantStatus: http.StatusOK,
  255. },
  256. {
  257. name: "enabled_allows_google_complete_registration",
  258. enabled: "true",
  259. path: "/api/v1/auth/oauth/google/complete-registration",
  260. wantStatus: http.StatusOK,
  261. },
  262. {
  263. name: "enabled_blocks_dingtalk_oauth_start",
  264. enabled: "true",
  265. path: "/api/v1/auth/oauth/dingtalk/start",
  266. wantStatus: http.StatusForbidden,
  267. },
  268. {
  269. name: "enabled_allows_dingtalk_oauth_callback",
  270. enabled: "true",
  271. path: "/api/v1/auth/oauth/dingtalk/callback",
  272. wantStatus: http.StatusOK,
  273. },
  274. {
  275. name: "enabled_allows_dingtalk_complete_registration",
  276. enabled: "true",
  277. path: "/api/v1/auth/oauth/dingtalk/complete-registration",
  278. wantStatus: http.StatusOK,
  279. },
  280. {
  281. name: "enabled_allows_dingtalk_create_account",
  282. enabled: "true",
  283. path: "/api/v1/auth/oauth/dingtalk/create-account",
  284. wantStatus: http.StatusOK,
  285. },
  286. {
  287. name: "enabled_allows_dingtalk_bind_login",
  288. enabled: "true",
  289. path: "/api/v1/auth/oauth/dingtalk/bind-login",
  290. wantStatus: http.StatusOK,
  291. },
  292. {
  293. name: "enabled_allows_oauth_pending_exchange",
  294. enabled: "true",
  295. path: "/api/v1/auth/oauth/pending/exchange",
  296. wantStatus: http.StatusOK,
  297. },
  298. {
  299. name: "enabled_allows_oauth_pending_send_verify_code",
  300. enabled: "true",
  301. path: "/api/v1/auth/oauth/pending/send-verify-code",
  302. wantStatus: http.StatusOK,
  303. },
  304. {
  305. name: "enabled_allows_oauth_pending_create_account",
  306. enabled: "true",
  307. path: "/api/v1/auth/oauth/pending/create-account",
  308. wantStatus: http.StatusOK,
  309. },
  310. {
  311. name: "enabled_allows_oauth_pending_bind_login",
  312. enabled: "true",
  313. path: "/api/v1/auth/oauth/pending/bind-login",
  314. wantStatus: http.StatusOK,
  315. },
  316. {
  317. name: "enabled_allows_provider_bind_login",
  318. enabled: "true",
  319. path: "/api/v1/auth/oauth/oidc/bind-login",
  320. wantStatus: http.StatusOK,
  321. },
  322. {
  323. name: "enabled_allows_provider_create_account",
  324. enabled: "true",
  325. path: "/api/v1/auth/oauth/wechat/create-account",
  326. wantStatus: http.StatusOK,
  327. },
  328. {
  329. name: "enabled_allows_legacy_complete_registration",
  330. enabled: "true",
  331. path: "/api/v1/auth/oauth/linuxdo/complete-registration",
  332. wantStatus: http.StatusOK,
  333. },
  334. {
  335. name: "enabled_blocks_register",
  336. enabled: "true",
  337. path: "/api/v1/auth/register",
  338. wantStatus: http.StatusForbidden,
  339. },
  340. {
  341. name: "enabled_blocks_forgot_password",
  342. enabled: "true",
  343. path: "/api/v1/auth/forgot-password",
  344. wantStatus: http.StatusForbidden,
  345. },
  346. }
  347. for _, tc := range tests {
  348. tc := tc
  349. t.Run(tc.name, func(t *testing.T) {
  350. gin.SetMode(gin.TestMode)
  351. r := gin.New()
  352. var svc *service.SettingService
  353. if !tc.nilService {
  354. svc = newBackendModeSettingService(t, tc.enabled)
  355. }
  356. r.Use(BackendModeAuthGuard(svc))
  357. r.Any("/*path", func(c *gin.Context) {
  358. c.JSON(http.StatusOK, gin.H{"ok": true})
  359. })
  360. w := httptest.NewRecorder()
  361. req := httptest.NewRequest(http.MethodGet, tc.path, nil)
  362. r.ServeHTTP(w, req)
  363. require.Equal(t, tc.wantStatus, w.Code)
  364. })
  365. }
  366. }