Nyiptakeun Pamaké Google tina PowerShell via API

Salam!

Tulisan ieu bakal ngajelaskeun palaksanaan interaksi PowerShell sareng Google API pikeun ngamanipulasi pangguna G Suite.

Kami nganggo sababaraha jasa internal sareng awan di sakumna organisasi. Kanggo sabagéan ageung, otorisasina diturunkeun ka Google atanapi Active Directory, anu antara kami henteu tiasa ngajaga réplika; sasuai, nalika karyawan énggal angkat, anjeun kedah nyiptakeun / ngaktipkeun akun dina dua sistem ieu. Pikeun ngajadikeun otomatis prosésna, kami mutuskeun pikeun nyerat naskah anu ngumpulkeun inpormasi sareng dikirimkeun ka duanana jasa.

mieun kuwasa

Nalika ngadamel sarat, kami mutuskeun pikeun ngagunakeun pangurus manusa nyata pikeun otorisasina; ieu nyederhanakeun analisa tindakan upami aya parobahan anu teu kahaja atanapi dihaja.

API Google nganggo protokol OAuth 2.0 pikeun auténtikasi sareng otorisasina. Kasus pamakean sareng katerangan anu langkung rinci tiasa dipendakan di dieu: Ngagunakeun OAuth 2.0 pikeun Aksés Google API.

Kuring milih naskah anu dianggo pikeun otorisasi dina aplikasi desktop. Aya ogé pilihan pikeun nganggo akun jasa, anu henteu meryogikeun gerakan anu teu perlu ti pangguna.

Gambar di handap ieu mangrupikeun pedaran skéma tina skenario anu dipilih tina halaman Google.

Nyiptakeun Pamaké Google tina PowerShell via API

  1. Kahiji, urang ngirim pamaké ka kaca auténtikasi Akun Google, nangtukeun parameter GET:
    • id aplikasi
    • wewengkon nu aplikasi perlu aksés ka
    • alamat nu pamaké bakal dialihkeun sanggeus réngsé prosedur
    • cara urang ngamutahirkeun token
    • Kodeu kaamanan
    • format pangiriman kode verifikasi

  2. Saatos otorisasina réngsé, pangguna bakal dialihkeun ka halaman anu ditunjuk dina pamundut anu munggaran, kalayan kasalahan atanapi kode otorisasi anu disalurkeun ku parameter GET
  3. Aplikasi (naskah) kedah nampi parameter ieu sareng, upami nampi kodeu, ngadamel pamundut di handap ieu pikeun kéngingkeun token
  4. Upami pamundutna leres, Google API mulih:
    • Token aksés anu urang tiasa ngadamel pamundut
    • Mangsa validitas token ieu
    • Refresh token diperlukeun pikeun refresh token Aksés.

Mimiti anjeun kedah angkat ka konsol Google API: Kapercayaan - Konsol API Google, pilih aplikasi nu dipikahoyong tur dina bagian Kapercayaan nyieun identifier OAuth klien. Aya (atanapi engké, dina sipat identifier anu diciptakeun) anjeun kedah netepkeun alamat anu diidinan alihan. Dina kasus urang, ieu bakal sababaraha éntri localhost sareng palabuhan anu béda (tingali di handap).

Sangkan leuwih merenah maca algoritma skrip, Anjeun bisa mintonkeun léngkah munggaran dina fungsi misah nu bakal balik Aksés jeung refresh tokens pikeun aplikasi nu:

$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

Urang nyetel ID Klién jeung Rahasia Klién diala dina sipat identifier klien OAuth, sarta verifier kode mangrupakeun string 43 nepi ka 128 karakter nu kudu acak dihasilkeun tina karakter unreserved: [AZ] / [az] / [0-9] / "-" / "." / "_" / "~".

Kode ieu lajeng bakal dikirimkeun deui. Éta ngaleungitkeun kerentanan dimana panyerang tiasa ngahalangan réspon anu dipulangkeun salaku alihan saatos otorisasi pangguna.
Anjeun tiasa ngirim verifier kode dina pamundut ayeuna dina téks jelas (anu ngajadikeun eta euweuh hartina - ieu ngan cocog pikeun sistem nu teu ngarojong SHA256), atawa ku nyieun Hash ngagunakeun algoritma SHA256, nu kudu disandikeun dina BASE64Url (béda. ti Base64 ku dua karakter tabel) jeung nyoplokkeun tungtung garis karakter: =.

Salajengna, urang kedah ngawitan ngadangukeun http dina mesin lokal dina urutan pikeun nampa respon sanggeus otorisasina, nu bakal balik salaku alihan.

tugas Administratif dipigawé dina server husus, urang teu bisa maréntah kaluar kamungkinan yén sababaraha pangurus bakal ngajalankeun naskah dina waktos anu sareng, ku kituna sacara acak bakal milih port pikeun pamaké ayeuna, tapi kuring dieusian port tos disetel sabab. aranjeunna ogé kudu ditambahkeun salaku dipercaya dina konsol API.

access_type=offline hartosna yén aplikasi tiasa ngamutahirkeun token anu kadaluwarsa nyalira tanpa interaksi pangguna sareng browser,
response_type=kode Nyetél format kumaha kode bakal dipulangkeun (rujukan kana metode otorisasi lami, nalika pangguna nyalin kodeu tina browser kana naskah),
wengkuan nunjukkeun wengkuan jeung tipe aksés. Éta kudu dipisahkeun ku spasi atawa %20 (nurutkeun URL Encoding). Daptar daérah aksés sareng jinis tiasa ditingali di dieu: OAuth 2.0 Lingkup pikeun Google API.

Saatos nampi kode otorisasina, aplikasi bakal ngabalikeun pesen anu caket kana browser, ngeureunkeun ngadangukeun palabuhan sareng ngirim pamundut POST pikeun kéngingkeun token. Kami nunjukkeun di dinya id sareng rusiah anu disayogikeun sateuacana tina API konsol, alamat dimana pangguna bakal dialihkeun sareng grant_type saluyu sareng spésifikasi protokol.

Salaku réspon, kami bakal nampi token Aksés, jaman validitasna dina sababaraha detik, sareng token Refresh, dimana urang tiasa ngapdet token Aksés.

Aplikasi kedah nyimpen token dina tempat anu aman sareng umur rak anu panjang, janten dugi ka urang ngabatalkeun aksés anu ditampi, aplikasina moal ngabalikeun deui token refresh. Tungtungna, kuring nambihan pamundut pikeun ngabatalkeun token; upami aplikasina henteu suksés réngsé sareng token refresh henteu dipulangkeun, éta bakal ngamimitian prosedur deui (kami nganggap éta henteu aman pikeun nyimpen token sacara lokal di terminal, sareng kami henteu Abdi henteu hoyong ngahesekeun hal sareng kriptografi atanapi sering muka 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
}

Sakumaha anjeun parantos terang, nalika nyabut token, Invoke-WebRequest dianggo. Teu kawas Invoke-RestMethod, éta henteu ngabalikeun data anu ditampi dina format anu tiasa dianggo sareng nunjukkeun status pamundut.

Salajengna, naskah naroskeun anjeun ngalebetkeun nami pangguna sareng nami tukang, ngahasilkeun login + email.

Paménta

Paménta salajengna bakal - mimitina, anjeun kedah mariksa naha pangguna anu login anu sami parantos aya pikeun nyandak kaputusan ngeunaan nyiptakeun anu énggal atanapi ngaktifkeun anu ayeuna.

Kuring mutuskeun pikeun nerapkeun sadaya pamundut dina format hiji fungsi kalayan pilihan, nganggo saklar:

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'])
      }
    }
  }
}

Dina unggal pamundut, anjeun kedah ngirim lulugu Otorisasi anu ngandung jinis token sareng token Aksés sorangan. Ayeuna, jenis token sok Bearer. Sabab urang kudu pariksa yen token teu kadaluwarsa sarta ngamutahirkeun sanggeus sajam ti momen ieu dikaluarkeun, Kuring dieusian pamundut pikeun fungsi sejen nu balik hiji token Aksés. Potongan kode anu sami aya dina awal naskah nalika nampi token Aksés anu munggaran:

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
}

Mariksa login pikeun ayana:

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
}

Surélék:$query request bakal nanya ka API pikeun néangan pamaké kalawan persis email éta, kaasup aliases. Anjeun ogé tiasa nganggo wildcard: =, :, :{PREFIX}*.

Pikeun kéngingkeun data, paké metodeu pamundut GET, pikeun nyelapkeun data (nyieun akun atanapi nambihan anggota kana grup) - POST, pikeun ngapdet data anu aya - PUT, pikeun ngahapus rékaman (contona, anggota tina grup) - MUPUS.

Skrip ogé bakal ménta nomer telepon (hiji string unvalidated) jeung citakan dina grup distribusi régional. Éta mutuskeun unit organisasi mana anu kedah dianggo ku pangguna dumasar kana Active Directory OU anu dipilih sareng nganggo kecap konci:

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"

Lajeng anjeunna mimiti ngamanipulasi 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 = '[email protected]'
    email = $email
  }
  (GoogleQuery 'AddMember' $query) | fl
}

Fungsi pikeun ngapdet sareng nyiptakeun akun gaduh sintaksis anu sami; henteu sadayana widang tambahan diperyogikeun; dina bagian anu nganggo nomer telepon, anjeun kedah netepkeun susunan anu tiasa ngandung dugi ka hiji rékaman kalayan nomer sareng jinisna.

Pikeun henteu nampi kasalahan nalika nambihan pangguna kana grup, urang tiasa pariksa heula naha anjeunna parantos janten anggota grup ieu ku cara kéngingkeun daptar anggota grup atanapi komposisi ti pangguna nyalira.

Querying kaanggotaan grup tina pamaké husus moal rekursif sarta ngan bakal nembongkeun kaanggotaan langsung. Kaasup pamaké dina grup indungna nu geus boga grup anak nu pamaké éta anggota bakal suksés.

kacindekan

Sadaya anu tetep nyaéta ngirimkeun sandi ka pangguna pikeun akun énggal. Urang ngalakukeun ieu via SMS, sarta ngirimkeun informasi umum kalawan parentah jeung login ka email pribadi, nu, babarengan jeung nomer telepon, ieu disadiakeun ku departemen rekrutmen. Alternatipna, anjeun tiasa nyimpen artos sareng ngirim kecap konci anjeun ka obrolan telegram rusiah, anu ogé tiasa dianggap faktor kadua (MacBooks bakal janten pengecualian).

Hatur nuhun pikeun maca nepi ka ahir. Abdi bakal bungah ningali bongbolongan pikeun ningkatkeun gaya nyerat artikel sareng ngaharepkeun anjeun langkung seueur kasalahan nalika nyerat naskah =)

Daptar tautan anu tiasa mangpaat sacara tematik atanapi ngan saukur ngajawab patarosan:

sumber: www.habr.com

Tambahkeun komentar