github.com/cloudreve/Cloudreve/v3@v3.0.0-20240224133659-3edb00a6484c/middleware/auth_test.go (about) 1 package middleware 2 3 import ( 4 "database/sql" 5 "errors" 6 "github.com/cloudreve/Cloudreve/v3/pkg/cache" 7 "github.com/cloudreve/Cloudreve/v3/pkg/filesystem" 8 "github.com/cloudreve/Cloudreve/v3/pkg/mq" 9 "github.com/cloudreve/Cloudreve/v3/pkg/serializer" 10 "github.com/qiniu/go-sdk/v7/auth/qbox" 11 "io/ioutil" 12 "net/http" 13 "net/http/httptest" 14 "strings" 15 "testing" 16 "time" 17 18 "github.com/DATA-DOG/go-sqlmock" 19 model "github.com/cloudreve/Cloudreve/v3/models" 20 "github.com/cloudreve/Cloudreve/v3/pkg/auth" 21 "github.com/cloudreve/Cloudreve/v3/pkg/util" 22 "github.com/gin-gonic/gin" 23 "github.com/jinzhu/gorm" 24 "github.com/stretchr/testify/assert" 25 ) 26 27 var mock sqlmock.Sqlmock 28 29 // TestMain 初始化数据库Mock 30 func TestMain(m *testing.M) { 31 var db *sql.DB 32 var err error 33 db, mock, err = sqlmock.New() 34 if err != nil { 35 panic("An error was not expected when opening a stub database connection") 36 } 37 model.DB, _ = gorm.Open("mysql", db) 38 defer db.Close() 39 m.Run() 40 } 41 42 func TestCurrentUser(t *testing.T) { 43 asserts := assert.New(t) 44 rec := httptest.NewRecorder() 45 c, _ := gin.CreateTestContext(rec) 46 c.Request, _ = http.NewRequest("GET", "/test", nil) 47 48 //session为空 49 sessionFunc := Session("233") 50 sessionFunc(c) 51 CurrentUser()(c) 52 user, _ := c.Get("user") 53 asserts.Nil(user) 54 55 //session正确 56 c, _ = gin.CreateTestContext(rec) 57 c.Request, _ = http.NewRequest("GET", "/test", nil) 58 sessionFunc(c) 59 util.SetSession(c, map[string]interface{}{"user_id": 1}) 60 rows := sqlmock.NewRows([]string{"id", "deleted_at", "email", "options"}). 61 AddRow(1, nil, "admin@cloudreve.org", "{}") 62 mock.ExpectQuery("^SELECT (.+)").WillReturnRows(rows) 63 CurrentUser()(c) 64 user, _ = c.Get("user") 65 asserts.NotNil(user) 66 asserts.NoError(mock.ExpectationsWereMet()) 67 } 68 69 func TestAuthRequired(t *testing.T) { 70 asserts := assert.New(t) 71 rec := httptest.NewRecorder() 72 c, _ := gin.CreateTestContext(rec) 73 c.Request, _ = http.NewRequest("GET", "/test", nil) 74 AuthRequiredFunc := AuthRequired() 75 76 // 未登录 77 AuthRequiredFunc(c) 78 asserts.NotNil(c) 79 80 // 类型错误 81 c.Set("user", 123) 82 AuthRequiredFunc(c) 83 asserts.NotNil(c) 84 85 // 正常 86 c.Set("user", &model.User{}) 87 AuthRequiredFunc(c) 88 asserts.NotNil(c) 89 } 90 91 func TestSignRequired(t *testing.T) { 92 asserts := assert.New(t) 93 rec := httptest.NewRecorder() 94 c, _ := gin.CreateTestContext(rec) 95 c.Request, _ = http.NewRequest("GET", "/test", nil) 96 authInstance := auth.HMACAuth{SecretKey: []byte(util.RandStringRunes(256))} 97 SignRequiredFunc := SignRequired(authInstance) 98 99 // 鉴权失败 100 SignRequiredFunc(c) 101 asserts.NotNil(c) 102 asserts.True(c.IsAborted()) 103 104 c, _ = gin.CreateTestContext(rec) 105 c.Request, _ = http.NewRequest("PUT", "/test", nil) 106 SignRequiredFunc(c) 107 asserts.NotNil(c) 108 asserts.True(c.IsAborted()) 109 110 // Sign verify success 111 c, _ = gin.CreateTestContext(rec) 112 c.Request, _ = http.NewRequest("PUT", "/test", nil) 113 c.Request = auth.SignRequest(authInstance, c.Request, 0) 114 SignRequiredFunc(c) 115 asserts.NotNil(c) 116 asserts.False(c.IsAborted()) 117 } 118 119 func TestWebDAVAuth(t *testing.T) { 120 asserts := assert.New(t) 121 rec := httptest.NewRecorder() 122 AuthFunc := WebDAVAuth() 123 124 // options请求跳过验证 125 { 126 c, _ := gin.CreateTestContext(rec) 127 c.Request, _ = http.NewRequest("OPTIONS", "/test", nil) 128 AuthFunc(c) 129 } 130 131 // 请求HTTP Basic Auth 132 { 133 c, _ := gin.CreateTestContext(rec) 134 c.Request, _ = http.NewRequest("POST", "/test", nil) 135 AuthFunc(c) 136 asserts.NotEmpty(c.Writer.Header()["WWW-Authenticate"]) 137 } 138 139 // 用户名不存在 140 { 141 c, _ := gin.CreateTestContext(rec) 142 c.Request, _ = http.NewRequest("POST", "/test", nil) 143 c.Request.Header = map[string][]string{ 144 "Authorization": {"Basic d2hvQGNsb3VkcmV2ZS5vcmc6YWRtaW4="}, 145 } 146 mock.ExpectQuery("SELECT(.+)users(.+)"). 147 WillReturnRows( 148 sqlmock.NewRows([]string{"id", "password", "email"}), 149 ) 150 AuthFunc(c) 151 asserts.NoError(mock.ExpectationsWereMet()) 152 asserts.Equal(c.Writer.Status(), http.StatusUnauthorized) 153 } 154 155 // 密码错误 156 { 157 c, _ := gin.CreateTestContext(rec) 158 c.Request, _ = http.NewRequest("POST", "/test", nil) 159 c.Request.Header = map[string][]string{ 160 "Authorization": {"Basic d2hvQGNsb3VkcmV2ZS5vcmc6YWRtaW4="}, 161 } 162 mock.ExpectQuery("SELECT(.+)users(.+)"). 163 WillReturnRows( 164 sqlmock.NewRows([]string{"id", "password", "email", "options"}).AddRow(1, "123", "who@cloudreve.org", "{}"), 165 ) 166 // 查找密码 167 mock.ExpectQuery("SELECT(.+)webdav(.+)").WillReturnRows(sqlmock.NewRows([]string{"id"})) 168 AuthFunc(c) 169 asserts.NoError(mock.ExpectationsWereMet()) 170 asserts.Equal(c.Writer.Status(), http.StatusUnauthorized) 171 } 172 173 //未启用 WebDAV 174 { 175 c, _ := gin.CreateTestContext(rec) 176 c.Request, _ = http.NewRequest("POST", "/test", nil) 177 c.Request.Header = map[string][]string{ 178 "Authorization": {"Basic d2hvQGNsb3VkcmV2ZS5vcmc6YWRtaW4="}, 179 } 180 mock.ExpectQuery("SELECT(.+)users(.+)"). 181 WillReturnRows( 182 sqlmock.NewRows( 183 []string{"id", "password", "email", "group_id", "options"}). 184 AddRow(1, 185 "rfBd67ti3SMtYvSg:ce6dc7bca4f17f2660e18e7608686673eae0fdf3", 186 "who@cloudreve.org", 187 1, 188 "{}", 189 ), 190 ) 191 mock.ExpectQuery("SELECT(.+)groups(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "web_dav_enabled"}).AddRow(1, false)) 192 // 查找密码 193 mock.ExpectQuery("SELECT(.+)webdav(.+)").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1)) 194 AuthFunc(c) 195 asserts.NoError(mock.ExpectationsWereMet()) 196 asserts.Equal(c.Writer.Status(), http.StatusForbidden) 197 } 198 199 //正常 200 { 201 c, _ := gin.CreateTestContext(rec) 202 c.Request, _ = http.NewRequest("POST", "/test", nil) 203 c.Request.Header = map[string][]string{ 204 "Authorization": {"Basic d2hvQGNsb3VkcmV2ZS5vcmc6YWRtaW4="}, 205 } 206 mock.ExpectQuery("SELECT(.+)users(.+)"). 207 WillReturnRows( 208 sqlmock.NewRows( 209 []string{"id", "password", "email", "group_id", "options"}). 210 AddRow(1, 211 "rfBd67ti3SMtYvSg:ce6dc7bca4f17f2660e18e7608686673eae0fdf3", 212 "who@cloudreve.org", 213 1, 214 "{}", 215 ), 216 ) 217 mock.ExpectQuery("SELECT(.+)groups(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "web_dav_enabled"}).AddRow(1, true)) 218 // 查找密码 219 mock.ExpectQuery("SELECT(.+)webdav(.+)").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1)) 220 AuthFunc(c) 221 asserts.NoError(mock.ExpectationsWereMet()) 222 asserts.Equal(c.Writer.Status(), 200) 223 _, ok := c.Get("user") 224 asserts.True(ok) 225 } 226 227 } 228 229 func TestUseUploadSession(t *testing.T) { 230 asserts := assert.New(t) 231 rec := httptest.NewRecorder() 232 AuthFunc := UseUploadSession("local") 233 234 // sessionID 为空 235 { 236 237 c, _ := gin.CreateTestContext(rec) 238 c.Params = []gin.Param{} 239 c.Request, _ = http.NewRequest("POST", "/api/v3/callback/remote/sessionID", nil) 240 authInstance := auth.HMACAuth{SecretKey: []byte("123")} 241 auth.SignRequest(authInstance, c.Request, 0) 242 AuthFunc(c) 243 asserts.True(c.IsAborted()) 244 } 245 246 // 成功 247 { 248 cache.Set( 249 filesystem.UploadSessionCachePrefix+"testCallBackRemote", 250 serializer.UploadSession{ 251 UID: 1, 252 VirtualPath: "/", 253 Policy: model.Policy{Type: "local"}, 254 }, 255 0, 256 ) 257 cache.Deletes([]string{"1"}, "policy_") 258 mock.ExpectQuery("SELECT(.+)users(.+)"). 259 WillReturnRows(sqlmock.NewRows([]string{"id", "group_id"}).AddRow(1, 1)) 260 mock.ExpectQuery("SELECT(.+)groups(.+)"). 261 WillReturnRows(sqlmock.NewRows([]string{"id", "policies"}).AddRow(1, "[513]")) 262 mock.ExpectQuery("SELECT(.+)policies(.+)"). 263 WillReturnRows(sqlmock.NewRows([]string{"id", "secret_key"}).AddRow(2, "123")) 264 c, _ := gin.CreateTestContext(rec) 265 c.Params = []gin.Param{ 266 {"sessionID", "testCallBackRemote"}, 267 } 268 c.Request, _ = http.NewRequest("POST", "/api/v3/callback/remote/testCallBackRemote", nil) 269 authInstance := auth.HMACAuth{SecretKey: []byte("123")} 270 auth.SignRequest(authInstance, c.Request, 0) 271 AuthFunc(c) 272 asserts.NoError(mock.ExpectationsWereMet()) 273 asserts.False(c.IsAborted()) 274 } 275 } 276 277 func TestUploadCallbackCheck(t *testing.T) { 278 a := assert.New(t) 279 rec := httptest.NewRecorder() 280 281 // 上传会话不存在 282 { 283 c, _ := gin.CreateTestContext(rec) 284 c.Params = []gin.Param{ 285 {"sessionID", "testSessionNotExist"}, 286 } 287 res := uploadCallbackCheck(c, "local") 288 a.Contains("上传会话不存在或已过期", res.Msg) 289 } 290 291 // 上传策略不一致 292 { 293 c, _ := gin.CreateTestContext(rec) 294 c.Params = []gin.Param{ 295 {"sessionID", "testPolicyNotMatch"}, 296 } 297 cache.Set( 298 filesystem.UploadSessionCachePrefix+"testPolicyNotMatch", 299 serializer.UploadSession{ 300 UID: 1, 301 VirtualPath: "/", 302 Policy: model.Policy{Type: "remote"}, 303 }, 304 0, 305 ) 306 res := uploadCallbackCheck(c, "local") 307 a.Contains("Policy not supported", res.Msg) 308 } 309 310 // 用户不存在 311 { 312 c, _ := gin.CreateTestContext(rec) 313 c.Params = []gin.Param{ 314 {"sessionID", "testUserNotExist"}, 315 } 316 cache.Set( 317 filesystem.UploadSessionCachePrefix+"testUserNotExist", 318 serializer.UploadSession{ 319 UID: 313, 320 VirtualPath: "/", 321 Policy: model.Policy{Type: "remote"}, 322 }, 323 0, 324 ) 325 mock.ExpectQuery("SELECT(.+)users(.+)"). 326 WillReturnRows(sqlmock.NewRows([]string{"id", "group_id"})) 327 res := uploadCallbackCheck(c, "remote") 328 a.Contains("找不到用户", res.Msg) 329 a.NoError(mock.ExpectationsWereMet()) 330 _, ok := cache.Get(filesystem.UploadSessionCachePrefix + "testUserNotExist") 331 a.False(ok) 332 } 333 } 334 335 func TestRemoteCallbackAuth(t *testing.T) { 336 asserts := assert.New(t) 337 rec := httptest.NewRecorder() 338 AuthFunc := RemoteCallbackAuth() 339 340 // 成功 341 { 342 c, _ := gin.CreateTestContext(rec) 343 c.Set(filesystem.UploadSessionCtx, &serializer.UploadSession{ 344 UID: 1, 345 VirtualPath: "/", 346 Policy: model.Policy{SecretKey: "123"}, 347 }) 348 c.Request, _ = http.NewRequest("POST", "/api/v3/callback/remote/testCallBackRemote", nil) 349 authInstance := auth.HMACAuth{SecretKey: []byte("123")} 350 auth.SignRequest(authInstance, c.Request, 0) 351 AuthFunc(c) 352 asserts.False(c.IsAborted()) 353 } 354 355 // 签名错误 356 { 357 c, _ := gin.CreateTestContext(rec) 358 c.Set(filesystem.UploadSessionCtx, &serializer.UploadSession{ 359 UID: 1, 360 VirtualPath: "/", 361 Policy: model.Policy{SecretKey: "123"}, 362 }) 363 c.Request, _ = http.NewRequest("POST", "/api/v3/callback/remote/testCallBackRemote", nil) 364 AuthFunc(c) 365 asserts.True(c.IsAborted()) 366 } 367 } 368 369 func TestQiniuCallbackAuth(t *testing.T) { 370 asserts := assert.New(t) 371 rec := httptest.NewRecorder() 372 AuthFunc := QiniuCallbackAuth() 373 374 // 成功 375 { 376 c, _ := gin.CreateTestContext(rec) 377 c.Set(filesystem.UploadSessionCtx, &serializer.UploadSession{ 378 UID: 1, 379 VirtualPath: "/", 380 Policy: model.Policy{ 381 SecretKey: "123", 382 AccessKey: "123", 383 }, 384 }) 385 c.Request, _ = http.NewRequest("POST", "/api/v3/callback/qiniu/testCallBackQiniu", nil) 386 mac := qbox.NewMac("123", "123") 387 token, err := mac.SignRequest(c.Request) 388 asserts.NoError(err) 389 c.Request.Header["Authorization"] = []string{"QBox " + token} 390 AuthFunc(c) 391 asserts.NoError(mock.ExpectationsWereMet()) 392 asserts.False(c.IsAborted()) 393 } 394 395 // 验证失败 396 { 397 c, _ := gin.CreateTestContext(rec) 398 c.Set(filesystem.UploadSessionCtx, &serializer.UploadSession{ 399 UID: 1, 400 VirtualPath: "/", 401 Policy: model.Policy{ 402 SecretKey: "123", 403 AccessKey: "123", 404 }, 405 }) 406 c.Request, _ = http.NewRequest("POST", "/api/v3/callback/qiniu/testCallBackQiniu", nil) 407 mac := qbox.NewMac("123", "1213") 408 token, err := mac.SignRequest(c.Request) 409 asserts.NoError(err) 410 c.Request.Header["Authorization"] = []string{"QBox " + token} 411 AuthFunc(c) 412 asserts.True(c.IsAborted()) 413 } 414 } 415 416 func TestOSSCallbackAuth(t *testing.T) { 417 asserts := assert.New(t) 418 rec := httptest.NewRecorder() 419 AuthFunc := OSSCallbackAuth() 420 421 // 签名验证失败 422 { 423 c, _ := gin.CreateTestContext(rec) 424 c.Set(filesystem.UploadSessionCtx, &serializer.UploadSession{ 425 UID: 1, 426 VirtualPath: "/", 427 Policy: model.Policy{ 428 SecretKey: "123", 429 AccessKey: "123", 430 }, 431 }) 432 c.Request, _ = http.NewRequest("POST", "/api/v3/callback/oss/testCallBackOSS", nil) 433 mac := qbox.NewMac("123", "123") 434 token, err := mac.SignRequest(c.Request) 435 asserts.NoError(err) 436 c.Request.Header["Authorization"] = []string{"QBox " + token} 437 AuthFunc(c) 438 asserts.True(c.IsAborted()) 439 } 440 441 // 成功 442 { 443 c, _ := gin.CreateTestContext(rec) 444 c.Set(filesystem.UploadSessionCtx, &serializer.UploadSession{ 445 UID: 1, 446 VirtualPath: "/", 447 Policy: model.Policy{ 448 SecretKey: "123", 449 AccessKey: "123", 450 }, 451 }) 452 c.Request, _ = http.NewRequest("POST", "/api/v3/callback/oss/TnXx5E5VyfJUyM1UdkdDu1rtnJ34EbmH", ioutil.NopCloser(strings.NewReader(`{"name":"2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","source_name":"1/1_hFRtDLgM_2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","size":114020,"pic_info":"810,539"}`))) 453 c.Request.Header["Authorization"] = []string{"e5LwzwTkP9AFAItT4YzvdJOHd0Y0wqTMWhsV/h5SG90JYGAmMd+8LQyj96R+9qUfJWjMt6suuUh7LaOryR87Dw=="} 454 c.Request.Header["X-Oss-Pub-Key-Url"] = []string{"aHR0cHM6Ly9nb3NzcHVibGljLmFsaWNkbi5jb20vY2FsbGJhY2tfcHViX2tleV92MS5wZW0="} 455 AuthFunc(c) 456 asserts.False(c.IsAborted()) 457 } 458 459 } 460 461 type fakeRead string 462 463 func (r fakeRead) Read(p []byte) (int, error) { 464 return 0, errors.New("error") 465 } 466 467 func TestUpyunCallbackAuth(t *testing.T) { 468 asserts := assert.New(t) 469 rec := httptest.NewRecorder() 470 AuthFunc := UpyunCallbackAuth() 471 472 // 无法获取请求正文 473 { 474 c, _ := gin.CreateTestContext(rec) 475 c.Set(filesystem.UploadSessionCtx, &serializer.UploadSession{ 476 UID: 1, 477 VirtualPath: "/", 478 Policy: model.Policy{ 479 SecretKey: "123", 480 AccessKey: "123", 481 }, 482 }) 483 c.Request, _ = http.NewRequest("POST", "/api/v3/callback/upyun/testCallBackUpyun", ioutil.NopCloser(fakeRead(""))) 484 AuthFunc(c) 485 asserts.True(c.IsAborted()) 486 } 487 488 // 正文MD5不一致 489 { 490 c, _ := gin.CreateTestContext(rec) 491 c.Set(filesystem.UploadSessionCtx, &serializer.UploadSession{ 492 UID: 1, 493 VirtualPath: "/", 494 Policy: model.Policy{ 495 SecretKey: "123", 496 AccessKey: "123", 497 }, 498 }) 499 c.Request, _ = http.NewRequest("POST", "/api/v3/callback/upyun/testCallBackUpyun", ioutil.NopCloser(strings.NewReader("1"))) 500 c.Request.Header["Content-Md5"] = []string{"123"} 501 AuthFunc(c) 502 asserts.True(c.IsAborted()) 503 } 504 505 // 签名不一致 506 { 507 c, _ := gin.CreateTestContext(rec) 508 c.Set(filesystem.UploadSessionCtx, &serializer.UploadSession{ 509 UID: 1, 510 VirtualPath: "/", 511 Policy: model.Policy{ 512 SecretKey: "123", 513 AccessKey: "123", 514 }, 515 }) 516 c.Request, _ = http.NewRequest("POST", "/api/v3/callback/upyun/testCallBackUpyun", ioutil.NopCloser(strings.NewReader("1"))) 517 c.Request.Header["Content-Md5"] = []string{"c4ca4238a0b923820dcc509a6f75849b"} 518 AuthFunc(c) 519 asserts.True(c.IsAborted()) 520 } 521 522 // 成功 523 { 524 c, _ := gin.CreateTestContext(rec) 525 c.Set(filesystem.UploadSessionCtx, &serializer.UploadSession{ 526 UID: 1, 527 VirtualPath: "/", 528 Policy: model.Policy{ 529 SecretKey: "123", 530 AccessKey: "123", 531 }, 532 }) 533 c.Request, _ = http.NewRequest("POST", "/api/v3/callback/upyun/testCallBackUpyun", ioutil.NopCloser(strings.NewReader("1"))) 534 c.Request.Header["Content-Md5"] = []string{"c4ca4238a0b923820dcc509a6f75849b"} 535 c.Request.Header["Authorization"] = []string{"UPYUN 123:GWueK9x493BKFFk5gmfdO2Mn6EM="} 536 AuthFunc(c) 537 asserts.False(c.IsAborted()) 538 } 539 } 540 541 func TestOneDriveCallbackAuth(t *testing.T) { 542 asserts := assert.New(t) 543 rec := httptest.NewRecorder() 544 AuthFunc := OneDriveCallbackAuth() 545 546 // 成功 547 { 548 c, _ := gin.CreateTestContext(rec) 549 c.Params = []gin.Param{ 550 {"sessionID", "TestOneDriveCallbackAuth"}, 551 } 552 c.Set(filesystem.UploadSessionCtx, &serializer.UploadSession{ 553 UID: 1, 554 VirtualPath: "/", 555 Policy: model.Policy{ 556 SecretKey: "123", 557 AccessKey: "123", 558 }, 559 }) 560 c.Request, _ = http.NewRequest("POST", "/api/v3/callback/upyun/TestOneDriveCallbackAuth", ioutil.NopCloser(strings.NewReader("1"))) 561 res := mq.GlobalMQ.Subscribe("TestOneDriveCallbackAuth", 1) 562 AuthFunc(c) 563 select { 564 case <-res: 565 case <-time.After(time.Millisecond * 500): 566 asserts.Fail("mq message should be published") 567 } 568 asserts.False(c.IsAborted()) 569 } 570 } 571 572 func TestIsAdmin(t *testing.T) { 573 asserts := assert.New(t) 574 rec := httptest.NewRecorder() 575 testFunc := IsAdmin() 576 577 // 非管理员 578 { 579 c, _ := gin.CreateTestContext(rec) 580 c.Set("user", &model.User{}) 581 testFunc(c) 582 asserts.True(c.IsAborted()) 583 } 584 585 // 是管理员 586 { 587 c, _ := gin.CreateTestContext(rec) 588 user := &model.User{} 589 user.Group.ID = 1 590 c.Set("user", user) 591 testFunc(c) 592 asserts.False(c.IsAborted()) 593 } 594 595 // 初始用户,非管理组 596 { 597 c, _ := gin.CreateTestContext(rec) 598 user := &model.User{} 599 user.Group.ID = 2 600 user.ID = 1 601 c.Set("user", user) 602 testFunc(c) 603 asserts.False(c.IsAborted()) 604 } 605 }