Buat pengguna Google dari PowerShell via API

Hai

Artikel ini akan menjelaskan bagaimana PowerShell berinteraksi dengan Google API untuk memanipulasi pengguna G Suite.

Dalam organisasi, kami menggunakan beberapa layanan internal dan cloud. Untuk sebagian besar, otorisasi di dalamnya turun ke Google atau Direktori Aktif, di antaranya kami tidak dapat mempertahankan replika, masing-masing, ketika karyawan baru dirilis, Anda perlu membuat / mengaktifkan akun di kedua sistem ini. Untuk mengotomatiskan proses, kami memutuskan untuk menulis skrip yang mengumpulkan informasi dan mengirimkannya ke kedua layanan.

Login


Menyusun persyaratan, kami memutuskan untuk menggunakan orang sungguhan sebagai administrator untuk otorisasi, ini menyederhanakan analisis tindakan jika terjadi perubahan besar yang tidak disengaja atau disengaja.

Google APIs menggunakan protokol OAuth 2.0 untuk otentikasi dan otorisasi. Gunakan kasing dan deskripsi yang lebih terperinci dapat ditemukan di sini: Menggunakan OAuth 2.0 untuk Mengakses Google API .

Saya memilih skrip yang digunakan untuk otorisasi dalam aplikasi desktop. Ada juga opsi untuk menggunakan akun layanan yang tidak memerlukan gerakan yang tidak perlu dari pengguna.

Gambar di bawah ini adalah deskripsi skematis dari skenario yang dipilih dari halaman Google.



  1. Pertama, kami mengirim pengguna ke halaman otentikasi di akun Google, menunjukkan parameter GET:
    • id aplikasi
    • area tempat aplikasi membutuhkan akses
    • alamat dimana pengguna akan dialihkan setelah prosedur selesai
    • cara kami akan memperbarui token
    • kode verifikasi
    • format pengiriman kode verifikasi

  2. Setelah otorisasi selesai, pengguna akan diarahkan ke halaman yang ditentukan dalam permintaan pertama, dengan kesalahan atau kode otorisasi ditransmisikan oleh parameter GET
  3. Aplikasi (skrip) perlu mendapatkan parameter ini dan, jika kode diterima, jalankan permintaan token berikut
  4. Jika permintaannya benar, Google API mengembalikan:

    • Token akses yang dengannya kami dapat membuat permintaan
    • Validitas token ini
    • Refresh token diperlukan untuk memperbarui token akses.

Pertama Anda harus pergi ke konsol Google API: Kredensial - Google API Console , pilih aplikasi yang diinginkan dan buat pengenal klien OAuth di bagian Kredensial. Di tempat yang sama (atau lebih baru, di properti pengenal yang dibuat), Anda harus menentukan alamat yang memungkinkan pengalihan. Dalam kasus kami, ini akan menjadi beberapa entri host lokal dengan port yang berbeda (lihat di bawah).

Untuk membuatnya lebih mudah untuk membaca algoritma skrip, Anda dapat menampilkan langkah-langkah pertama dalam fungsi terpisah yang akan mengembalikan Access dan menyegarkan token untuk aplikasi:

$client_secret = 'Our Client Secret' $client_id = 'Our Client ID' function Get-GoogleAuthToken { if (-not [System.Net.HttpListener]::IsSupported) { "HttpListener is not supported." exit 1 } $codeverifier = -join ((65..90) + (97..122) + (48..57) + 45 + 46 + 95 + 126 |Get-Random -Count 60| % {[char]$_}) $hasher = new-object System.Security.Cryptography.SHA256Managed $hashByteArray = $hasher.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($codeverifier)) $base64 = ((([System.Convert]::ToBase64String($hashByteArray)).replace('=','')).replace('+','-')).replace('/','_') $ports = @(10600,15084,39700,42847,65387,32079) $port = $ports[(get-random -Minimum 0 -maximum 5)] Write-Host "Start browser..." Start-Process "https://accounts.google.com/o/oauth2/v2/auth?code_challenge_method=S256&code_challenge=$base64&access_type=offline&client_id=$client_id&redirect_uri=http://localhost:$port&response_type=code&scope=https://www.googleapis.com/auth/admin.directory.user https://www.googleapis.com/auth/admin.directory.group" $listener = New-Object System.Net.HttpListener $listener.Prefixes.Add("http://localhost:"+$port+'/') try {$listener.Start()} catch { "Unable to start listener." exit 1 } while (($code -eq $null)) { $context = $listener.GetContext() Write-Host "Connection accepted" -f 'mag' $url = $context.Request.RawUrl $code = $url.split('?')[1].split('=')[1].split('&')[0] if ($url.split('?')[1].split('=')[0] -eq 'error') { Write-Host "Error!"$code -f 'red' $buffer = [System.Text.Encoding]::UTF8.GetBytes("Error!"+$code) $context.Response.ContentLength64 = $buffer.Length $context.Response.OutputStream.Write($buffer, 0, $buffer.Length) $context.Response.OutputStream.Close() $listener.Stop() exit 1 } $buffer = [System.Text.Encoding]::UTF8.GetBytes("Now you can close this browser tab.") $context.Response.ContentLength64 = $buffer.Length $context.Response.OutputStream.Write($buffer, 0, $buffer.Length) $context.Response.OutputStream.Close() $listener.Stop() } Return Invoke-RestMethod -Method Post -Uri "https://www.googleapis.com/oauth2/v4/token" -Body @{ code = $code client_id = $client_id client_secret = $client_secret redirect_uri = 'http://localhost:'+$port grant_type = 'authorization_code' code_verifier = $codeverifier } $code = $null 

Kami menetapkan ID Klien dan Rahasia Klien yang diperoleh dalam properti pengenal klien OAuth, dan pemverifikasi kode adalah string yang panjangnya 43 hingga 128 karakter, yang harus dihasilkan secara acak dari karakter yang tidak dilindungi: [AZ] / [az] / [0-9 ] / "-" / "." / "_" / "~".

Selanjutnya kode ini akan dikirim ulang. Ini menghilangkan kerentanan di mana penyerang bisa mencegat respons yang dikembalikan sebagai pengalihan setelah otorisasi pengguna.
Anda dapat mengirim pemverifikasi kode dalam permintaan saat ini dalam bentuk yang jelas (yang membuatnya tidak berarti - ini hanya cocok untuk sistem yang tidak mendukung SHA256), atau dengan membuat hash SHA256 yang perlu dikodekan dalam BASE64Url (berbeda dari Base64 dalam dua karakter dari tabel) dan menghapus karakter. akhir baris: =.

Selanjutnya, kita harus mulai mendengarkan http di mesin lokal untuk mendapatkan respons setelah otorisasi, yang akan kembali sebagai pengalihan.

Tugas administratif dilakukan pada server khusus, kami tidak dapat mengecualikan kemungkinan bahwa beberapa administrator akan menjalankan skrip pada saat yang sama, jadi dia akan secara acak memilih port untuk pengguna saat ini, tetapi saya menentukan port yang telah ditentukan, karena mereka juga harus ditambahkan sebagai tepercaya di konsol API.

access_type = offline berarti aplikasi dapat memperbarui token yang kadaluwarsa tanpa interaksi pengguna dengan browser,
response_type = kode mengatur format untuk bagaimana kode akan dikembalikan (merujuk pada metode otorisasi lama ketika pengguna menyalin kode dari browser ke skrip),
ruang lingkup menunjukkan ruang lingkup dan jenis akses. Mereka harus dipisahkan oleh spasi atau% 20 (sesuai dengan URL Encoding). Daftar area akses dengan tipe dapat dilihat di sini: OAuth 2.0 Scopes for Google APIs .

Setelah menerima kode otorisasi, aplikasi akan mengembalikan pesan penutup ke browser, berhenti mendengarkan port dan mengirim permintaan POST untuk menerima token. Kami menunjukkan di dalamnya id dan rahasia yang sebelumnya ditetapkan dari konsol API, alamat yang akan diarahkan pengguna, dan grant_type sesuai dengan spesifikasi protokol.

Sebagai tanggapan, kita akan mendapatkan token Access, durasinya dalam detik dan token Refresh, yang dengannya kita dapat memperbarui token Access.

Aplikasi harus menyimpan token di tempat yang aman dengan masa simpan yang lama, jadi hingga kami mencabut akses yang diterima, token penyegaran tidak akan dikembalikan ke aplikasi. Pada akhirnya, saya menambahkan permintaan untuk mencabut token, jika aplikasi tidak berhasil diselesaikan dan token penyegelan tidak kembali, itu akan memulai prosedur lagi (kami menganggap tidak aman untuk menyimpan token secara lokal di terminal, tetapi kami tidak ingin menyulitkan kriptografi atau sering membuka browser).

 do { $token_result = Get-GoogleAuthToken $token = $token_result.access_token if ($token_result.refresh_token -eq $null) { Write-Host ("Session is not destroyed. Revoking token...") Invoke-WebRequest -Uri ("https://accounts.google.com/o/oauth2/revoke?token="+$token) } } while ($token_result.refresh_token -eq $null) $refresh_token = $token_result.refresh_token $minute = ([int]("{0:mm}" -f ([timespan]::fromseconds($token_result.expires_in))))+((Get-date).Minute)-2 if ($minute -lt 0) {$minute += 60} elseif ($minute -gt 59) {$minute -=60} $token_expire = @{ hour = ([int]("{0:hh}" -f ([timespan]::fromseconds($token_result.expires_in))))+((Get-date).Hour) minute = $minute } 

Seperti yang sudah Anda perhatikan, invoke-webrequest digunakan saat mencabut token. Tidak seperti Invoke-RestMethod, itu tidak mengembalikan data yang diterima dalam format yang nyaman untuk digunakan dan menunjukkan status permintaan.

Selanjutnya, skrip akan meminta Anda untuk memasukkan nama pengguna, menghasilkan nama pengguna + email.

Pertanyaan


Berikut ini adalah permintaan - pertama-tama, Anda perlu memeriksa apakah pengguna sudah ada dengan login tersebut untuk mendapatkan keputusan tentang pembentukan yang baru atau mengaktifkan yang sekarang.

Saya memutuskan untuk mengimplementasikan semua permintaan dalam format fungsi tunggal dengan pilihan menggunakan sakelar:

 function GoogleQuery { param ( $type, $query ) switch ($type) { "SearchAccount" { Return Invoke-RestMethod -Method Get -Uri "https://www.googleapis.com/admin/directory/v1/users" -Headers @{Authorization = "Bearer "+(Get-GoogleToken)} -Body @{ domain = 'rocketguys.com' query = "email:$query" } } "UpdateAccount" { $body = @{ name = @{ givenName = $query['givenName'] familyName = $query['familyName'] } suspended = 'false' password = $query['password'] changePasswordAtNextLogin = 'true' phones = @(@{ primary = 'true' value = $query['phone'] type = "mobile" }) orgUnitPath = $query['orgunit'] } Return Invoke-RestMethod -Method Put -Uri ("https://www.googleapis.com/admin/directory/v1/users/"+$query['email']) -Headers @{Authorization = "Bearer "+(Get-GoogleToken)} -Body (ConvertTo-Json $body) -ContentType 'application/json; charset=utf-8' } "CreateAccount" { $body = @{ primaryEmail = $query['email'] name = @{ givenName = $query['givenName'] familyName = $query['familyName'] } suspended = 'false' password = $query['password'] changePasswordAtNextLogin = 'true' phones = @(@{ primary = 'true' value = $query['phone'] type = "mobile" }) orgUnitPath = $query['orgunit'] } Return Invoke-RestMethod -Method Post -Uri "https://www.googleapis.com/admin/directory/v1/users" -Headers @{Authorization = "Bearer "+(Get-GoogleToken)} -Body (ConvertTo-Json $body) -ContentType 'application/json; charset=utf-8' } "AddMember" { $body = @{ userKey = $query['email'] } $ifrequest = Invoke-RestMethod -Method Get -Uri "https://www.googleapis.com/admin/directory/v1/groups" -Headers @{Authorization = "Bearer "+(Get-GoogleToken)} -Body $body $array = @() foreach ($group in $ifrequest.groups) {$array += $group.email} if ($array -notcontains $query['groupkey']) { $body = @{ email = $query['email'] role = "MEMBER" } Return Invoke-RestMethod -Method Post -Uri ("https://www.googleapis.com/admin/directory/v1/groups/"+$query['groupkey']+"/members") -Headers @{Authorization = "Bearer "+(Get-GoogleToken)} -Body (ConvertTo-Json $body) -ContentType 'application/json; charset=utf-8' } else { Return ($query['email']+" now is a member of "+$query['groupkey']) } } } } 

Di setiap permintaan, Anda harus mengirim tajuk Otorisasi yang berisi jenis token dan token Access itu sendiri. Saat ini, jenis token selalu Bearer. Karena kita perlu memeriksa bahwa token tidak kedaluwarsa dan memperbaruinya setelah satu jam dari saat dikeluarkan, saya menunjukkan permintaan untuk fungsi lain yang mengembalikan token akses. Bagian kode yang sama ada di awal skrip saat menerima token akses pertama:

 function Get-GoogleToken { if (((Get-date).Hour -gt $token_expire.hour) -or (((Get-date).Hour -ge $token_expire.hour) -and ((Get-date).Minute -gt $token_expire.minute))) { Write-Host "Token Expired. Refreshing..." $request = (Invoke-RestMethod -Method Post -Uri "https://www.googleapis.com/oauth2/v4/token" -ContentType 'application/x-www-form-urlencoded' -Body @{ client_id = $client_id client_secret = $client_secret refresh_token = $refresh_token grant_type = 'refresh_token' }) $token = $request.access_token $minute = ([int]("{0:mm}" -f ([timespan]::fromseconds($request.expires_in))))+((Get-date).Minute)-2 if ($minute -lt 0) {$minute += 60} elseif ($minute -gt 59) {$minute -=60} $script:token_expire = @{ hour = ([int]("{0:hh}" -f ([timespan]::fromseconds($request.expires_in))))+((Get-date).Hour) minute = $minute } } return $token } 

Memeriksa keberadaan login:

 function Check_Google { $query = (GoogleQuery 'SearchAccount' $username) if ($query.users -ne $null) { $user = $query.users[0] Write-Host $user.name.fullName' - '$user.PrimaryEmail' - suspended: '$user.Suspended $GAresult = $user } if ($GAresult) { $return = $GAresult } else {$return = 'gg'} return $return } 

Permintaan email: $ query akan meminta API untuk mencari pengguna dengan email itu, termasuk alias akan ditemukan. Anda juga dapat menggunakan wildcard: =,:,: {PREFIX} * .

Untuk memperoleh data, metode GET request digunakan, untuk menyisipkan data (membuat akun atau menambahkan anggota ke grup) - POST, untuk memperbarui data yang ada - PUT, untuk menghapus entri (misalnya, peserta dari grup) - HAPUS.

Skrip juga akan meminta nomor telepon (string yang tidak valid) dan untuk memasukkannya dalam grup distribusi regional. Itu memutuskan unit organisasi mana yang pengguna harus berdasarkan pada Active Directory OU yang dipilih dan akan muncul dengan kata sandi:

 do { $phone = Read-Host "   +7" } while (-not $phone) do { $moscow = Read-Host "  ? (y/n) " } while (-not (($moscow -eq 'y') -or ($moscow -eq 'n'))) $orgunit = '/' if ($OU -like "*OU=Delivery,OU=Users,OU=ROOT,DC=rocket,DC=local") { Write-host "   /Team delivery" $orgunit = "/Team delivery" } $Password = -join ( 48..57 + 65..90 + 97..122 | Get-Random -Count 12 | % {[char]$_})+"*Ba" 

Dan kemudian mulai memanipulasi akun:

 $query = @{ email = $email givenName = $firstname familyName = $lastname password = $password phone = $phone orgunit = $orgunit } if ($GMailExist) { Write-Host "  " -f mag (GoogleQuery 'UpdateAccount' $query) | fl write-host "      $Username  Google." } else { Write-Host "  " -f mag (GoogleQuery 'CreateAccount' $query) | fl } if ($moscow -eq "y"){ write-host "   moscowoffice" $query = @{ groupkey = 'moscowoffice@rocketguys.com' email = $email } (GoogleQuery 'AddMember' $query) | fl } 

Fungsi memperbarui dan membuat akun memiliki sintaks yang sama, tidak semua bidang tambahan diperlukan, di bagian dengan nomor telepon Anda perlu menentukan array yang dapat berisi dari satu rekaman dengan nomor dan jenisnya.

Agar tidak mendapatkan kesalahan saat menambahkan pengguna ke grup, pertama-tama kita dapat memeriksa apakah dia sudah ada di grup ini dengan menerima daftar anggota grup atau komposisi dari pengguna itu sendiri.

Permintaan untuk komposisi grup pengguna tertentu tidak akan bersifat rekursif dan hanya akan menunjukkan keanggotaan langsung. Dimasukkannya pengguna dalam grup induk, di mana grup tambahan di mana pengguna menjadi anggota, sudah berhasil.

Kesimpulan


Tetap mengirim kata sandi kepada pengguna untuk akun baru. Kami melakukan ini melalui SMS, dan mengirim informasi umum dengan instruksi dan login ke surat pribadi, yang, bersama dengan nomor telepon, disediakan oleh departemen pemilihan personel. Sebagai alternatif, Anda dapat menghemat uang dan mengirim kata sandi ke obrolan telegram rahasia, yang juga dapat dianggap sebagai faktor kedua (macbook akan menjadi pengecualian).

Terima kasih sudah membaca sampai akhir. Saya akan senang melihat saran untuk meningkatkan gaya penulisan artikel dan saya berharap Anda menangkap lebih sedikit kesalahan saat menulis skrip =)

Daftar tautan yang mungkin berguna secara tematis atau jawab saja pertanyaan Anda:

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


All Articles