RBAC Güvenlik Dokümantasyonu#

Role-Based Access Control Implementasyonu
Bu dokümanda rol bazlı yetkilendirme sisteminin tam detayları bulunmaktadır.


İçindekiler#

  1. Genel Bakış
  2. Rol Tanımları
  3. Permission Matrix
  4. 3 Katmanlı Güvenlik
  5. Implementasyon Detayları
  6. Test Senaryoları
  7. 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:

  1. UI Katmanı: Conditional rendering (öğrenciler “Sınıf Oluştur” butonunu görmez)
  2. Service Katmanı: Permission checks (her API çağrısında yetki kontrolü)
  3. 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#

İşlemStudentTeacherAdminEditor
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