|
8 | 8 | "net/http/httptest" |
9 | 9 | "testing" |
10 | 10 |
|
| 11 | + "github.com/gofrs/uuid" |
11 | 12 | "github.com/stretchr/testify/assert" |
12 | 13 | "github.com/stretchr/testify/require" |
13 | 14 | "github.com/stretchr/testify/suite" |
@@ -437,3 +438,228 @@ func (ts *OAuthClientTestSuite) TestHandlerValidation() { |
437 | 438 | require.Error(ts.T(), err) |
438 | 439 | assert.Contains(ts.T(), err.Error(), "invalid redirect_uri") |
439 | 440 | } |
| 441 | + |
| 442 | +// Helper function to create a test user |
| 443 | +func (ts *OAuthClientTestSuite) createTestUser(email string) *models.User { |
| 444 | + user, err := models.NewUser("", email, "password123", "authenticated", nil) |
| 445 | + require.NoError(ts.T(), err) |
| 446 | + require.NotNil(ts.T(), user) |
| 447 | + |
| 448 | + err = ts.DB.Create(user) |
| 449 | + require.NoError(ts.T(), err) |
| 450 | + |
| 451 | + return user |
| 452 | +} |
| 453 | + |
| 454 | +// Helper function to create a test OAuth consent |
| 455 | +func (ts *OAuthClientTestSuite) createTestConsent(userID, clientID string, scopes []string) *models.OAuthServerConsent { |
| 456 | + userUUID, err := uuid.FromString(userID) |
| 457 | + require.NoError(ts.T(), err) |
| 458 | + |
| 459 | + clientUUID, err := uuid.FromString(clientID) |
| 460 | + require.NoError(ts.T(), err) |
| 461 | + |
| 462 | + consent := models.NewOAuthServerConsent(userUUID, clientUUID, scopes) |
| 463 | + require.NoError(ts.T(), models.UpsertOAuthServerConsent(ts.DB, consent)) |
| 464 | + |
| 465 | + return consent |
| 466 | +} |
| 467 | + |
| 468 | +// Helper function to create a test session for OAuth |
| 469 | +func (ts *OAuthClientTestSuite) createTestSession(userID, clientID string) *models.Session { |
| 470 | + userUUID, err := uuid.FromString(userID) |
| 471 | + require.NoError(ts.T(), err) |
| 472 | + |
| 473 | + clientUUID, err := uuid.FromString(clientID) |
| 474 | + require.NoError(ts.T(), err) |
| 475 | + |
| 476 | + session, err := models.NewSession(userUUID, nil) |
| 477 | + require.NoError(ts.T(), err) |
| 478 | + session.OAuthClientID = &clientUUID |
| 479 | + |
| 480 | + err = ts.DB.Create(session) |
| 481 | + require.NoError(ts.T(), err) |
| 482 | + |
| 483 | + return session |
| 484 | +} |
| 485 | + |
| 486 | +func (ts *OAuthClientTestSuite) TestUserListOAuthGrants() { |
| 487 | + // Create test user |
| 488 | + user := ts. createTestUser( "[email protected]") |
| 489 | + |
| 490 | + // Create test OAuth clients |
| 491 | + client1, _ := ts.createTestOAuthClient() |
| 492 | + client2, _ := ts.createTestOAuthClient() |
| 493 | + |
| 494 | + // Create consents for the user |
| 495 | + ts.createTestConsent(user.ID.String(), client1.ID.String(), []string{"read", "write"}) |
| 496 | + ts.createTestConsent(user.ID.String(), client2.ID.String(), []string{"read"}) |
| 497 | + |
| 498 | + // Create HTTP request |
| 499 | + req := httptest.NewRequest(http.MethodGet, "/user/oauth/grants", nil) |
| 500 | + |
| 501 | + // Add user to context (normally done by requireAuthentication middleware) |
| 502 | + ctx := shared.WithUser(req.Context(), user) |
| 503 | + req = req.WithContext(ctx) |
| 504 | + |
| 505 | + w := httptest.NewRecorder() |
| 506 | + |
| 507 | + // Call handler |
| 508 | + err := ts.Server.UserListOAuthGrants(w, req) |
| 509 | + require.NoError(ts.T(), err) |
| 510 | + |
| 511 | + // Check response |
| 512 | + assert.Equal(ts.T(), http.StatusOK, w.Code) |
| 513 | + |
| 514 | + var response UserOAuthGrantsListResponse |
| 515 | + err = json.Unmarshal(w.Body.Bytes(), &response) |
| 516 | + require.NoError(ts.T(), err) |
| 517 | + |
| 518 | + // Should have 2 grants |
| 519 | + assert.Len(ts.T(), response.Grants, 2) |
| 520 | + |
| 521 | + // Verify client details are included |
| 522 | + for _, grant := range response.Grants { |
| 523 | + assert.NotEmpty(ts.T(), grant.ClientID) |
| 524 | + assert.Equal(ts.T(), "Test Client", grant.ClientName) |
| 525 | + assert.NotEmpty(ts.T(), grant.Scopes) |
| 526 | + assert.NotEmpty(ts.T(), grant.GrantedAt) |
| 527 | + } |
| 528 | + |
| 529 | + // Check that client1 (with read and write scopes) is in the response |
| 530 | + found := false |
| 531 | + for _, grant := range response.Grants { |
| 532 | + if grant.ClientID == client1.ID.String() { |
| 533 | + found = true |
| 534 | + assert.Contains(ts.T(), grant.Scopes, "read") |
| 535 | + assert.Contains(ts.T(), grant.Scopes, "write") |
| 536 | + } |
| 537 | + } |
| 538 | + assert.True(ts.T(), found, "client1 should be in the grants list") |
| 539 | +} |
| 540 | + |
| 541 | +func (ts *OAuthClientTestSuite) TestUserListOAuthGrantsEmpty() { |
| 542 | + // Create test user with no grants |
| 543 | + user := ts. createTestUser( "[email protected]") |
| 544 | + |
| 545 | + req := httptest.NewRequest(http.MethodGet, "/user/oauth/grants", nil) |
| 546 | + ctx := shared.WithUser(req.Context(), user) |
| 547 | + req = req.WithContext(ctx) |
| 548 | + |
| 549 | + w := httptest.NewRecorder() |
| 550 | + |
| 551 | + err := ts.Server.UserListOAuthGrants(w, req) |
| 552 | + require.NoError(ts.T(), err) |
| 553 | + |
| 554 | + assert.Equal(ts.T(), http.StatusOK, w.Code) |
| 555 | + |
| 556 | + var response UserOAuthGrantsListResponse |
| 557 | + err = json.Unmarshal(w.Body.Bytes(), &response) |
| 558 | + require.NoError(ts.T(), err) |
| 559 | + |
| 560 | + // Should have 0 grants |
| 561 | + assert.Len(ts.T(), response.Grants, 0) |
| 562 | +} |
| 563 | + |
| 564 | +func (ts *OAuthClientTestSuite) TestUserListOAuthGrantsNoAuth() { |
| 565 | + // Test without user in context (unauthenticated) |
| 566 | + req := httptest.NewRequest(http.MethodGet, "/user/oauth/grants", nil) |
| 567 | + w := httptest.NewRecorder() |
| 568 | + |
| 569 | + err := ts.Server.UserListOAuthGrants(w, req) |
| 570 | + require.Error(ts.T(), err) |
| 571 | + assert.Contains(ts.T(), err.Error(), "authentication required") |
| 572 | +} |
| 573 | + |
| 574 | +func (ts *OAuthClientTestSuite) TestUserRevokeOAuthGrant() { |
| 575 | + // Create test user |
| 576 | + user := ts. createTestUser( "[email protected]") |
| 577 | + |
| 578 | + // Create a client and consent |
| 579 | + client, _ := ts.createTestOAuthClient() |
| 580 | + ts.createTestConsent(user.ID.String(), client.ID.String(), []string{"read", "write"}) |
| 581 | + |
| 582 | + // Create a session for this OAuth client |
| 583 | + session := ts.createTestSession(user.ID.String(), client.ID.String()) |
| 584 | + |
| 585 | + // Create HTTP request with query parameter |
| 586 | + req := httptest.NewRequest(http.MethodDelete, "/user/oauth/grants?client_id="+client.ID.String(), nil) |
| 587 | + |
| 588 | + // Add user to context |
| 589 | + ctx := shared.WithUser(req.Context(), user) |
| 590 | + req = req.WithContext(ctx) |
| 591 | + |
| 592 | + w := httptest.NewRecorder() |
| 593 | + |
| 594 | + // Call handler - should succeed |
| 595 | + err := ts.Server.UserRevokeOAuthGrant(w, req) |
| 596 | + require.NoError(ts.T(), err) |
| 597 | + |
| 598 | + // Check response |
| 599 | + assert.Equal(ts.T(), http.StatusNoContent, w.Code) |
| 600 | + assert.Empty(ts.T(), w.Body.String()) |
| 601 | + |
| 602 | + // Verify consent was revoked |
| 603 | + consent, err := models.FindOAuthServerConsentByUserAndClient(ts.DB, user.ID, client.ID) |
| 604 | + require.NoError(ts.T(), err) |
| 605 | + assert.NotNil(ts.T(), consent.RevokedAt, "consent should be revoked") |
| 606 | + |
| 607 | + // Verify session was deleted |
| 608 | + deletedSession, err := models.FindSessionByID(ts.DB, session.ID, false) |
| 609 | + assert.Error(ts.T(), err, "session should be deleted") |
| 610 | + assert.Nil(ts.T(), deletedSession) |
| 611 | +} |
| 612 | + |
| 613 | +func (ts *OAuthClientTestSuite) TestUserRevokeOAuthGrantNotFound() { |
| 614 | + // Create test user |
| 615 | + user := ts. createTestUser( "[email protected]") |
| 616 | + |
| 617 | + // Create a client but don't create a consent |
| 618 | + client, _ := ts.createTestOAuthClient() |
| 619 | + |
| 620 | + // Create HTTP request with query parameter |
| 621 | + req := httptest.NewRequest(http.MethodDelete, "/user/oauth/grants?client_id="+client.ID.String(), nil) |
| 622 | + |
| 623 | + // Add user to context |
| 624 | + ctx := shared.WithUser(req.Context(), user) |
| 625 | + req = req.WithContext(ctx) |
| 626 | + |
| 627 | + w := httptest.NewRecorder() |
| 628 | + |
| 629 | + // Call handler - should return error |
| 630 | + err := ts.Server.UserRevokeOAuthGrant(w, req) |
| 631 | + require.Error(ts.T(), err) |
| 632 | + assert.Contains(ts.T(), err.Error(), "No active grant found") |
| 633 | +} |
| 634 | + |
| 635 | +func (ts *OAuthClientTestSuite) TestUserRevokeOAuthGrantInvalidClientID() { |
| 636 | + // Create test user |
| 637 | + user := ts. createTestUser( "[email protected]") |
| 638 | + |
| 639 | + // Create HTTP request with invalid client ID query parameter |
| 640 | + req := httptest.NewRequest(http.MethodDelete, "/user/oauth/grants?client_id=invalid-uuid", nil) |
| 641 | + |
| 642 | + // Add user to context |
| 643 | + ctx := shared.WithUser(req.Context(), user) |
| 644 | + req = req.WithContext(ctx) |
| 645 | + |
| 646 | + w := httptest.NewRecorder() |
| 647 | + |
| 648 | + // Call handler - should return error |
| 649 | + err := ts.Server.UserRevokeOAuthGrant(w, req) |
| 650 | + require.Error(ts.T(), err) |
| 651 | + assert.Contains(ts.T(), err.Error(), "invalid client_id format") |
| 652 | +} |
| 653 | + |
| 654 | +func (ts *OAuthClientTestSuite) TestUserRevokeOAuthGrantNoAuth() { |
| 655 | + // Test without user in context (unauthenticated) |
| 656 | + client, _ := ts.createTestOAuthClient() |
| 657 | + |
| 658 | + req := httptest.NewRequest(http.MethodDelete, "/user/oauth/grants?client_id="+client.ID.String(), nil) |
| 659 | + |
| 660 | + w := httptest.NewRecorder() |
| 661 | + |
| 662 | + err := ts.Server.UserRevokeOAuthGrant(w, req) |
| 663 | + require.Error(ts.T(), err) |
| 664 | + assert.Contains(ts.T(), err.Error(), "authentication required") |
| 665 | +} |
0 commit comments