Yang dimaksud dengan, “Seorang pengguna tidak boleh memutuskan apakah itu seorang Admin atau bukan. Hak Istimewa atau sistem Keamanan seharusnya. "

55

Contoh yang digunakan dalam pertanyaan memberikan data minimum ke suatu fungsi menyentuh cara terbaik untuk menentukan apakah pengguna adalah administrator atau tidak. Satu jawaban yang umum adalah:

user.isAdmin()

Ini mendorong komentar yang diulang beberapa kali dan banyak dipilih:

Seorang pengguna tidak boleh memutuskan apakah itu seorang Admin atau bukan. Hak Istimewa atau sistem Keamanan harus. Sesuatu yang tergabung erat dengan kelas tidak berarti itu adalah ide yang baik untuk menjadikannya bagian dari kelas itu.

Saya membalas,

Pengguna tidak memutuskan apa pun. Objek / tabel Pengguna menyimpan data tentang setiap pengguna. Pengguna yang sebenarnya tidak bisa mengubah segalanya tentang diri mereka sendiri.

Tetapi ini tidak produktif. Jelas ada perbedaan mendasar dari perspektif yang membuat komunikasi menjadi sulit. Dapatkah seseorang menjelaskan kepada saya mengapa user.isAdmin () buruk, dan melukis sketsa singkat tentang apa yang tampak seperti dilakukan "benar"?

Sungguh, saya gagal melihat keuntungan memisahkan keamanan dari sistem yang dilindungi. Teks keamanan apa pun akan mengatakan bahwa keamanan perlu dirancang ke dalam sistem sejak awal dan dipertimbangkan pada setiap tahap pengembangan, penerapan, pemeliharaan, dan bahkan akhir masa pakainya. Itu bukan sesuatu yang bisa dibaut ke samping. Namun sejauh ini 17 suara di komentar ini mengatakan bahwa saya kehilangan sesuatu yang penting.

GlenPeterson
sumber
8
Saya tidak berpikir itu buruk, terutama jika itu hanya bendera di tabel db pengguna. Jika ada perubahan di masa depan, ubahlah di masa depan. Anda juga dapat memanggil sistem keamanan dari objek pengguna. Saya pernah membaca tentang pola desain yang harus diakses setiap db / sumber daya melalui objek pengguna saat ini, untuk memastikan Anda tidak pernah mencoba melakukan sesuatu yang tidak diperbolehkan. Mungkin sedikit berlebihan, tetapi pengguna bisa menyediakan objek "hak istimewa" yang mencakup semua hal yang berhubungan dengan keamanan.
Cephalopod
Saya pikir ada kesalahpahaman. Bagi Anda, "Pengguna" berarti orang yang menggunakan perangkat lunak. Bagi mereka, "Pengguna" berarti kode yang menggunakan fungsionalitas Anda.
Philipp
2
Bukan Anda yang bertanya, tetapi hal yang paling saya harus berhati-hati dalam kasus ini adalah metode IsAdmin () sama sekali, di mana pun Anda meletakkannya. Saya biasanya menyarankan agar "Admin" tidak dikodekan dengan keras, tetapi hanya memiliki satu set izin yang mungkin dan "Admin" kebetulan memiliki seluruh rangkaian. Dengan begitu ketika Anda perlu membagi peran Admin nanti, banyak hal menjadi lebih mudah. Dan itu mengurangi logika kasus khusus.
psr
1
@ Pilip Saya tidak berpikir begitu ... Kedua situs secara eksplisit berbicara tentang Userobjek dengan isAdmin()metode (apa pun yang dilakukannya di balik layar)
Izkata
Terlambat untuk mengedit; Maksud saya "Kedua belah pihak", bukan "Kedua situs" ... = P
Izkata

Jawaban:

12

Pikirkan tentang Pengguna sebagai kunci, dan izin sebagai nilai.

Konteks yang berbeda mungkin memiliki izin yang berbeda untuk setiap pengguna. Karena izin relevan dengan konteks, Anda harus mengubah pengguna untuk setiap konteks. Jadi lebih baik Anda meletakkan logika itu di tempat lain (misalnya kelas konteks), dan lihat saja izin yang diperlukan.

Efek sampingnya adalah itu mungkin membuat sistem Anda lebih aman, karena Anda tidak bisa lupa untuk menghapus flag admin untuk tempat-tempat yang tidak relevan.

Wilbert
sumber
40

Komentar itu sangat buruk.

Intinya bukan apakah objek pengguna "memutuskan" apakah itu admin, pengguna uji coba, pengguna super atau apa pun di dalam atau di luar kebiasaan. Jelas itu tidak memutuskan hal-hal itu, sistem perangkat lunak pada umumnya, seperti yang diterapkan oleh Anda, tidak. Intinya adalah apakah "pengguna" kelas harus mewakili peran kepala sekolah bahwa benda-benda yang mewakili, atau apakah ini merupakan tanggung jawab dari kelas lain.

Kilian Foth
sumber
6
Aduh (itu komentar saya), tetapi Anda mengatakannya jauh lebih baik daripada saya.
Marjan Venema
Anda mengatakan untuk memodelkan peran dalam kelas Peran yang terpisah, bukan pada kelas Pengguna? Anda tidak merekomendasikan sistem terpisah seperti LDAP? Bagaimana jika setiap keputusan tunggal yang dibuat oleh kelas Peran memerlukan permintaan catatan pengguna yang sesuai dan tidak memerlukan informasi lain? Haruskah masih terpisah dari Pengguna dan jika demikian, mengapa?
GlenPeterson
2
@ GlenPeterson: tidak harus, hanya kelas-kelas <> tabel dan biasanya ada lebih banyak kelas untuk dilihat daripada tabel. Berorientasi pada data memang mengarah pada pendefinisian sublists pada kelas, ketika berkali-kali daftar ini harus didefinisikan pada "buku besar" lain (pesanan) atau beberapa kelas yang melewati pengguna (atau urutan) yang digunakan untuk mengambil ... Ini lebih merupakan masalah tanggung jawab daripada kata kerja dan kata benda.
Marjan Venema
9
Pikirkan tentang Pengguna sebagai kunci, dan izin sebagai nilai. Konteks yang berbeda mungkin memiliki izin yang berbeda untuk setiap pengguna. Karena izin relevan dengan konteks, Anda harus mengubah pengguna untuk setiap konteks. Jadi lebih baik Anda meletakkan logika itu di tempat lain (misalnya kelas konteks).
Wilbert
2
@ Willbert: Konteks! Itu sangat mencerahkan! Ya, jika Anda memiliki lebih dari satu konteks untuk izin tersebut maka semuanya masuk akal bagi saya. Biasanya saya melihat izin ini dalam satu konteks, dan menempelkannya pada pengguna adalah solusi paling sederhana dalam kasus itu. Jelas mereka tidak pantas berada di sana jika mereka menerapkan secara berbeda dalam konteks kedua. Jika Anda menuliskannya sebagai jawaban, saya cenderung menerimanya. Terima kasih.
GlenPeterson
30

Saya tidak akan memilih untuk mengucapkan komentar asli seperti yang dikatakan, tetapi mengidentifikasi masalah yang berpotensi sah.

Secara khusus, masalah yang memerlukan pemisahan adalah otentikasi vs otorisasi .

Otentikasi mengacu pada proses masuk dan mendapatkan identitas. Begitulah sistem mengetahui siapa Anda , dan digunakan untuk hal-hal seperti personalisasi, kepemilikan objek, dll.

Otorisasi mengacu pada apa yang Anda boleh lakukan , dan ini (umumnya) tidak ditentukan oleh siapa Anda . Sebaliknya, itu ditentukan oleh beberapa kebijakan keamanan seperti peran atau izin, yang tidak peduli tentang hal-hal seperti nama atau alamat email Anda.

Keduanya dapat berubah secara orthogonal satu sama lain. Misalnya, Anda dapat mengubah model otentikasi dengan menambahkan penyedia OpenID / OpenAuth. Dan Anda dapat mengubah kebijakan keamanan dengan menambahkan peran baru, atau mengubah dari RBAC ke ABAC.

Jika semua ini masuk ke dalam satu kelas atau abstraksi, maka kode keamanan Anda, yang merupakan salah satu alat terpenting Anda untuk mitigasi risiko , ironisnya, berisiko tinggi.

Saya telah bekerja dengan sistem di mana otentikasi dan otorisasi terlalu erat digabungkan. Dalam satu sistem, ada dua database pengguna paralel, masing-masing untuk satu jenis "peran". Orang atau tim yang mendesainnya tampaknya tidak pernah mempertimbangkan bahwa pengguna fisik tunggal mungkin berada di kedua peran, atau bahwa mungkin ada tindakan tertentu yang umum terjadi pada beberapa peran, atau bahwa mungkin ada masalah dengan tabrakan ID Pengguna. Ini adalah contoh yang diakui ekstrem, tetapi sangat menyakitkan untuk dikerjakan.

Microsoft dan Sun / Oracle (Java) merujuk pada agregat informasi otentikasi dan otorisasi sebagai Principal Keamanan . Itu tidak sempurna, tetapi bekerja dengan cukup baik. NET, misalnya, Anda memiliki IPrincipal, yang merangkum yang IIdentity- mantan makhluk kebijakan (otorisasi) objek sedangkan yang kedua adalah identitas (otentikasi). Anda bisa mempertanyakan keputusan untuk menempatkan satu di dalam yang lain, tetapi yang penting adalah bahwa sebagian besar kode yang Anda tulis hanya untuk salah satu abstraksi yang berarti mudah untuk menguji dan refactor.

Tidak ada yang salah dengan User.IsAdminbidang ... kecuali ada juga User.Namebidang. Ini akan menunjukkan bahwa konsep "Pengguna" tidak didefinisikan dengan benar dan ini, sayangnya, adalah kesalahan yang sangat umum di kalangan pengembang yang agak basah di belakang telinga ketika datang ke keamanan. Biasanya, satu-satunya hal yang harus dibagikan oleh identitas dan kebijakan adalah ID Pengguna, yang, tidak secara kebetulan, adalah persis bagaimana itu diterapkan di kedua model keamanan Windows dan * nix.

Benar-benar dapat diterima untuk membuat objek pembungkus yang merangkum identitas dan kebijakan. Misalnya, ini akan memfasilitasi pembuatan layar dasbor di mana Anda perlu menampilkan pesan "halo" di samping berbagai widget atau tautan yang diizinkan diakses oleh pengguna saat ini. Selama pembungkus ini hanya membungkus identitas dan informasi kebijakan, dan tidak mengklaim memilikinya. Dengan kata lain, selama itu tidak disajikan sebagai akar agregat .

Model keamanan yang sederhana selalu tampak seperti ide yang bagus ketika Anda pertama kali merancang aplikasi baru, karena YAGNI dan semua itu, tetapi hampir selalu akhirnya kembali menggigit Anda nanti, karena, kejutan yang mengejutkan, fitur baru ditambahkan!

Jadi, jika Anda tahu yang terbaik untuk Anda, Anda akan memisahkan informasi otentikasi dan otorisasi. Sekalipun "otorisasi" saat ini sesederhana bendera "IsAdmin", Anda masih akan lebih baik jika itu bukan bagian dari kelas atau tabel yang sama dengan informasi otentikasi, sehingga jika dan ketika kebijakan keamanan Anda perlu ubah, Anda tidak perlu melakukan operasi rekonstruksi pada sistem otentikasi Anda yang sudah berfungsi dengan baik.

Aaronaught
sumber
11
Oh, saya berharap saya punya waktu untuk menulis ini. Kemudian lagi, saya mungkin tidak akan bisa menaruhnya seperti yang Anda lakukan tanpa banyak memikirkan bagian saya. Seringkali naluri saya memberi tahu saya untuk pergi ke satu atau lain cara, tetapi menjelaskan mengapa - untuk diri saya sendiri atau orang lain - membutuhkan lebih banyak pemikiran dan upaya. Terima kasih untuk posting ini. Akan memiliki banyak-plussed itu, tetapi SE tidak akan membiarkan saya ...
Marjan Venema
Ini terdengar sangat mirip dengan proyek yang sedang saya kerjakan dan jawaban Anda memberi saya perspektif lain. Terima kasih banyak!
Brandon
"Tidak ada yang salah dengan bidang User.IsAdmin ... kecuali ada juga bidang User.Name" tulis Anda. Tapi apa isAdminitu metode? Mungkin saja mendelegasikan ke objek yang tanggung jawabnya untuk melacak izin. Apa praktik terbaik dalam desain model relasional (bidang apa yang dimiliki entitas) belum tentu diterjemahkan ke praktik terbaik dalam model OO (pesan apa yang dapat ditanggapi oleh objek).
KaptajnKold
@KaptajnKold: Saya terus membaca sentimen ini di berbagai utas Q&A di forum ini. Jika ada metode, tidak masalah jika langsung melakukan operasi atau mendelegasikannya , itu tetap dianggap sebagai tanggung jawab karena kelas lain mungkin mengandalkannya . Anda User.IsAdminmungkin hanya menjadi satu-liner yang tidak melakukan apa-apa selain menelepon PermissionManager.IsAdmin(this.Id), tetapi jika direferensikan oleh 30 kelas lain, Anda tidak dapat mengubah atau menghapusnya. Itu sebabnya SRP ada.
Aaronaught
Dan, @KaptajnKold, sejauh model relasional pergi, saya tidak yakin apa yang Anda maksud; bahkan lebih buruk memiliki IsAdmin ladang . Ini adalah jenis bidang yang cenderung bertahan lama setelah sistem berevolusi menjadi RBAC atau sesuatu yang lebih kompleks, dan secara bertahap keluar dari sinkronisasi dengan izin "nyata", dan orang-orang lupa bahwa mereka tidak seharusnya menggunakannya lagi, dan ... well, katakan saja tidak. Bahkan jika Anda berpikir Anda hanya memiliki dua peran, "admin" dan "non-admin", jangan menyimpannya sebagai bidang boolean / bit, normalkan dengan benar dan miliki tabel izin.
Aaronaught
28

Ya, setidaknya itu melanggar prinsip tanggung jawab tunggal . Haruskah ada perubahan (beberapa level admin, peran bahkan lebih berbeda?) Desain semacam ini akan cukup berantakan dan mengerikan untuk dipertahankan.

Pertimbangkan keuntungan memisahkan sistem keamanan dari kelas pengguna, sehingga keduanya dapat memiliki peran tunggal yang didefinisikan dengan baik. Anda berakhir dengan lebih bersih, lebih mudah untuk mempertahankan desain secara keseluruhan.

Zavior
sumber
2
@ GlenPeterson: Tidak, pengguna bisa eksis tanpa keamanan. Bagaimana dengan nama saya, nama tampilan saya, alamat saya, alamat email saya, dll. Di mana Anda akan meletakkannya? Identifikasi (otentikasi) dan otorisasi adalah binatang yang sama sekali berbeda.
Marjan Venema
2
class Useradalah komponen inti dalam triplet keamanan Identifikasi, Otentikasi, Otorisasi . Ini terkait erat pada tingkat desain, sehingga tidak masuk akal bagi kode untuk mencerminkan saling ketergantungan ini.
MSalters
1
Pertanyaannya adalah, apakah kelas Pengguna harus memuat semua logika untuk menangani triplet!
Zavior
3
@ MSalters: jika itu adalah API untuk logika seperti itu, ia tahu tentang hal itu bahkan jika mendelegasikan tempat lain untuk logika yang tepat. Poin yang dibuat adalah bahwa Otorisasi (perizinan) adalah tentang kombinasi pengguna dengan sesuatu yang lain dan oleh karena itu tidak boleh menjadi bagian dari pengguna atau sesuatu yang lain, tetapi bagian dari sistem perizinan yang benar-benar diketahui oleh pengguna dan apa pun sedang diberikan izin untuk.
Marjan Venema
2
Should there be changes- yah, kita tidak tahu bahwa akan ada perubahan. YAGNI dan semuanya. Apakah perubahan itu ketika diperlukan, kan?
Izkata
10

Saya pikir masalahnya, mungkin tidak diutarakan dengan baik, adalah terlalu banyak memberatkan Userkelas. Tidak, tidak ada masalah keamanan dengan memiliki metode User.isAdmin(), seperti yang disarankan dalam komentar tersebut. Lagi pula, jika seseorang cukup dalam dalam kode Anda untuk menyuntikkan kode berbahaya untuk salah satu kelas inti Anda, Anda memiliki masalah serius. Di sisi lain, tidak masuk akal Userjika memutuskan apakah itu administrator untuk setiap konteks. Bayangkan Anda menambahkan peran yang berbeda: moderator untuk forum Anda, editor yang dapat mempublikasikan posting ke halaman depan, penulis yang dapat menulis konten untuk diterbitkan oleh editor, dan sebagainya. Dengan pendekatan meletakkannya pada Userobjek, Anda akan berakhir dengan terlalu banyak metode dan terlalu banyak logika yang hidup pada Useryang tidak berhubungan langsung dengan kelas.

Sebagai gantinya, Anda bisa menggunakan enum dari berbagai jenis izin, dan PermissionsManagerkelas. Mungkin Anda bisa memiliki metode seperti PermissionsManager.userHasPermission(User, Permission), mengembalikan nilai boolean. Dalam praktiknya, ini mungkin terlihat seperti (dalam java):

if (!PermissionsManager.userHasPermission(user, Permissions.EDITOR)) {
  Site.renderSecurityRejectionPage();
}

Ini pada dasarnya setara dengan metode before_filtermetode Rails , yang memberikan contoh jenis pemeriksaan izin di dunia nyata.

asthasr
sumber
6
Bagaimana itu berbeda dari user.hasPermission(Permissions.EDITOR), kecuali bahwa itu lebih verbose dan prosedural daripada OO?
Cephalopod
9
@Arian: karena user.hasPermissionsmenempatkan terlalu banyak tanggung jawab ke dalam kelas pengguna. Ini mengharuskan pengguna mengetahui tentang izin, sementara pengguna (kelas) harus bisa ada tanpa pengetahuan tersebut. Anda tidak ingin kelas pengguna mengetahui tentang cara mencetak atau menampilkan (membuat formulir) itu sendiri, Anda menyerahkannya ke kelas pembuat render / pembangun atau tampilan kelas renderer / pembangun.
Marjan Venema
4
user.hasPermission(P)adalah API yang benar, tetapi itu hanya antarmuka. Keputusan nyata tentu saja harus dibuat dalam subsistem keamanan. Untuk menanggapi komentar Syrion tentang pengujian, selama pengujian Anda dapat menukar subsistem itu. Namun, manfaat langsungnya user.hasPermission(P)adalah Anda tidak mencemari semua kode hanya agar Anda dapat menguji class User.
MSalters
3
@MarjanVenema Dengan logika itu, Pengguna akan tetap menjadi objek data tanpa tanggung jawab sama sekali. Saya tidak mengatakan bahwa seluruh manajemen izin harus diimplementasikan di kelas pengguna, jika ada kebutuhan untuk sistem yang sedemikian rumit. Anda mengatakan bahwa pengguna tidak perlu tahu tentang izin, tapi saya tidak mengerti mengapa setiap kelas lain perlu tahu cara menyelesaikan izin untuk pengguna. Ini membuat mengejek dan menguji jauh lebih sulit.
Cephalopod
6
@Arian: sementara kelas anemia adalah bau, beberapa kelas tidak memiliki perilaku ... Pengguna, imho, adalah salah satu kelas yang lebih baik digunakan sebagai parameter untuk sejumlah besar hal lain (yang ingin melakukan hal-hal Saya membuat keputusan berdasarkan pada siapa yang meminta mereka), daripada digunakan sebagai wadah untuk semua jenis perilaku hanya karena tampaknya nyaman untuk membuat kode seperti itu.
Marjan Venema
9

Object.isX () digunakan untuk mewakili hubungan yang jelas antara objek dan X, yang dapat diekspresikan sebagai hasil boolean, misalnya:

Water.isBoiling ()

Circuit.isOpen ()

User.isAdmin () mengasumsikan makna tunggal 'Administrator' dalam setiap konteks sistem , bahwa pengguna, di setiap bagian sistem, adalah administrator.

Walaupun kedengarannya cukup sederhana, program dunia nyata hampir tidak akan pernah cocok dengan model ini, pasti akan ada persyaratan bagi pengguna untuk mengelola sumber daya [X] tetapi tidak [Y], atau untuk jenis administrator yang lebih berbeda (proyek [ ] administrator vs administrator [sistem]).

Situasi ini biasanya memerlukan penyelidikan persyaratan. Jarang, jika pernah, klien menginginkan hubungan yang benar-benar diwakili oleh User.isAdmin (), jadi saya akan ragu untuk mengimplementasikan solusi semacam itu tanpa klarifikasi.

FMJaguar
sumber
6

Poster itu hanya memiliki desain yang berbeda dan lebih rumit dalam pikiran. Menurut saya, User.isAdmin()tidak apa-apa . Bahkan jika Anda kemudian memperkenalkan beberapa model izin yang rumit, saya melihat sedikit alasan untuk pindah User.isAdmin(). Kemudian, Anda mungkin ingin membagi kelas Pengguna menjadi objek yang mewakili pengguna yang masuk dan objek statis yang mewakili data tentang pengguna. Atau mungkin juga tidak. Simpan besok untuk besok.

kevin cline
sumber
4
Save tomorrow for tomorrow. Ya. Ketika Anda mengetahui bahwa kode yang digabungkan dengan ketat yang baru saja Anda tulis telah membuat Anda bingung dan Anda harus menulis ulang karena Anda tidak mengambil 20 menit ekstra untuk memisahkannya ...
MirroredFate
5
@MirroredFate: Sangat mungkin untuk memiliki User.isAdminmetode yang terhubung ke mekanisme izin sewenang-wenang tanpa menghubungkan kelas Pengguna ke mekanisme tertentu.
kevin cline
3
Agar User.isAdmin digabungkan ke mekanisme izin sewenang-wenang, bahkan tanpa kopling ke mekanisme tertentu, membutuhkan ... well ... kopling. Yang minimum adalah antarmuka. Dan antarmuka itu seharusnya tidak ada di sana. Ini memberi kelas pengguna (dan antarmuka) sesuatu yang seharusnya tidak bertanggung jawab. Aaronaught menjelaskannya jauh lebih baik daripada yang saya dapat (seperti halnya kombinasi dari semua jawaban lain untuk pertanyaan ini).
Marjan Venema
8
@MirroredFate: Saya tidak peduli jika saya harus menulis ulang implementasi sederhana untuk memenuhi persyaratan yang lebih kompleks. Tetapi saya telah memiliki pengalaman yang sangat buruk dengan API yang terlalu rumit yang dirancang untuk memenuhi persyaratan masa depan yang tidak pernah muncul.
kevin cline
3
@ kevincline Meskipun API yang terlalu rumit (menurut definisi) adalah hal yang buruk, saya tidak menyarankan seseorang untuk menulis API yang terlalu rumit. Saya menyatakan itu Save tomorrow for tomorrowadalah pendekatan desain yang mengerikan karena itu membuat masalah yang akan sepele jika sistem diimplementasikan dengan benar jauh lebih buruk. Faktanya, mentalitas itulah yang menghasilkan API yang terlalu rumit, karena lapisan demi lapisan ditambahkan untuk menambal desain awal yang buruk. Saya kira sikap saya adalah measure twice, cut once.
MirroredFate
5

Tidak terlalu masalah ...

Saya tidak melihat masalah dengan User.isAdmin(). Saya tentu saja lebih suka yang seperti itu PermissionManager.userHasPermission(user, permissions.ADMIN), yang dalam nama suci SRP membuat kode kurang jelas dan tidak menambah nilai.

Saya pikir SRP sedang ditafsirkan sedikit secara harfiah oleh beberapa orang. Saya pikir itu baik-baik saja, bahkan lebih baik bagi kelas untuk memiliki antarmuka yang kaya. SRP berarti bahwa suatu objek harus mendelegasikan kepada kolaborator apa pun yang tidak termasuk dalam tanggung jawab tunggal itu. Jika peran admin pengguna adalah sesuatu yang lebih terlibat daripada bidang boolean, mungkin sangat masuk akal bagi objek pengguna untuk mendelegasikan ke Manajer Izin. Tetapi itu tidak berarti bahwa itu juga bukan ide yang baik bagi pengguna untuk mempertahankan isAdminmetodenya. Bahkan itu berarti bahwa ketika aplikasi Anda lulus dari yang sederhana ke kompleks, Anda ingin mengubah kode yang menggunakan objek Pengguna. TKI, klien Anda tidak perlu tahu apa yang sebenarnya diperlukan untuk menjawab pertanyaan apakah pengguna adalah admin atau tidak.

... tetapi mengapa Anda benar-benar ingin tahu?

Yang mengatakan, Sepertinya saya Anda jarang perlu tahu apakah Pengguna adalah admin kecuali untuk dapat menjawab beberapa pertanyaan lain, seperti apakah pengguna diizinkan untuk melakukan beberapa tindakan tertentu, misalnya memperbarui Widget. Jika itu masalahnya, saya lebih suka memiliki metode pada Widget, seperti isUpdatableBy(User user):

boolean isUpdatableBy(User user) {
    return user.isAdmin();
}

Dengan cara itu Widget bertanggung jawab untuk mengetahui kriteria apa yang harus dipenuhi agar dapat diperbarui oleh pengguna, dan pengguna bertanggung jawab untuk mengetahui apakah itu adalah admin. Desain ini mengkomunikasikan dengan jelas tentang niatnya dan membuatnya lebih mudah untuk lulus ke logika bisnis yang lebih kompleks jika dan ketika saatnya tiba.

[Sunting]

Masalah dengan tidak memiliki User.isAdmin()dan menggunakanPermissionManager.userHasPermission(...)

Saya pikir saya akan menambahkan jawaban saya untuk menjelaskan mengapa saya lebih suka memanggil metode pada objek Pengguna daripada memanggil metode pada objek PermissionManager jika saya ingin tahu apakah pengguna adalah admin (atau memiliki peran admin).

Saya pikir adil untuk menganggap bahwa Anda akan selalu bergantung pada kelas Pengguna di mana pun Anda perlu bertanya apakah pengguna ini admin? Itu ketergantungan yang tidak bisa Anda singkirkan. Tetapi jika Anda perlu meneruskan pengguna ke objek yang berbeda untuk mengajukan pertanyaan tentangnya, itu menciptakan ketergantungan baru pada objek di atas yang sudah Anda miliki pada Pengguna. Jika pertanyaannya banyak ditanyakan, itu adalah banyak tempat di mana Anda membuat ketergantungan ekstra, dan itu adalah banyak tempat yang mungkin harus Anda ubah jika salah satu dari ketergantungan itu menuntutnya.

Bandingkan ini dengan memindahkan ketergantungan ke kelas Pengguna. Sekarang, tiba-tiba Anda memiliki sistem di mana kode klien (kode yang perlu mengajukan pertanyaan adalah pengguna ini admin ) tidak digabungkan dengan implementasi bagaimana pertanyaan ini dijawab. Anda bebas untuk mengubah sistem izin sepenuhnya, dan Anda hanya perlu memperbarui satu metode dalam satu kelas untuk melakukannya. Semua kode klien tetap sama.

Bersikeras tidak memiliki isAdminmetode pada Pengguna karena takut menciptakan ketergantungan di kelas Pengguna pada subsistem izin, menurut saya mirip dengan menghabiskan dolar untuk mendapatkan uang receh. Tentu, Anda menghindari satu ketergantungan di kelas Pengguna, tetapi dengan biaya menciptakan satu di setiap tempat di mana Anda perlu mengajukan pertanyaan. Bukan tawaran yang bagus.

KaptajnKold
sumber
2
"SRP hanya berarti bahwa suatu objek harus mendelegasikan kepada kolaborator apa pun yang tidak termasuk dalam tanggung jawab tunggal" - salah . Definisi SRP mencakup semua yang dilakukan objek secara langsung dan semua yang didelegasikan. Kelas yang hanya melakukan satu tugas secara langsung, tetapi melakukan 50 tugas lainnya secara tidak langsung melalui delegasi, masih (mungkin) melanggar SRP.
Aaronaught
KaptajnKold: Saya memberi +1 karena saya setuju dengan Anda dan saya merasa sedikit bersalah tentang itu. Posting Anda menambah diskusi, tetapi tidak menjawab pertanyaan.
GlenPeterson
@ GlenPeterson Yah, saya mencoba untuk menjawab bagian dari pertanyaan yang 'seperti apa yang dilakukan dengan "benar"' Juga: Anda seharusnya sama sekali tidak merasa bersalah karena setuju dengan saya :)
KaptajnKold
1
@ JamesNnell Mungkin. Jika. Tapi itu detail implementasi yang saya masih akan membatasi dalam metode isAdmin pada Pengguna. Dengan begitu kode klien Anda tidak perlu berubah ketika "admin" seorang Pengguna berevolusi dari menjadi bidang boolean menjadi sesuatu yang lebih maju. Anda mungkin memiliki banyak tempat di mana Anda perlu tahu apakah Pengguna adalah admin dan Anda tidak ingin harus mengubahnya setiap kali sistem otorisasi Anda berubah.
KaptajnKold
1
@ JamesSnell Mungkin kita salah paham satu sama lain? Pertanyaan apakah pengguna adalah admin tidak sama dengan pertanyaan apakah pengguna diizinkan untuk melakukan tindakan tertentu atau tidak . Jawaban untuk pertanyaan pertama selalu independen dari konteksnya. Jawaban untuk yang kedua sangat tergantung pada konteksnya. Inilah yang saya coba sampaikan pada bagian kedua dari jawaban awal saya.
KaptajnKold
1

Diskusi ini mengingatkan saya pada Blog Post oleh Eric Lippert, yang menarik perhatian saya dalam diskusi serupa tentang desain kelas, keamanan dan kebenaran.

Mengapa Banyak Kelas Kerangka Disegel?

Secara khusus, Eric mengemukakan intinya:

4) Aman. Inti dari polimorfisme adalah bahwa Anda dapat melewati benda-benda yang terlihat seperti Hewan tetapi sebenarnya Jerapah. Ada masalah keamanan potensial di sini.

Setiap kali Anda menerapkan metode yang mengambil instance dari tipe yang tidak disegel, Anda HARUS menulis metode itu untuk menjadi kuat dalam menghadapi contoh yang berpotensi memusuhi tipe itu. Anda tidak dapat mengandalkan invarian apa pun yang Anda tahu benar tentang implementasi ANDA, karena beberapa halaman web yang bermusuhan dapat mensubklasifikasikan implementasi Anda, mengganti metode virtual untuk melakukan hal-hal yang mengacaukan logika Anda, dan meneruskannya. Setiap kali saya menyegel kelas , Saya bisa menulis metode yang menggunakan kelas itu dengan keyakinan bahwa saya tahu apa yang dilakukan kelas itu.

Ini mungkin tampak seperti bersinggungan, tapi saya pikir itu sesuai dengan poin yang disampaikan poster lain tentang prinsip tanggung jawab tunggal SOLID . Pertimbangkan kelas jahat berikut sebagai alasan untuk tidak menerapkan metode atau properti.User.IsAdmin

public class MyUser : User
{

    public new boolean IsAdmin()
    {
        // You know it!
        return true;
    }

    // Anything else we can break today?

}

Memang, ini sedikit sulit: belum ada yang menyarankan membuat IsAmdminmetode virtual, tetapi itu bisa terjadi jika arsitektur pengguna / peran Anda akhirnya menjadi sangat rumit. (Atau mungkin tidak. Pertimbangkan public class Admin : User { ... }: kelas seperti itu mungkin memiliki persis kode di atas di dalamnya.) Mendapatkan biner berbahaya ke tempatnya bukanlah vektor serangan umum dan memiliki potensi lebih besar untuk kekacauan daripada perpustakaan pengguna yang tidak jelas - sekali lagi, ini bisa jadi bug Privilege Escalation yang membuka pintu bagi kejahatan nyata. Akhirnya, jika sebuah instance biner atau objek jahat menemukan jalannya ke run time, itu tidak lebih dari sekadar membayangkan "Privilege or Security System" diganti dengan cara yang sama.

Tetapi mengingat inti dari hati Eric, jika Anda memasukkan kode pengguna Anda ke dalam jenis sistem rentan tertentu, mungkin Anda memang baru saja kehilangan permainan.

Oh, dan lebih tepatnya untuk catatan, saya setuju dengan postulat dalam pertanyaan: “Seorang pengguna tidak boleh memutuskan apakah itu seorang Admin atau bukan. Sistem Privileges atau Keamanan seharusnya. ” User.IsAdminMetode adalah ide yang buruk jika Anda menjalankan kode pada sistem Anda tidak tetap dalam kontrol 100% dari kode - Anda harus melakukannya Permissions.IsAdmin(User user).

Patrick M
sumber
Hah? Bagaimana ini relevan? Tidak peduli di mana Anda meletakkan kebijakan keamanan, Anda selalu dapat berpotensi subkelas dengan sesuatu yang tidak aman. Tidak masalah jika sesuatu itu adalah Pengguna, Kepala Sekolah, Kebijakan, PermissionSet, atau apa pun. Ini masalah yang sama sekali tidak terkait.
Aaronaught
Ini adalah argumen keamanan yang mendukung Single Responsibility. Untuk langsung menjawab poin Anda, modul keamanan & izin akan menjadi kelas tersegel, tetapi desain tertentu mungkin menginginkan kelas Pengguna menjadi virtual & diwarisi. Ini adalah poin terakhir pada artikel blog (jadi mungkin yang paling tidak mungkin / penting?), Dan mungkin saya sedikit mengklarifikasi masalah, tetapi mengingat sumbernya, jelas bahwa polimorfisme dapat menjadi ancaman keamanan dan karenanya membatasi tanggung jawab kelas / objek tertentu lebih aman.
Patrick M
Saya tidak yakin mengapa Anda mengatakan itu. Banyak kerangka kerja keamanan dan perizinan sangat dapat diperluas. Sebagian besar tipe .NET inti yang terkait dengan keamanan (IPrincipal, IIdentity, ClaimSet, dll.) Tidak hanya tidak disegel tetapi pada kenyataannya adalah antarmuka atau kelas abstrak sehingga Anda dapat mencolokkan apa pun yang Anda inginkan. Apa yang Eric bicarakan adalah Anda tidak ingin sesuatu seperti System.Stringwarisan karena semua jenis kode kerangka kerja kritis membuat asumsi yang sangat spesifik tentang cara kerjanya. Dalam praktiknya, sebagian besar "kode pengguna" (termasuk keamanan) dapat diwarisi untuk memungkinkan uji ganda.
Aaronaught
1
Bagaimanapun, polimorfisme tidak disebutkan di mana pun dalam pertanyaan, dan jika Anda mengikuti tautan penulis ke tempat komentar asli dibuat, jelas bahwa itu tidak merujuk pada warisan atau bahkan kelas invarian pada umumnya - ini tentang tanggung jawab.
Aaronaught
Saya tahu itu tidak disebutkan, tetapi prinsip tanggung jawab kelas terkait langsung dengan polimorfisme. Jika tidak ada IsAdminmetode untuk mengganti / mengkooptasi, tidak ada lubang keamanan potensial. Meninjau metode dari antarmuka sistem itu, IPrincipaldan IIdentity, jelas bahwa para desainer tidak setuju dengan saya, dan jadi saya mengakui intinya.
Patrick M
0

Masalahnya di sini adalah:

if (user.isAdmin()) {
   doPriviledgedOp();
} else {
   // maybe we can anyway!
   doPriviledgedOp();
}

Entah Anda telah mengatur keamanan Anda dengan benar dalam hal mana metode istimewa harus memeriksa apakah penelepon memiliki otoritas yang memadai - dalam hal ini cek itu berlebihan. Atau Anda belum dan mempercayai kelas yang tidak memiliki hak istimewa untuk membuat keputusan sendiri tentang keamanan.

James Anderson
sumber
4
Apa yang dimaksud dengan kelas tidak privat?
Brandon
1
Sebenarnya tidak ada yang dapat Anda lakukan untuk mencegah penulisan kode semacam ini. Tidak peduli bagaimana Anda mendesain aplikasi Anda, di suatu tempat akan ada pemeriksaan eksplisit seperti ini, dan seseorang dapat menulisnya menjadi tidak aman. Sekalipun sistem perizinan Anda sama efisiennya dengan menghias metode dengan atribut peran atau izin - seseorang dapat secara tidak benar melemparkan izin "publik" atau "semua orang" di atasnya. Jadi saya tidak benar-benar melihat bagaimana ini membuat metode seperti User.IsAdmin khusus masalah.
Aaronaught