Revisi tingkat akses pengguna menggunakan Power BI menggunakan contoh Bitrix CMS (BUS)

gambar

Artikel ini menunjukkan contoh penggunaan Power BI untuk menganalisis akses pengguna di situs yang menjalankan 1C-Bitrix.

Masalah


Seiring waktu, semakin banyak pengguna yang terhubung ke pengembangan sumber daya Internet dalam satu atau lain cara, dengan hak-hak lanjutan dari rata-rata pengguna situs.

Dalam hal ini, menjadi semakin sulit untuk mengontrol akses ke fungsi-fungsi rahasia. Nah, jika aturan ditulis yang membantu mengendalikan akses pada tingkat yang lebih atau kurang aman. Tetapi sering terjadi bahwa rekan kerja pindah ke unit lain, pergi dengan dekrit :) atau pergi, dan akses tetap.

Secara alami, ini membawa ancaman berbeda: kebocoran basis pelanggan, baik, hingga sabotase, dll.
Usia proyek tempat saya bekerja sudah 10 tahun. Basis data memiliki ratusan ribu pengguna, termasuk ratusan dengan hak istimewa.

Artikel ini menunjukkan contoh bagaimana Anda dapat menyederhanakan audit pengguna ke berbagai objek situs di bawah kendali Bitrix CMS (BUS).

Masalahnya adalah bahwa panel admin Bitrix tidak memberikan kesempatan untuk mendapatkan gambar lengkap dengan akses; Mengklik banyak tautan dan menunggu halaman admin dimuat juga tidak menyenangkan.

Power BI akan digunakan sebagai alat utama untuk ini (sedikit dari tujuan utamanya :)

Diasumsikan bahwa pembaca sudah terbiasa dengan Power BI pada tingkat dasar, tahu dasar-dasar SQL, dan juga tahu cara menggunakan panel admin Bitrix. Fitur aksesibilitas standar Bitrix akan dipertimbangkan.

Kerugian dari panel admin Bitrix


Tidak mungkin untuk melakukan audit di panel admin standar untuk waktu yang dapat diterima karena kurangnya gambar yang koheren dengan akses - data ringkasan untuk semua modul / bagian / blok info, dll., Di mana akses diberikan.

Kinerja Admin:

  1. Di bagian "Grup pengguna" pada panel admin Bitrix ada fitur yang menghasilkan kueri SQL untuk memilih semua grup dengan jumlah jumlah pengguna. Semuanya baik ketika alasnya kecil. Tetapi dengan database ratusan ribu pengguna, dengan ratusan grup pengguna pada server khusus dengan 128 GB RAM, cukup membuka bagian ini membutuhkan waktu 8 detik.
  2. Ada juga permintaan dalam kartu grup, yang karena alasan tertentu memilih semua grup pengguna, alih-alih menerima data hanya untuk yang dipilih. Kerugian ditahan 3 detik.

Solusi


Biasanya ada beberapa solusi untuk masalah tersebut.

  1. Tulis aturan untuk menyediakan akses ke situs dan ikuti dengan jelas.
  2. Melakukan audit akses secara berkala.
  3. Berharap yang terbaik dan tidak menyia-nyiakan sumber daya perusahaan yang terbatas.

Artikel ini hanya akan membahas metode kedua.

Tugasnya


  1. Pilih alat yang akan memungkinkan Anda memperoleh data dengan cepat pada tingkat akses setiap pengguna dengan hak yang diperluas.
  2. Siapkan alat-alat sehingga mereka jelas menunjukkan gambar dengan akses secara keseluruhan dengan detail dan interaktivitas yang diperlukan.
  3. Melakukan audit akses.

Akses penyimpanan dalam Bitrix


Bitrix memungkinkan Anda untuk mengkonfigurasi hak secara cukup fleksibel melalui grup pengguna.
Pengaturan akses disimpan terutama di tabel MySQL. Beberapa pengaturan disimpan dalam file. Misalnya, akses file dan folder disimpan dalam file .access.php.

Analisis akses pengguna dan grup pengguna ke:

  • blok info
  • formulir web dengan tingkat akses
  • status formulir web dengan tingkat akses
  • bagian dari situs
  • Modul Bitrix dengan tingkat akses

Alat-alatnya


  1. Power BI Desktop, yang memungkinkan Anda untuk memvisualisasikan data dengan baik, mendapatkan data dari berbagai sumber (hampir) di luar kotak. Sebenarnya, Power BI dapat diganti dengan Excel 2016 yang biasa dan lebih tinggi - PowerQuery sudah termasuk dalam pengirimannya, di mana Anda dapat memilih semua data untuk dianalisis. Namun, Power BI memungkinkan Anda untuk menampilkan data secara interaktif berdasarkan hubungan mereka, dan ini memungkinkan Anda untuk dengan cepat menemukan dependensi tersembunyi.
  2. Konektor MySQL harus dapat membuat kueri melalui Power BI ke server web MySQL.
  3. Kitty atau Putty untuk tunneling ke MySql jika akses ke database terbuka hanya melalui SSH.

Skema akses berikut diperoleh: Daya BI → Konektor MySQL → Kitty → MySQL.

Kekuatan BI


Power BI Desktop - memungkinkan Anda untuk memvisualisasikan data dengan baik, mendapatkan data dari berbagai sumber (hampir) di luar kotak. Sebenarnya, Power BI dapat diganti dengan Excel 2016 yang biasa dan lebih tinggi - PowerQuery sudah termasuk dalam pengirimannya, di mana Anda dapat memilih semua data untuk dianalisis. Namun, Power BI memungkinkan Anda untuk menampilkan data secara interaktif berdasarkan hubungan mereka, dan ini memungkinkan Anda untuk dengan cepat menemukan dependensi tersembunyi, yang kami butuhkan untuk revisi akses.

Anda dapat mengunduhnya di halaman resmi .

Konektor MySQL


Pergi ke halaman . Unduh dan pasang. Terkadang Anda harus me-restart PC Anda setelah instalasi.

Kitty / dempul


Untuk mengeksekusi query SQL ke database Bitrix, Anda perlu mengkonfigurasi tunnel.

  1. Masukkan IP server dan porta

    gambar
  2. Kami memalu nama pengguna dan kata sandi di SSH

    gambar
  3. Kami melakukan port forwarding:

    gambar
  4. Kami menyimpan pengaturan yang dibuat untuk digunakan di masa depan di profil:

    gambar
  5. Kita mulai.

Anda juga bisa mengunduh Putty dan menjalankannya dengan perintah:

putty.exe -ssh "USER@HOST" -pw "PASSWORD" -2 -v -P 22 -L 3306:127.0.0.1:3306 

Secara alami, Kitty / Putty harus dijalankan sebelum memperbarui data di Power BI.

Pengguna dan grup pengguna


Seperti dalam banyak CMS, Bitrix mengimplementasikan mekanisme untuk membatasi hak akses melalui grup pengguna.

Membongkar entitas dari database ke dalam model data Power BI:

  • Grup
  • Pengguna

... serta hubungan antara grup dan pengguna.

Grup


Kami membatasi diri hanya untuk grup aktif.

Daftar grup disimpan di tabel b_group.

  1. Buat koneksi:

    gambar
  2. Masukkan:

    1. di bidang Server: localhost: 3306
    2. di dalam Basis Data: bitrix_db (nama basis data yang digunakan Bitrix)
    3. Permintaan SQL:

       SELECT id, timestamp_x, active, name, description, anonymous FROM b_group WHERE active = 'Y'; 

      gambar

  3. Masukkan login dan kata sandi ke database dan kirim permintaan:

    gambar

    gambar

    gambar
  4. Segera berikan nama yang ramah untuk permintaan:

    gambar
  5. Kami mencantumkan grup pada lembar terpisah dalam bentuk tabel:

    gambar

Metode penggalian dan penyajian data ini akan serupa untuk pertanyaan lain yang terkait dengan basis data Bitrix.

Pengguna


Sekarang bongkar semua pengguna yang memiliki hak lanjut. Tetapi Anda tidak boleh membongkar pengguna yang hanya termasuk dalam grup yang tidak memberi mereka hak tambahan apa pun, misalnya, "Semua pengguna, termasuk yang tidak terdaftar" (perlu dicatat bahwa koneksi grup ini dengan pengguna disimpan untuk semua pengguna yang terdaftar sebelum versi 12. Di yang lebih baru versi, grup dianggap sistemik dan tidak menyimpan data tentang komunikasi dengan pengguna basis data).

Kami membatasi diri hanya untuk pengguna yang diaktifkan.

Untuk melakukan ini, Anda perlu:

  1. Pilih semua ID grup yang memberikan hak yang diperluas. Ini diperlukan untuk menghemat lalu lintas, karena jumlah entri dalam b_user_group dapat mencapai jutaan tergantung pada kompleksitas proyek.
  2. Buat permintaan dinamis untuk membongkar tautan Pengguna - Grup
  3. Bongkar pengguna yang memiliki tautan dari klausa 2.

Mari kita mulai:

  1. Hubungi editor kueri: Beranda → Edit Kueri
  2. Mari kita buat tautan ke permintaan awal "Grup":

    gambar
  3. Ubah nama permintaan baru menjadi "ID Grup" dan pilih hanya grup yang menarik dari sudut pandang keamanan.

    gambar
  4. Sekarang kita mendapatkan garis yang berisi ID grup yang dipisahkan oleh koma:
    • Tambahkan kolom khusus: AddColumn → Umum → Kolom Kustom

      gambar
    • Hapus semua kolom kecuali ID dan Pengelompokan:

      gambar
    • Kelompokkan menurut kolom “Pengelompokan”:

      gambar

      gambar
    • Tambahkan kolom lain sebagai berikut:

      gambar
    • Mari kita perluas daftar sehingga nilainya dipisahkan dengan koma:

      gambar
    • Dan jatuh ke dalam sel yang dihasilkan:

      gambar
    • Power BI kemudian mengonversi kueri menjadi variabel yang dapat digunakan dalam kueri SQL dinamis:

      gambar
  5. Mari kita membuat permintaan "Kelompok-pengguna" yang berisi hubungan pengguna dengan grup, mirip dengan bagaimana hal itu dilakukan di bagian "Grup".

    Permintaan SQL:

     SELECT ug.user_id, ug.group_id FROM b_user_group ug JOIN b_group g ON g.id = ug.group_id JOIN b_user u ON u.id = ug.user_id WHERE g.ACTIVE = 'Y' AND u.ACTIVE = 'Y' AND ug.group_id IN (); 

    XXX perlu diganti dengan ID grup, dipisahkan dengan koma.
  6. Kami akan memanggil sumber permintaan untuk mengedit dan menggantinya dengan yang berikut:

     let sql = "SELECT ug.user_id, ug.group_id #(lf)FROM b_user_group ug #(lf)JOIN b_group g ON g.id = ug.group_id #(lf)JOIN b_user u ON u.id = ug.user_id #(lf)WHERE g.ACTIVE = 'Y' #(lf) AND u.ACTIVE = 'Y' #(lf) AND ug.group_id IN ("&#"ID "&");", Source = MySQL.Database("localhost:3306", "bitrix_db", [ReturnSingleDatabase=true, Query=sql, CreateNavigationProperties=false]) in Source 

  7. Setelah itu, Anda bisa mendapatkan peringatan berikut:

     Formula.Firewall: Query '-' (step 'Source') references other queries or steps, so it may not directly access a data source. Please rebuild this data combination. 

    Untuk menghilangkannya, Anda perlu mengubah tingkat privasi:

    gambar

    gambar

    Setelah itu, perbarui kueri.
  8. Kami membuat variabel "ID Pengguna" dengan cara yang sama seperti yang dilakukan untuk "ID Grup" (yaitu, kami membuat tautan dari permintaan Pengguna, dll.). Dengan menggunakannya, kami akan menghasilkan kueri SQL yang memungkinkan kami memilih hanya pengguna yang kami butuhkan untuk analisis. Pertama hapus duplikat user_id:

    gambar
  9. Kami membuat permintaan untuk pemilihan pengguna, mirip dengan bagaimana hal itu dilakukan untuk "Kelompok-pengguna".

     SQL: SELECT id, last_name, NAME, email, date_register, last_login FROM b_user WHERE active = 'Y' AND id IN ( ); 

    XXX harus mengganti ID pengguna.

Menyiapkan hubungan antara permintaan


Agar Power BI menyaring data secara interaktif dalam tampilan yang berbeda, Anda perlu menentukan hubungan antara kueri. Dalam kasus kami, kami perlu menghubungkan bidang:

  • “Grup pengguna” [group_id] → “Grup” [id]
  • “Kelompok pengguna” [user_id] → “Pengguna” [id]

gambar

Demikian pula, kami akan mengikat pertanyaan lain.

Laporan Pengguna dan Grup Pengguna


Pada tab Laporan, kami menampilkan daftar pengguna dan grup yang menggunakan Tabel sebagai elemen visualisasi.

Dari permintaan “Pengguna”, pilih bidang: last_name, name, last_login, email.
Dari permintaan “Kelompok pengguna”, pilih bidang group_id.
Karena Karena kami menetapkan koneksi antar permintaan, Power BI akan dapat menggunakan fungsi agregasi Count dengan benar untuk menghitung jumlah grup yang dimiliki masing-masing pengguna.

gambar

Mari kita tambahkan Tabel lain di sebelahnya dan pilih bidang nama dari permintaan Grup, dan bidang user_id dari permintaan Grup pengguna - setel agregasi "Hitung (Berbeda)" untuknya untuk melihat jumlah pengguna dalam grup.

Karena Permintaan "Grup" dan "Pengguna" terhubung melalui permintaan asosiatif "Grup-pengguna", maka ketika Anda mengklik pengguna dalam tabel dengan daftar grup, hanya grup-grup yang menjadi milik pengguna yang dipilih yang akan ditampilkan. Begitu juga sebaliknya.

gambar

Dengan cara ini, Anda dapat mengklik salah satu pengguna dan melihat grup yang menjadi anggotanya, atau mengklik grup tersebut dan melihat pengguna mana yang merupakan bagian dari grup. Nah, kemudian buat keputusan tentang mengubah akses bagi pengguna.

Berikut ini menjelaskan cara menempatkan tabel yang tersisa di laporan umum Power BI, karena ini dilakukan dengan cara yang serupa.

.access.php


Dalam Bitrix Anda dapat mengatur akses ke folder dan file dengan menentukan nomor grup dan tingkat akses yang diperlukan dalam file .access.php.

Tugas kita adalah untuk mengurangi data dari semua file .access.php yang tersebar di sekitar server proyek menjadi tampilan tabular.

Untuk melakukan ini:

  1. Kami mencari dan mengarsipkan semua file .access.php dari server, menyimpan path ke file-file ini.
    Saya menggunakan terminalka untuk mencari, menyalin, dan mengarsipkan file yang ditemukan. Contoh perintah:

     find “BITRIX_PROJECT_DIR” -name '.access.php' -type f > “OUTPUT_DIR/.access.php.files.txt”&&tar cvfpz “OUTPUT_DIR/.access.php.files.tar” -T “OUTPUT_DIR/.access.php.files.txt”&&find “OUTPUT_DIR” -type d -exec chmod 775 {} \; && find “OUTPUT_DIR” -type f -exec chmod 775 {} \;&&find “OUTPUT_DIR” -type d -exec chown bitrix:bitrix {} \; && find “OUTPUT_DIR”/ -type f -exec chown bitrix:bitrix {} \; 

    Di sini:

    • BITRIX_PROJECT_DIR - folder dengan proyek di Bitrix.
    • OUTPUT_DIR - path ke folder tempat file .access.php.files.txt dengan daftar .access.php yang ditemukan akan ditempatkan, serta arsip .access.php.files.tar yang berisi salinan semua yang ditemukan .access.php.

    Tentu, jika ada banyak proyek (multisite digunakan), maka kami memilih folder yang berisi semua proyek.
  2. Unduh dan unzip arsip .access.php di suatu tempat di sebelah proyek Power BI.
    Saya menulis file batch yang melakukan ini secara otomatis: unduhan diimplementasikan melalui wget; melalui 7zip - membuka ritsleting.

    Contoh file batch:

    gambar

    File yang berisi pengaturan untuk file batch:

    gambar

Sekarang buat kueri yang akan meringkas isi semua .access.php dalam bentuk tabel.

  1. Untuk kenyamanan, buat parameter yang akan berisi path ke folder dari mana kita akan mengekstrak konten semua .access.php

    gambar
  2. Kami akan memilih permintaan mengetik "Folder" dan memilih parameter kami sebagai jalur:

    gambar
  3. Luaskan bidang Konten:

    gambar

    XXXXXXX adalah pemisah kolom, Anda perlu satu kolom setelah mengimpor data dari semua file.
  4. Setelah itu, Power BI akan menghapus kolom yang kita butuhkan, berisi path ke .access.php. Oleh karena itu, kita perlu mengedit langkah "Hapus kolom lain1", memilih "Jalur Folder" di dalamnya:

    gambar
  5. Biarkan kolom: Folder Path dan Column1.
  6. Untuk menghapus path absolut ke file lokal dari Folder Path, gunakan penggantian:

    gambar
  7. File .access.php berisi pengaturan akses dalam format:

     $PERM[""]["ID "] = "< >"; 

    Tugas kami adalah untuk menyebarkan kolom: Path, ID Grup, Level Akses. Ini dilakukan dengan menggunakan filter, pemisahan kolom (Split Column) dan kolom khusus (Custom kolom).
  8. Hasilnya harus berupa tabel berikut:

    gambar

    Seperti yang Anda lihat di bidang ID grup ada "*" (akses untuk semua orang). Agar dapat menentukan koneksi dengan permintaan lain, kita perlu membuat bilangan bulat bidang ini, tanpa kehilangan informasi tentang "*" (yang berarti untuk semua grup). Mari kita membuat dua permintaan, seperti "tautan" ke permintaan DotAccessPhp asli:

    • DotAccessPhpForRels pertama hanya akan berisi ID grup integer (kami menggunakan filter dengan menghapus * di kolom ID grup) - kami akan menghubungkannya dengan sisa permintaan:
      gambar
    • Yang kedua - DotAccessPhpForAll - saja * (gunakan filter).

Diagram koneksi:

gambar

Untuk hanya menampilkan data terkait saat memilih file dari DotAccessForRels dalam tampilan lain, ubah parameter "Arah filter silang" ke Keduanya:

gambar

Untuk permintaan lain yang akan ditambahkan di bawah, ini juga perlu dilakukan.

Blok info


Anda perlu membongkar daftar blok info dan daftar tautan blok info dengan grup.

Kami hanya akan mengunggah informasi tentang blok info aktif.

  1. Kami membuat permintaan "Infoblocks". Permintaan SQL:

     SELECT i.id, i.NAME '', i.TIMESTAMP_X ' ', GROUP_CONCAT(ist.SITE_ID SEPARATOR ', ') '' FROM b_iblock i JOIN b_iblock_site ist ON ist.IBLOCK_ID = i.id GROUP BY 1,2,3;   “-”: SELECT ig.iblock_id, ig.group_id, ig.permission FROM b_iblock_group ig JOIN b_group g ON g.id = ig.group_id JOIN b_iblock i ON i.ID = ig.IBLOCK_ID WHERE g.ACTIVE = 'Y' AND i.ACTIVE = 'Y'; 
  2. Kami memperbarui skema komunikasi, tidak lupa mengubah parameter "Arah filter silang" menjadi Keduanya:

    gambar

Bentuk


Dalam hal formulir, hak untuk grup pengguna diberikan baik untuk formulir itu sendiri maupun status di mana hasil mengisi formulir berada.

  1. Buat permintaan untuk "Formulir":

     SELECT f.ID, f.name '', GROUP_CONCAT(f2s.SITE_ID SEPARATOR ', ') '' FROM b_form f JOIN b_form_2_site f2s ON f2s.FORM_ID = f.ID GROUP BY 1, 2 ORDER BY 2; 
  2. Buat permintaan "Grup formulir":

     SELECT DISTINCT f2g.group_id, f2g.form_id, f2g.PERMISSION ' ' FROM b_form_2_site f2s JOIN b_form_2_group f2g ON f2g.FORM_ID = f2s.FORM_ID JOIN b_group g ON g.ID = f2g.group_ID WHERE g.ACTIVE = 'Y' ORDER BY 1, 2, 3; 
  3. Kami membuat permintaan "Formulir Status".

     SELECT fs.ID, fs.TITLE '', fs.form_id FROM b_form_status fs JOIN b_form f ON f.ID = fs.FORM_ID WHERE fs.ACTIVE = 'Y' AND EXISTS (SELECT f2s.FORM_ID FROM b_form_2_site f2s WHERE f2s.FORM_ID = f.ID LIMIT 1) ORDER BY 3, 2; 
  4. Buat permintaan "Formulir status grup"

     SELECT fs2g.status_id, fs2g.group_id, fs2g.PERMISSION '' FROM b_form_status_2_group fs2g JOIN b_form_status fs ON fs.ID = fs2g.STATUS_ID JOIN b_group g ON g.ID = fs2g.group_ID JOIN b_form f ON f.ID = fs2g.GROUP_ID JOIN b_form_2_site f2s ON f2s.FORM_ID = f.ID WHERE fs.ACTIVE = 'Y' AND (g.ACTIVE = 'Y') ORDER BY 1, 2, 3; 
  5. Memperbarui skema koneksi:

    gambar

Modul


  1. Kami membuat permintaan "Grup modul".

     SELECT mg.MODULE_ID '', mg.group_id, mg.G_ACCESS '', t.LETTER, t.NAME FROM b_module_group mg JOIN b_group g ON g.id = mg.GROUP_ID LEFT JOIN b_task t ON t.MODULE_ID = mg.MODULE_ID AND t.BINDING = 'module' WHERE g.active = 'Y' AND mg.G_ACCESS = t.LETTER; 
  2. Memperbarui komunikasi:

    gambar

Papan skor


Kami menyesuaikan gaya tabel, menggunakan ruang yang dapat digunakan secara maksimal.

Hasilnya harus mirip dengan yang berikut:

gambar

Papan skor yang sedikit dimodifikasi (jumlah elemen dalam tabel):

gambar

Ngomong-ngomong, lebih mudah untuk mengkonfigurasi tampilan satu tabel, dan kemudian menerapkannya ke tabel lain menggunakan Home → Format Painter. Fungsi ini bertindak dengan cara yang sama seperti di Word dan Excel (Format oleh sampel).

Tautan Admin


Agar Anda dapat dengan cepat masuk ke situs dan membuat pengaturan di panel admin, Anda dapat menambahkan kolom khusus dalam bahasa DAX dan menjadikannya jenis "URL Web". Untuk melakukan ini, pilih kolom yang dibuat dan tentukan jenis yang sesuai (Pemodelan → Properti → Kategori Data → URL Web).

Contoh untuk permintaan Grup:

gambar

Tambahkan kolom ke tampilan:

gambar
Sekarang Anda cukup mengklik pada sel tabel dan pergi ke kartu grup di panel admin Bitrix.

Laporan File


Untuk kenyamanan, Anda dapat membuat laporan terpisah dengan menempatkan tabel di atasnya mengenai akses ke file dan bagian dari sumber daya Internet:

gambar

Laporan ini juga menambahkan tautan untuk mengedit semua .access.php secara langsung melalui panel admin Bitrix.

Ringkasan


Bitrix adalah juara di antara monster cms dengan pro dan kontra yang jelas, cantik di luar dan mengerikan di dalam. Itu tidak memiliki alat administrasi akses yang mudah. Tetapi masalah ini diselesaikan dengan bantuan alat gratis, tanpa menarik waktu programmer yang berharga untuk proses ini.

Kelebihan dari pendekatan ini juga mencakup kemampuan untuk dengan cepat melengkapi model di Power BI dengan informasi tambahan dari Bitrix, misalnya, seseorang ingin tahu kapan .access.php dan lainnya dibuat atau diubah.

Sekarang, setelah membangun model hak akses dan memvisualisasikannya di Power BI, itu sudah cukup:

  1. klik secara konsisten pada pengguna, grup, formulir, file, dan secara real time lihat semua koneksi terkait akses;
  2. cepat pergi ke halaman admin yang diperlukan untuk mengedit;
  3. Perbarui model data dengan data Bitrix terbaru langsung di Power BI.

Akibatnya, audit dilakukan dan penyesuaian dilakukan dalam akses pengguna.

PS Di pasar ada modul gratis "Access Control Center", tetapi sangat terbatas, dan komentar terakhir adalah lebih dari 5 tahun. Mungkin seseorang akan menyukai gagasan membangun dasbor seperti itu di Bitrix dan dia akan mengimplementasikannya sebagai modul ...

PS2. Jika ada yang tertarik pada topik menggunakan Power BI untuk memecahkan masalah menemukan dependensi tersembunyi di berbagai sistem akuntansi, kemudian tulis di komentar. Saya kemudian akan menulis beberapa artikel tentang hal ini.

PS3 Terima kasih kepada kolega saya karena membantu saya menyiapkan artikel ini: Alexander Voronkov, Evgeny Shapochkin, Alexei Titov.

Source: https://habr.com/ru/post/id457626/


All Articles