mirror of https://github.com/docusealco/docuseal
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.
14 KiB
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
# 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
# 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
# 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
# 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
# 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
# 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
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
# 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
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
# 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
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
# 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
# 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
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
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
- Install Ruby 3.4.2
- Run
bundle install - Start Redis:
sudo systemctl start redis-server - Start PostgreSQL:
sudo systemctl start postgresql - Run migrations:
bin/rails db:migrate
Test Execution Commands
# 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)