<!-- Provide a brief summary of your changes --> ## Motivation and Context <!-- Why is this change needed? What problem does it solve? --> We keep bumping `go 1.26.x` across the repo every time a new Go patch drops ## How Has This Been Tested? <!-- Have you tested this in a real application? Which scenarios were tested? --> - **`go.mod` / `deploy/go.mod`**: `go 1.25.8` → `go 1.26` (minor only — "this module needs Go 1.26") - **All CI workflows**: `go-version-file: 'go.mod'` → `go-version: 'stable'` — CI always uses the latest stable Go, no more bump PRs - **govulncheck**: removed hardcoded `go-version-input: '1.26.1'` → `go-version-input: 'stable'` ## Breaking Changes <!-- Will users need to update their code or configurations? --> ## Types of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist <!-- Go over all the following points, and put an `x` in all the boxes that apply. --> - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context <!-- Add any other context, implementation notes, or design decisions --> Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
107 lines
3.2 KiB
Go
107 lines
3.2 KiB
Go
package auth_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/modelcontextprotocol/registry/internal/api/handlers/v0/auth"
|
|
"github.com/modelcontextprotocol/registry/internal/config"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// MockGenericOIDCValidator for testing
|
|
type MockGenericOIDCValidator struct {
|
|
validateFunc func(ctx context.Context, token string) (*auth.OIDCClaims, error)
|
|
}
|
|
|
|
func (m *MockGenericOIDCValidator) ValidateToken(ctx context.Context, token string) (*auth.OIDCClaims, error) {
|
|
if m.validateFunc != nil {
|
|
return m.validateFunc(ctx, token)
|
|
}
|
|
return nil, fmt.Errorf("not implemented")
|
|
}
|
|
|
|
func TestOIDCHandler_ExchangeToken(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config *config.Config
|
|
mockValidator *MockGenericOIDCValidator
|
|
token string
|
|
expectedError bool
|
|
}{
|
|
{ //nolint:gosec // G101: test data, not real credentials
|
|
name: "successful token exchange with publish permissions",
|
|
config: &config.Config{
|
|
OIDCEnabled: true,
|
|
OIDCIssuer: "https://accounts.google.com",
|
|
OIDCClientID: "test-client-id",
|
|
OIDCExtraClaims: `[{"hd":"modelcontextprotocol.io"}]`,
|
|
OIDCPublishPerms: "*",
|
|
JWTPrivateKey: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", // 32 byte hex
|
|
},
|
|
mockValidator: &MockGenericOIDCValidator{
|
|
validateFunc: func(_ context.Context, _ string) (*auth.OIDCClaims, error) {
|
|
return &auth.OIDCClaims{
|
|
ExtraClaims: map[string]any{
|
|
"email": "admin@modelcontextprotocol.io",
|
|
"email_verified": true,
|
|
"hd": "modelcontextprotocol.io",
|
|
"preferred_username": "admin",
|
|
},
|
|
}, nil
|
|
},
|
|
},
|
|
token: "valid-oidc-token",
|
|
expectedError: false,
|
|
},
|
|
{
|
|
name: "failed validation with invalid hosted domain",
|
|
config: &config.Config{
|
|
OIDCEnabled: true,
|
|
OIDCIssuer: "https://accounts.google.com",
|
|
OIDCClientID: "test-client-id",
|
|
OIDCExtraClaims: `[{"hd":"modelcontextprotocol.io"}]`,
|
|
OIDCPublishPerms: "*",
|
|
JWTPrivateKey: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
|
|
},
|
|
mockValidator: &MockGenericOIDCValidator{
|
|
validateFunc: func(_ context.Context, _ string) (*auth.OIDCClaims, error) {
|
|
return &auth.OIDCClaims{
|
|
ExtraClaims: map[string]any{
|
|
"email": "user@example.com",
|
|
"email_verified": true,
|
|
"hd": "example.com", // Wrong domain
|
|
"preferred_username": "user",
|
|
},
|
|
}, nil
|
|
},
|
|
},
|
|
token: "invalid-domain-token",
|
|
expectedError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
handler := auth.NewOIDCHandler(tt.config)
|
|
if tt.mockValidator != nil {
|
|
handler.SetValidator(tt.mockValidator)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
response, err := handler.ExchangeToken(ctx, tt.token)
|
|
|
|
if tt.expectedError {
|
|
assert.Error(t, err)
|
|
assert.Nil(t, response)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, response)
|
|
assert.NotEmpty(t, response.RegistryToken)
|
|
assert.Greater(t, response.ExpiresAt, 0)
|
|
}
|
|
})
|
|
}
|
|
}
|