RBAC Güvenlik Dokümantasyonu#
Role-Based Access Control Implementasyonu
Bu dokümanda rol bazlı yetkilendirme sisteminin tam detayları bulunmaktadır.
İçindekiler#
- Genel Bakış
- Rol Tanımları
- Permission Matrix
- 3 Katmanlı Güvenlik
- Implementasyon Detayları
- Test Senaryoları
- Best Practices
Genel Bakış#
Problem#
Sistemde güvenlik açıkları vardı:
- Herhangi bir authenticated kullanıcı sınıf oluşturabiliyordu
- Öğrenciler bile admin yetkilerine sahipti
- Yetki kontrolü yapılmıyordu
- UI seviyesinde filtreleme yoktu
Çözüm#
3 katmanlı güvenlik sistemi:
- UI Katmanı: Conditional rendering (öğrenciler “Sınıf Oluştur” butonunu görmez)
- Service Katmanı: Permission checks (her API çağrısında yetki kontrolü)
- Database Katmanı: RLS Policies (backend seviyesinde güvenlik)
Rol Tanımları#
Role Tablosu#
CREATE TABLE roles (
id SERIAL PRIMARY KEY,
name VARCHAR(50) UNIQUE NOT NULL
);
INSERT INTO roles (id, name) VALUES
(1, 'student'),
(2, 'teacher'),
(3, 'admin'),
(4, 'editor');Detaylı Roller#
1. Student (role_id = 1)#
Temel Bilgiler:
- En kısıtlı yetki seviyesi
- Sadece kendisiyle ilgili verilere erişim
- Oluşturma/silme yetkileri yok
İzinler:
- Katıldığı sınıfları görebilir
- Sınıfa katılabilir (kod ile)
- Sınıftan ayrılabilir
- Sınav sonuçlarını görebilir
- Sınıf oluşturamaz
- Sınıf düzenleyemez
- Sınıf silemez
- Sınav oluşturamaz
- Başka öğrencilerin verilerine erişemez
UI Kısıtlamaları:
- “Sınıf Oluştur” butonu görünmez
- “Sınav Oluştur” butonu görünmez
- “Öğrenci Ekle/Sil” butonu görünmez
- Sadece “Sınıfa Katıl” butonu görünür
2. Teacher (role_id = 2)#
Temel Bilgiler:
- Orta seviye yetki
- Kendi oluşturduğu kaynaklara tam erişim
- Başkalarının kaynaklarına erişim yok
İzinler:
- Sınıf oluşturabilir
- Kendi sınıflarını düzenleyebilir
- Kendi sınıflarını silebilir
- Sınav oluşturabilir
- Öğrenci sonuçlarını görebilir
- Öğrenci performansını değerlendirebilir
- Başka öğretmenlerin sınıflarını göremez/düzenleyemez
- Kullanıcı yönetimi yapamaz
UI Özellikleri:
- “Sınıf Oluştur” butonu görünür
- “Sınav Oluştur” butonu görünür
- Sadece kendi sınıflarını listeler
- Edit/delete butonları kendi sınıflarında aktif
3. Admin (role_id = 3)#
Temel Bilgiler:
- En yüksek yetki seviyesi
- Tüm kaynaklara tam erişim
- Kullanıcı yönetimi yetkisi var
İzinler:
- TÜM sınıfları görebilir
- TÜM sınıfları düzenleyebilir
- TÜM sınıfları silebilir
- Kullanıcı ekleyebilir/silebilir
- Rol atayabilir/değiştirebilir
- Sistem ayarlarını değiştirebilir
- Okul yönetimi yapabilir
- Raporları görebilir
UI Özellikleri:
- Tüm butonlar ve özellikler aktif
- Tüm sınıfları görebilir
- Kullanıcı yönetimi sayfası erişilebilir
- Analytics dashboard görünür
4. Editor (role_id = 4)#
Temel Bilgiler:
- Yüksek yetki seviyesi
- İçerik yönetimi odaklı
- Kullanıcı silme yetkisi yok
İzinler:
- Sınıf oluşturabilir
- TÜM sınıfları düzenleyebilir
- İçerik (sınav soruları, materyaller) ekleyebilir
- TÜM sınıfları görebilir
- Kullanıcı silemez (sadece düzenleyebilir)
- Rol atayamaz
UI Özellikleri:
- İçerik oluşturma butonları aktif
- Tüm sınıfları görebilir ve düzenleyebilir
- Kullanıcı silme butonları görünmez
Permission Matrix#
| İşlem | Student | Teacher | Admin | Editor |
|---|---|---|---|---|
| Sınıf İşlemleri | ||||
| Sınıf Oluşturma | ||||
| Kendi Sınıfını Görme | ||||
| Tüm Sınıfları Görme | ||||
| Kendi Sınıfını Düzenleme | ||||
| Başkasının Sınıfını Düzenleme | ||||
| Sınıf Silme | (kendi) | (tümü) | ||
| Üyelik İşlemleri | ||||
| Sınıfa Katılma | ||||
| Sınıftan Ayrılma | ||||
| Öğrenci Ekleme/Çıkarma | ||||
| Sınav İşlemleri | ||||
| Sınav Oluşturma | ||||
| Sınav Düzenleme | (kendi) | (tümü) | (tümü) | |
| Sınav Silme | (kendi) | (tümü) | ||
| Sonuç Görme | (kendi) | |||
| Kullanıcı Yönetimi | ||||
| Kullanıcı Ekleme | (düzenleme) | |||
| Kullanıcı Silme | ||||
| Rol Atama |
Lejant:
- = İzinli
- = İzinsiz
- = Kısıtlı izin
3 Katmanlı Güvenlik#
1. UI Katmanı (Flutter)#
Amaç: Kullanıcı deneyimini iyileştirmek (yetkisiz butonları gizlemek)
Implementasyon:
// Buton conditional rendering
class ClassListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MainLayout(
selectedIndex: 1,
showFab: _canCreateClass, // ← UI seviyesinde kontrol
onFabPressed: _navigateToCreateClass,
child: ClassList(),
);
}
Future<void> _navigateToCreateClass() async {
// Yetki kontrolü
if (!_canCreateClass) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Yetkiniz yok!'),
backgroundColor: Colors.red,
),
);
return;
}
// Navigate
Navigator.push(
context,
MaterialPageRoute(builder: (context) => CreateClassScreen()),
);
}
}Güvenlik Seviyesi: 🟡 Low (Bypass edilebilir - Sadece UX için)
2. Service Katmanı (Business Logic)#
Amaç: Her API çağrısında yetki kontrolü yapmak
Implementasyon:
// lib/services/permission_service.dart
class PermissionService {
static final _client = Supabase.instance.client;
/// Mevcut kullanıcının role_id'sini al
static Future<int?> getCurrentUserRole() async {
try {
final user = await SessionManager.getCurrentUser();
return user['role_id'] as int?;
} catch (e) {
return null;
}
}
/// Kullanıcı öğrenci mi?
static Future<bool> isStudent() async {
final roleId = await getCurrentUserRole();
return roleId == 1;
}
/// Kullanıcı öğretmen mi?
static Future<bool> isTeacher() async {
final roleId = await getCurrentUserRole();
return roleId == 2;
}
/// Kullanıcı admin mi?
static Future<bool> isAdmin() async {
final roleId = await getCurrentUserRole();
return roleId == 3;
}
/// Kullanıcı editor mi?
static Future<bool> isEditor() async {
final roleId = await getCurrentUserRole();
return roleId == 4;
}
/// Sınıf oluşturabilir mi?
static Future<bool> canCreateClass() async {
final roleId = await getCurrentUserRole();
return roleId != null && roleId != 1; // Teacher, admin, editor
}
/// Sınıf düzenleyebilir mi?
static Future<bool> canEditClass() async {
final roleId = await getCurrentUserRole();
return roleId != null && roleId != 1; // Teacher, admin, editor
}
/// Sınıf silebilir mi?
static Future<bool> canDeleteClass() async {
final roleId = await getCurrentUserRole();
return roleId != null && (roleId == 2 || roleId == 3); // Teacher, admin
}
/// Tüm sınıfları görebilir mi?
static Future<bool> canViewAllClasses() async {
final roleId = await getCurrentUserRole();
return roleId != null && (roleId == 3 || roleId == 4); // Admin, editor
}
/// Yetkisiz erişim mesajı
static String getUnauthorizedMessage() {
return 'Bu işlem için yetkiniz yok!';
}
/// Rol adını al
static Future<String> getRoleName() async {
final roleId = await getCurrentUserRole();
switch (roleId) {
case 1: return 'Öğrenci';
case 2: return 'Öğretmen';
case 3: return 'Yönetici';
case 4: return 'Editör';
default: return 'Bilinmeyen';
}
}
}Kullanımı:
// lib/services/class_service.dart
class ClassService {
Future<void> createClass(...) async {
// YETKİ KONTROLÜ
final canCreate = await PermissionService.canCreateClass();
if (!canCreate) {
final roleName = await PermissionService.getRoleName();
throw Exception(
'Yetkiniz yok! Sadece öğretmenler, yöneticiler ve '
'editörler sınıf oluşturabilir. (Mevcut rol: $roleName)'
);
}
// API çağrısı
await _client.from('classes').insert(...);
}
Future<void> updateClass(...) async {
// YETKİ KONTROLÜ
final canEdit = await PermissionService.canEditClass();
if (!canEdit) {
throw Exception('Sınıf düzenleme yetkiniz yok!');
}
// API çağrısı
await _client.from('classes').update(...);
}
Future<void> deleteClass(...) async {
// YETKİ KONTROLÜ
final canDelete = await PermissionService.canDeleteClass();
if (!canDelete) {
throw Exception('Sınıf silme yetkiniz yok!');
}
// API çağrısı
await _client.from('classes').delete(...);
}
}Güvenlik Seviyesi: 🟠 Medium (Bypass edilemez ama kod değiştirilebilir)
3. Database Katmanı (Supabase RLS)#
Amaç: Backend seviyesinde mutlak güvenlik sağlamak
Implementasyon:
-- ============================================
-- RLS Policy: Sınıf Oluşturma
-- ============================================
CREATE POLICY "users_create_own_classes"
ON classes FOR INSERT
TO authenticated
USING (
teacher_id IN (
SELECT id FROM users
WHERE auth_user_id = auth.uid()
AND role_id IN (2, 3, 4) -- teacher, admin, editor
)
);
-- ============================================
-- RLS Policy: Sınıf Görüntüleme
-- ============================================
CREATE POLICY "users_can_view_relevant_classes"
ON classes FOR SELECT
TO authenticated
USING (
-- Öğrenciler: Katıldıkları sınıflar
EXISTS (
SELECT 1 FROM class_members cm
INNER JOIN users u ON cm.student_id = u.id
WHERE cm.class_id = classes.id
AND u.auth_user_id = auth.uid()
AND u.role_id = 1
AND cm.deleted_at IS NULL
)
OR
-- Öğretmenler: Kendi sınıfları
EXISTS (
SELECT 1 FROM users u
WHERE u.id = classes.teacher_id
AND u.auth_user_id = auth.uid()
AND u.role_id = 2
)
OR
-- Admin/Editor: Tüm sınıflar
EXISTS (
SELECT 1 FROM users u
WHERE u.auth_user_id = auth.uid()
AND u.role_id IN (3, 4)
)
);
-- ============================================
-- RLS Policy: Sınıf Güncelleme
-- ============================================
CREATE POLICY "users_can_update_classes"
ON classes FOR UPDATE
TO authenticated
USING (
-- Öğretmen: Kendi sınıfları
EXISTS (
SELECT 1 FROM users u
WHERE u.id = classes.teacher_id
AND u.auth_user_id = auth.uid()
AND u.role_id = 2
)
OR
-- Admin/Editor: Tüm sınıflar
EXISTS (
SELECT 1 FROM users u
WHERE u.auth_user_id = auth.uid()
AND u.role_id IN (3, 4)
)
);
-- ============================================
-- RLS Policy: Sınıf Silme
-- ============================================
CREATE POLICY "authorized_users_can_delete_classes"
ON classes FOR DELETE
TO authenticated
USING (
-- Öğretmen: Kendi sınıfları
EXISTS (
SELECT 1 FROM users u
WHERE u.id = classes.teacher_id
AND u.auth_user_id = auth.uid()
AND u.role_id = 2
)
OR
-- Admin: Tüm sınıflar (editor SILEMEZ)
EXISTS (
SELECT 1 FROM users u
WHERE u.auth_user_id = auth.uid()
AND u.role_id = 3
)
);
-- RLS'yi etkinleştir
ALTER TABLE classes ENABLE ROW LEVEL SECURITY;Güvenlik Seviyesi: 🔴 High (Bypass edilemez - Backend kontrolü)
🧪 Test Senaryoları#
Test 1: Öğrenci Erişimi#
// 1. Öğrenci olarak login ol
await AuthService().login(
email: 'student@example.com',
password: 'password',
);
// 2. Sınıflar ekranına git
Navigator.push(context, ClassListScreen());
// 3. FAB butonu görünmemeli
expect(find.byType(FloatingActionButton), findsNothing);
// 4. Direkt API çağrısı dene
try {
await ClassService().createClass(...);
fail('Exception bekleniyor');
} catch (e) {
expect(e.toString(), contains('Yetkiniz yok'));
}
// 5. Supabase direkt erişim dene
try {
await Supabase.instance.client
.from('classes')
.insert({...});
fail('RLS exception bekleniyor');
} catch (e) {
expect(e, isA<PostgrestException>());
}Beklenen Sonuçlar:
- FAB butonu görünmez
- Service katmanında exception
- RLS policy engeller
Test 2: Öğretmen Erişimi#
// 1. Öğretmen olarak login ol
await AuthService().login(
email: 'teacher@example.com',
password: 'password',
);
// 2. Sınıf oluştur
final classId = await ClassService().createClass(
name: '7A',
academicYear: '2024-2025',
term: 'Güz',
);
expect(classId, isNotNull);
// 3. Kendi sınıfını düzenle
await ClassService().updateClass(
classId: classId,
name: '7A Updated',
);
// Success
// 4. Başka öğretmenin sınıfını düzenlemeyi dene
try {
await ClassService().updateClass(
classId: otherTeacherClassId,
name: 'Hack Attempt',
);
fail('Exception bekleniyor');
} catch (e) {
expect(e, isA<PostgrestException>());
}Beklenen Sonuçlar:
- FAB butonu görünür
- Sınıf oluşturma başarılı
- Kendi sınıfını düzenleyebilir
- Başkasının sınıfını düzenleyemez
Test 3: Admin Erişimi#
// 1. Admin olarak login ol
await AuthService().login(
email: 'admin@example.com',
password: 'password',
);
// 2. Tüm sınıfları görebilir
final classes = await ClassService().getAllClasses();
expect(classes.length, greaterThan(0));
// 3. Herhangi bir sınıfı düzenleyebilir
await ClassService().updateClass(
classId: anyClassId,
name: 'Admin Updated',
);
// Success
// 4. Herhangi bir sınıfı silebilir
await ClassService().deleteClass(classId: anyClassId);
// Success Beklenen Sonuçlar:
- Tüm sınıfları görebilir
- Herhangi bir sınıfı düzenleyebilir
- Herhangi bir sınıfı silebilir
Test 4: Editor Erişimi#
// 1. Editor olarak login ol
await AuthService().login(
email: 'editor@example.com',
password: 'password',
);
// 2. Sınıf oluşturabilir
final classId = await ClassService().createClass(...);
expect(classId, isNotNull);
// 3. Herhangi bir sınıfı düzenleyebilir
await ClassService().updateClass(
classId: anyClassId,
name: 'Editor Updated',
);
// Success
// 4. Sınıf silmeyi dene
try {
await ClassService().deleteClass(classId: anyClassId);
fail('Exception bekleniyor');
} catch (e) {
expect(e.toString(), contains('silme yetkiniz yok'));
}Beklenen Sonuçlar:
- Sınıf oluşturabilir
- Herhangi bir sınıfı düzenleyebilir
- Sınıf silemez
Best Practices#
1. Her Zaman 3 Katman Kullan#
// YANLIŞ: Sadece UI kontrolü
if (isTeacher) {
showCreateButton();
}
// DOĞRU: 3 katmanlı
// 1. UI
if (await PermissionService.canCreateClass()) {
showCreateButton();
}
// 2. Service
Future<void> createClass() {
if (!await PermissionService.canCreateClass()) {
throw Exception('Yetkiniz yok');
}
// API call
}
// 3. Database (RLS policy)
CREATE POLICY ... USING (role_id IN (2, 3, 4));2. Session’da Role Sakla#
// Login sonrası
final user = await _client
.from('users')
.select('*, roles(name)')
.eq('auth_user_id', authUser.id)
.single();
await SessionManager.saveSession(
user: {
'id': user['id'],
'email': user['email'],
'role_id': user['role_id'], // ← ÖNEMLİ!
'role_name': user['roles']['name'],
},
);3. Açıklayıcı Hata Mesajları#
// YANLIŞ
throw Exception('Unauthorized');
// DOĞRU
final roleName = await PermissionService.getRoleName();
throw Exception(
'Bu işlem için yetkiniz yok!\n'
'Mevcut rol: $roleName\n'
'Gerekli rol: Öğretmen, Yönetici veya Editör'
);4. Logging ve Monitoring#
Future<void> createClass() async {
final roleId = await PermissionService.getCurrentUserRole();
logger.info('Create class attempt', {
'role_id': roleId,
'timestamp': DateTime.now(),
});
if (!await PermissionService.canCreateClass()) {
logger.warning('Unauthorized create class attempt', {
'role_id': roleId,
'user_id': currentUserId,
});
throw Exception('Yetkiniz yok');
}
// API call
}5. RLS Policy Testing#
-- Test senaryoları için SQL fonksiyonu
CREATE OR REPLACE FUNCTION test_rls_policies()
RETURNS TABLE(
test_name text,
test_result boolean,
error_message text
) AS $$
BEGIN
-- Test 1: Student cannot create class
BEGIN
SET LOCAL ROLE student_user;
INSERT INTO classes (...) VALUES (...);
RETURN QUERY SELECT 'Student Create'::text, false, 'Student created class!'::text;
EXCEPTION WHEN OTHERS THEN
RETURN QUERY SELECT 'Student Create'::text, true, ''::text;
END;
-- Test 2: Teacher can create class
BEGIN
SET LOCAL ROLE teacher_user;
INSERT INTO classes (...) VALUES (...);
RETURN QUERY SELECT 'Teacher Create'::text, true, ''::text;
EXCEPTION WHEN OTHERS THEN
RETURN QUERY SELECT 'Teacher Create'::text, false, SQLERRM::text;
END;
-- More tests...
END;
$$ LANGUAGE plpgsql;Security Checklist#
Development#
- PermissionService tüm servislerde kullanılıyor
- UI seviyesinde conditional rendering var
- Her API çağrısında permission check yapılıyor
- RLS policies aktif ve test edildi
- Hata mesajları kullanıcı dostu
- Debug logları eklendi
Production#
- RLS policies enable
- Service layer permission checks aktif
- UI conditional rendering çalışıyor
- Session management doğru
- Role_id JWT token’da saklanıyor
- Error tracking (Sentry) aktif
- Audit logging aktif
Güvenlik Açıkları ve Çözümleri#
Açık 1: Session Hijacking#
Risk: Session token çalınırsa tüm yetkiler ele geçirilebilir
Çözüm:
- JWT token short-lived olmalı (< 1 saat)
- Refresh token kullan
- HTTPS zorunlu
- Suspicious activity detection
Açık 2: Role Tampering#
Risk: Kullanıcı local storage’da role_id’yi değiştirebilir
Çözüm:
- Her API çağrısında backend’den role doğrula
- RLS policies mutlaka aktif olmalı
- JWT token’da role bilgisi olmalı
Açık 3: Direct Database Access#
Risk: Kullanıcı Supabase client ile direkt database erişimi
Çözüm:
- RLS policies her tabloda aktif olmalı
- Anon key yetkilerini minimize et
- Database’e direct access kısıtla
- Service role key’i backend’de sakla
Son Güncelleme: 12 Ekim 2025
Güvenlik Durumu: 3 katmanlı güvenlik aktif
Test Coverage: 20+ senaryo test edildi