You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
docuseal/docs/qa/security-test-plan.md

541 lines
14 KiB

# FloDoc Security Test Plan (Phase 4)
## Overview
This document outlines the comprehensive security testing requirements for the FloDoc Institution Management system, following Winston's IV4 integration verification.
## Test Categories
### 1. Model Layer Security Tests
#### 1.1 Data Isolation Tests
```ruby
# spec/models/institution_spec.rb
describe 'Data Isolation' do
it 'Institution.for_user returns only accessible institutions' do
user1 = create(:user)
user2 = create(:user)
inst1 = create(:institution, account: user1.account)
inst2 = create(:institution, account: user2.account)
expect(Institution.for_user(user1)).to include(inst1)
expect(Institution.for_user(user1)).not_to include(inst2)
end
it 'User.can_access_institution? works correctly' do
user = create(:user)
inst = create(:institution, account: user.account)
# Create access
create(:account_access, user: user, institution: inst)
expect(user.can_access_institution?(inst)).to be true
end
end
```
#### 1.2 Token Security Tests
```ruby
# spec/models/cohort_admin_invitation_spec.rb
describe 'Token Security' do
it 'generates 512-bit secure tokens' do
invitation = build(:cohort_admin_invitation)
token = invitation.generate_token
expect(token.length).to be >= 64
expect(invitation.hashed_token).not_to eq(token)
expect(invitation.token_preview).to match(/\A.{8}\.\.\.\z/)
end
it 'validates token with Redis single-use enforcement' do
invitation = create(:cohort_admin_invitation)
token = invitation.generate_token
invitation.save!
# First validation should succeed
expect(invitation.valid_token?(token)).to be true
# Second validation should fail
expect(invitation.valid_token?(token)).to be false
end
it 'rejects expired tokens' do
invitation = create(:cohort_admin_invitation, expires_at: 1.hour.ago)
token = invitation.generate_token
expect(invitation.valid_token?(token)).to be false
end
it 'rejects wrong email' do
invitation = create(:cohort_admin_invitation, email: 'correct@example.com')
token = invitation.generate_token
# Simulate wrong email by creating new user with different email
wrong_user = create(:user, email: 'wrong@example.com')
expect(invitation.valid_token?(token)).to be true
# But acceptance should fail
result = InvitationService.accept_invitation(token, wrong_user)
expect(result).to be_nil
end
end
```
#### 1.3 Rate Limiting Tests
```ruby
# spec/services/invitation_service_spec.rb
describe 'Rate Limiting' do
it 'prevents more than 5 invitations per email' do
institution = create(:institution)
user = create(:user)
email = 'test@example.com'
# Create 5 valid invitations
5.times do
create(:cohort_admin_invitation,
institution: institution,
email: email,
used_at: nil,
expires_at: 24.hours.from_now)
end
expect {
InvitationService.create_invitation(institution, email, 'cohort_admin', user)
}.to raise_error(RateLimit::LimitApproached)
end
it 'allows new invitations after expiration' do
institution = create(:institution)
user = create(:user)
email = 'test@example.com'
# Create 5 expired invitations
5.times do
create(:cohort_admin_invitation,
institution: institution,
email: email,
used_at: nil,
expires_at: 1.hour.ago)
end
expect {
InvitationService.create_invitation(institution, email, 'cohort_admin', user)
}.not_to raise_error
end
end
```
### 2. Request/Controller Security Tests
#### 2.1 Cross-Institution Access Tests
```ruby
# spec/requests/api/v1/institutions_spec.rb
describe 'Cross-Institution Access Prevention' do
let(:user1) { create(:user) }
let(:user2) { create(:user) }
let(:inst1) { create(:institution, account: user1.account) }
let(:inst2) { create(:institution, account: user2.account) }
it 'prevents access to other institutions' do
sign_in user1
# Try to access user2's institution
get "/api/v1/institutions/#{inst2.id}"
expect(response).to have_http_status(:forbidden)
expect(SecurityEvent.where(event_type: 'unauthorized_institution_access').count).to eq(1)
end
it 'prevents updates to other institutions' do
sign_in user1
patch "/api/v1/institutions/#{inst2.id}", params: { institution: { name: 'Hacked' } }
expect(response).to have_http_status(:forbidden)
expect(inst2.reload.name).not_to eq('Hacked')
end
end
```
#### 2.2 Role-Based Access Tests
```ruby
# spec/requests/api/v1/admin/invitations_spec.rb
describe 'Role-Based Authorization' do
let(:institution) { create(:institution) }
let(:super_admin) { create(:user) }
let(:admin) { create(:user) }
before do
create(:account_access, user: super_admin, institution: institution, role: 'cohort_super_admin')
create(:account_access, user: admin, institution: institution, role: 'cohort_admin')
end
it 'allows super admin to create invitations' do
sign_in super_admin
post '/api/v1/admin/invitations', params: {
institution_id: institution.id,
email: 'newadmin@example.com',
role: 'cohort_admin'
}
expect(response).to have_http_status(:created)
end
it 'prevents regular admin from creating invitations' do
sign_in admin
post '/api/v1/admin/invitations', params: {
institution_id: institution.id,
email: 'newadmin@example.com',
role: 'cohort_admin'
}
expect(response).to have_http_status(:forbidden)
end
end
```
### 3. Token Security Scenarios
#### 3.1 Concurrent Token Validation
```ruby
# spec/requests/api/v1/admin/invitation_acceptance_spec.rb
describe 'Concurrent Token Validation' do
it 'handles race conditions correctly' do
invitation = create(:cohort_admin_invitation)
token = invitation.generate_token
invitation.save!
user = create(:user, email: invitation.email)
# Simulate 50 concurrent requests
results = 50.times.map do
Thread.new do
InvitationService.accept_invitation(token, user)
end
end
# Only one should succeed
successful = results.map(&:value).compact
expect(successful.length).to eq(1)
# Verify token is marked as used
expect(invitation.reload.used_at).not_to be_nil
end
end
```
#### 3.2 Token Reuse Prevention
```ruby
it 'prevents token reuse across different users' do
invitation = create(:cohort_admin_invitation)
token = invitation.generate_token
invitation.save!
user1 = create(:user, email: invitation.email)
user2 = create(:user, email: invitation.email)
# First user accepts
result1 = InvitationService.accept_invitation(token, user1)
expect(result1).not_to be_nil
# Second user tries to use same token
result2 = InvitationService.accept_invitation(token, user2)
expect(result2).to be_nil
end
```
### 4. Security Event Logging Tests
#### 4.1 Event Capture Tests
```ruby
# spec/models/security_event_spec.rb
describe 'Security Event Logging' do
it 'logs all 6 event types' do
user = create(:user)
events = [
:unauthorized_institution_access,
:insufficient_privileges,
:token_validation_failure,
:rate_limit_exceeded,
:invitation_accepted,
:super_admin_demoted
]
events.each do |event_type|
SecurityEvent.log(event_type, user, { test: true })
end
expect(SecurityEvent.count).to eq(6)
end
it 'captures IP address and details' do
event = SecurityEvent.log(:unauthorized_institution_access, nil, {
ip_address: '192.168.1.100',
institution_id: 123,
attempted_action: 'show'
})
expect(event.ip_address).to eq('192.168.1.100')
expect(event.details['institution_id']).to eq(123)
expect(event.details['attempted_action']).to eq('show')
end
end
```
#### 4.2 Alert Threshold Tests
```ruby
describe 'Alert Thresholds' do
it 'detects unauthorized access threshold' do
user = create(:user)
# Create 5 unauthorized access attempts
5.times do
SecurityEvent.log(:unauthorized_institution_access, user, {
institution_id: 999
})
end
expect(SecurityEvent.alert_threshold_exceeded?(
'unauthorized_institution_access',
threshold: 5,
time_window: 1.hour
)).to be true
end
it 'detects token failure threshold' do
user = create(:user)
# Create 20 token failures
20.times do
SecurityEvent.log(:token_validation_failure, user, {
reason: 'invalid_token'
})
end
expect(SecurityEvent.alert_threshold_exceeded?(
'token_validation_failure',
threshold: 20,
time_window: 1.hour
)).to be true
end
end
```
### 5. Performance Tests
#### 5.1 Query Performance
```ruby
# spec/performance/institution_query_spec.rb
describe 'Institution Query Performance' do
it 'performs scoped queries efficiently' do
# Setup: 1000 institutions, 100 users
account = create(:account)
1000.times { create(:institution, account: account) }
users = create_list(:user, 100, account: account)
# Benchmark
expect {
Institution.for_user(users.first).limit(10).to_a
}.to perform_under(50).ms
end
it 'handles concurrent user loads' do
# 100 concurrent users accessing different institutions
users = create_list(:user, 100)
institutions = create_list(:institution, 100)
threads = users.zip(institutions).map do |user, institution|
Thread.new do
create(:account_access, user: user, institution: institution)
institution.accessible_by?(user)
end
end
threads.each(&:join)
expect(AccountAccess.count).to eq(100)
end
end
```
#### 5.2 Redis Performance
```ruby
describe 'Redis Token Enforcement' do
it 'handles high concurrent token validation' do
invitation = create(:cohort_admin_invitation)
token = invitation.generate_token
invitation.save!
# 50 concurrent validation attempts
results = 50.times.map do
Thread.new do
invitation.valid_token?(token)
end
end
# Only first should succeed
valid_count = results.map(&:value).count(true)
expect(valid_count).to eq(1)
end
end
```
### 6. Integration Tests
#### 6.1 Complete Invitation Flow
```ruby
# spec/system/invitation_flow_spec.rb
describe 'Complete Invitation Flow' do
it 'handles full invitation acceptance workflow' do
# 1. Super admin creates invitation
super_admin = create(:user)
institution = create(:institution)
create(:account_access, user: super_admin, institution: institution, role: 'cohort_super_admin')
sign_in super_admin
post '/api/v1/admin/invitations', params: {
institution_id: institution.id,
email: 'newadmin@example.com',
role: 'cohort_admin'
}
invitation = CohortAdminInvitation.last
token = invitation.generate_token # In real flow, this is in Redis
# 2. New user accepts invitation
new_user = create(:user, email: 'newadmin@example.com')
result = InvitationService.accept_invitation(token, new_user)
expect(result).not_to be_nil
# 3. Verify access granted
expect(new_user.cohort_admin?).to be true
expect(new_user.institutions).to include(institution)
# 4. Verify security events logged
expect(SecurityEvent.where(event_type: 'invitation_created').count).to eq(1)
expect(SecurityEvent.where(event_type: 'invitation_accepted').count).to eq(1)
end
end
```
#### 6.2 Migration Rollback Test
```ruby
# spec/migrations/rollback_spec.rb
describe 'Migration Rollback' do
it 'rolls back without data loss' do
# Setup data
institution = create(:institution)
user = create(:user)
invitation = create(:cohort_admin_invitation, institution: institution)
# Run rollback
`bin/rails db:rollback STEP=6`
# Verify data integrity
expect(User.exists?(user.id)).to be true
expect(Account.exists?(institution.account_id)).to be true
# Verify original functionality still works
expect(Template.count).to eq(0) # No templates created yet
end
end
```
### 7. Penetration Testing Scenarios
#### 7.1 SQL Injection Attempts
```ruby
describe 'SQL Injection Prevention' do
it 'prevents SQL injection in scoped queries' do
user = create(:user)
# Attempt SQL injection
malicious_id = "1; DROP TABLE users; --"
expect {
Institution.for_user(user).find_by(id: malicious_id)
}.not_to raise_error
# Verify users table still exists
expect(User.count).to be >= 1
end
end
```
#### 7.2 Token Brute Force
```ruby
describe 'Token Brute Force Protection' do
it 'rate limits token validation attempts' do
invitation = create(:cohort_admin_invitation)
wrong_tokens = Array.new(100) { SecureRandom.hex(32) }
# Simulate brute force
results = wrong_tokens.map do |token|
invitation.valid_token?(token)
end
# All should fail
expect(results.all?(false)).to be true
# Check security events logged
expect(SecurityEvent.where(event_type: 'token_validation_failure').count).to eq(100)
end
end
```
## Test Execution Plan
### Prerequisites
1. Install Ruby 3.4.2
2. Run `bundle install`
3. Start Redis: `sudo systemctl start redis-server`
4. Start PostgreSQL: `sudo systemctl start postgresql`
5. Run migrations: `bin/rails db:migrate`
### Test Execution Commands
```bash
# Run all security tests
bundle exec rspec spec/models/security_event_spec.rb
bundle exec rspec spec/models/cohort_admin_invitation_spec.rb
bundle exec rspec spec/services/invitation_service_spec.rb
bundle exec rspec spec/requests/api/v1/admin/security_events_spec.rb
# Run performance tests
bundle exec rspec spec/performance/
# Run integration tests
bundle exec rspec spec/system/
# Run penetration tests
bundle exec rspec spec/requests/api/v1/institutions_spec.rb
bundle exec rspec spec/requests/api/v1/admin/invitation_acceptance_spec.rb
# Run all tests with coverage
bundle exec rspec --format documentation --color
```
### Success Criteria
**All tests must pass with:**
- 80% minimum code coverage
- Zero security event violations in production
- Performance within 10% of baseline
- All 6 security event types logged correctly
- Token system handles 50+ concurrent requests
- Rollback procedure verified
### Test Results Documentation
Create `docs/qa/security-test-results-YYYYMMDD.md` with:
- Test execution summary
- Performance metrics
- Security event counts
- Penetration test findings
- Remediation recommendations
---
**Status:** 📋 **READY FOR EXECUTION** (pending Ruby environment setup)