Paglikha ng Mga User ng Google mula sa PowerShell sa pamamagitan ng API

ΠŸΡ€ΠΈΠ²Π΅Ρ‚!

Ilalarawan ng artikulong ito ang pagpapatupad ng pakikipag-ugnayan ng PowerShell sa Google API upang manipulahin ang mga user ng G Suite.

Gumagamit kami ng ilang internal at cloud na serbisyo sa buong organisasyon. Para sa karamihan, ang pahintulot sa mga ito ay bumababa sa Google o Active Directory, kung saan hindi namin mapanatili ang isang replika; ayon dito, kapag umalis ang isang bagong empleyado, kailangan mong gumawa/mag-enable ng account sa dalawang system na ito. Upang i-automate ang proseso, nagpasya kaming magsulat ng script na nangongolekta ng impormasyon at ipinapadala ito sa parehong mga serbisyo.

Awtorisasyon

Kapag iginuhit ang mga kinakailangan, nagpasya kaming gumamit ng mga tunay na administrador ng tao para sa awtorisasyon; pinapasimple nito ang pagsusuri ng mga aksyon kung sakaling magkaroon ng hindi sinasadya o sadyang malalaking pagbabago.

Ginagamit ng mga Google API ang OAuth 2.0 protocol para sa pagpapatunay at pagpapahintulot. Ang mga kaso ng paggamit at mas detalyadong paglalarawan ay matatagpuan dito: Paggamit ng OAuth 2.0 upang I-access ang mga Google API.

Pinili ko ang script na ginagamit para sa awtorisasyon sa mga desktop application. Mayroon ding opsyon na gumamit ng service account, na hindi nangangailangan ng mga hindi kinakailangang paggalaw mula sa user.

Ang larawan sa ibaba ay isang eskematiko na paglalarawan ng napiling senaryo mula sa pahina ng Google.

Paglikha ng Mga User ng Google mula sa PowerShell sa pamamagitan ng API

  1. Una, ipinapadala namin ang user sa pahina ng pagpapatunay ng Google Account, na tumutukoy sa mga parameter ng GET:
    • application id
    • mga lugar kung saan nangangailangan ng access ang application
    • ang address kung saan ire-redirect ang user pagkatapos makumpleto ang pamamaraan
    • ang paraan ng pag-update namin ng token
    • Security Code
    • format ng paghahatid ng verification code

  2. Pagkatapos makumpleto ang awtorisasyon, ire-redirect ang user sa page na tinukoy sa unang kahilingan, na may error o authorization code na ipinasa ng mga parameter ng GET
  3. Ang application (script) ay kailangang makatanggap ng mga parameter na ito at, kung natanggap ang code, gawin ang sumusunod na kahilingan upang makakuha ng mga token
  4. Kung tama ang kahilingan, ibabalik ng Google API ang:
    • Access token kung saan maaari kaming gumawa ng mga kahilingan
    • Ang panahon ng bisa ng token na ito
    • Kailangan ng refresh token para i-refresh ang Access token.

Una kailangan mong pumunta sa Google API console: Mga Kredensyal - Google API Console, piliin ang nais na application at sa seksyong Mga Kredensyal lumikha ng isang client OAuth identifier. Doon (o mas bago, sa mga katangian ng nilikha na identifier) ​​kailangan mong tukuyin ang mga address kung saan pinapayagan ang pag-redirect. Sa aming kaso, ito ay magiging ilang mga localhost na entry na may iba't ibang port (tingnan sa ibaba).

Upang gawing mas maginhawang basahin ang script algorithm, maaari mong ipakita ang mga unang hakbang sa isang hiwalay na function na magbabalik ng Access at pag-refresh ng mga token para sa application:

$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

Itinakda namin ang Client ID at Client Secret na nakuha sa mga property ng OAuth client identifier, at ang code verifier ay isang string ng 43 hanggang 128 character na dapat random na nabuo mula sa mga hindi nakareserbang character: [AZ] / [az] / [0-9 ] / "-" / "." / "_" / "~".

Ang code na ito ay ipapadala muli. Inaalis nito ang kahinaan kung saan maaaring harangin ng isang umaatake ang isang tugon na ibinalik bilang pag-redirect pagkatapos ng pahintulot ng user.
Maaari kang magpadala ng code verifier sa kasalukuyang kahilingan sa malinaw na text (na ginagawang walang kabuluhan - ito ay angkop lamang para sa mga system na hindi sumusuporta sa SHA256), o sa pamamagitan ng paggawa ng hash gamit ang SHA256 algorithm, na dapat na naka-encode sa BASE64Url (naiiba mula sa Base64 ng dalawang character ng talahanayan) at inaalis ang mga dulo ng linya ng character: =.

Susunod, kailangan nating simulan ang pakikinig sa http sa lokal na makina upang makatanggap ng tugon pagkatapos ng pahintulot, na ibabalik bilang isang pag-redirect.

Ang mga gawaing pang-administratibo ay ginagawa sa isang espesyal na server, hindi namin mabubukod ang posibilidad na maraming mga administrator ang magpapatakbo ng script nang sabay-sabay, kaya random itong pipili ng port para sa kasalukuyang user, ngunit tinukoy ko ang mga paunang natukoy na port dahil dapat ding idagdag ang mga ito bilang pinagkakatiwalaan sa API console.

access_type=offline nangangahulugan na ang application ay maaaring mag-update ng isang nag-expire na token sa sarili nitong walang pakikipag-ugnayan ng user sa browser,
response_type=code nagtatakda ng format kung paano ibabalik ang code (isang reference sa lumang paraan ng awtorisasyon, kapag kinopya ng user ang code mula sa browser papunta sa script),
saklaw ay nagpapahiwatig ng saklaw at uri ng pag-access. Dapat silang paghiwalayin ng mga puwang o %20 (ayon sa URL Encoding). Ang isang listahan ng mga access area na may mga uri ay makikita dito: Mga Saklaw ng OAuth 2.0 para sa mga Google API.

Pagkatapos matanggap ang authorization code, magbabalik ang application ng malapit na mensahe sa browser, hihinto sa pakikinig sa port at magpadala ng POST request para makuha ang token. Isinasaad namin dito ang dating tinukoy na id at sikreto mula sa console API, ang address kung saan ire-redirect ang user at grant_type alinsunod sa detalye ng protocol.

Bilang tugon, makakatanggap kami ng Access token, validity period nito sa loob ng ilang segundo, at Refresh token, kung saan maaari naming i-update ang Access token.

Ang application ay dapat mag-imbak ng mga token sa isang ligtas na lugar na may mahabang buhay ng istante, kaya hanggang sa bawiin namin ang natanggap na access, hindi ibabalik ng application ang refresh token. Sa huli, nagdagdag ako ng kahilingan na bawiin ang token; kung hindi matagumpay na nakumpleto ang aplikasyon at hindi naibalik ang refresh token, sisimulan nitong muli ang pamamaraan (itinuring namin na hindi ligtas na mag-imbak ng mga token nang lokal sa terminal, at hindi namin Hindi nais na gawing kumplikado ang mga bagay sa cryptography o buksan ang browser nang madalas).

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
}

Tulad ng napansin mo na, kapag binawi ang isang token, ginagamit ang Invoke-WebRequest. Hindi tulad ng Invoke-RestMethod, hindi nito ibinabalik ang natanggap na data sa isang magagamit na format at ipinapakita ang status ng kahilingan.

Susunod, hinihiling sa iyo ng script na ipasok ang pangalan at apelyido ng user, na bumubuo ng login + email.

kahilingan

Ang mga susunod na kahilingan ay - una sa lahat, kailangan mong suriin kung ang isang user na may parehong login ay mayroon na upang makakuha ng desisyon sa paggawa ng bago o paganahin ang kasalukuyan.

Nagpasya akong ipatupad ang lahat ng mga kahilingan sa format ng isang function na may isang pagpipilian, gamit ang switch:

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

Sa bawat kahilingan, kailangan mong magpadala ng Authorization header na naglalaman ng uri ng token at ang Access token mismo. Sa kasalukuyan, ang uri ng token ay palaging Tagadala. kasi kailangan nating suriin na ang token ay hindi pa nag-expire at i-update ito pagkatapos ng isang oras mula sa sandaling ito ay inisyu, tinukoy ko ang isang kahilingan para sa isa pang function na nagbabalik ng isang Access token. Ang parehong piraso ng code ay nasa simula ng script kapag natanggap ang unang Access token:

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
}

Sinusuri ang pag-login para sa pagkakaroon:

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
}

Hihilingin ng email:$query request ang API na maghanap ng user na may eksaktong email na iyon, kasama ang mga alias. Maaari mo ring gamitin ang wildcard: =, :, :{PREFIX}*.

Para makakuha ng data, gamitin ang GET request method, para magsingit ng data (paggawa ng account o pagdaragdag ng miyembro sa isang grupo) - POST, para i-update ang umiiral na data - PUT, para tanggalin ang isang record (halimbawa, isang miyembro mula sa isang grupo) - I-DELETE.

Hihilingin din ng script ang isang numero ng telepono (isang hindi wastong string) at para sa pagsasama sa isang pangkat ng pamamahagi ng rehiyon. Ito ang magpapasya kung aling unit ng organisasyon ang dapat magkaroon ng user batay sa napiling Active Directory OU at may kasamang password:

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"

At pagkatapos ay sinimulan niyang manipulahin ang account:

$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
}

Ang mga function para sa pag-update at paglikha ng isang account ay may katulad na syntax; hindi lahat ng karagdagang mga patlang ay kinakailangan; sa seksyon na may mga numero ng telepono, kailangan mong tukuyin ang isang array na maaaring maglaman ng hanggang sa isang talaan na may numero at uri nito.

Upang hindi makatanggap ng error kapag nagdadagdag ng user sa isang grupo, maaari muna nating suriin kung miyembro na siya ng grupong ito sa pamamagitan ng pagkuha ng listahan ng mga miyembro ng grupo o komposisyon mula sa user mismo.

Ang pagtatanong sa membership ng grupo ng isang partikular na user ay hindi magiging recursive at direktang membership lang ang ipapakita. Magtatagumpay ang pagsasama ng user sa isang parent group na mayroon nang child group kung saan miyembro ang user.

Konklusyon

Ang natitira na lang ay ipadala sa user ang password para sa bagong account. Ginagawa namin ito sa pamamagitan ng SMS, at nagpapadala ng pangkalahatang impormasyon na may mga tagubilin at pag-login sa isang personal na email, na, kasama ang isang numero ng telepono, ay ibinigay ng departamento ng pangangalap. Bilang isang kahalili, maaari kang makatipid ng pera at ipadala ang iyong password sa isang lihim na telegrama na chat, na maaari ding ituring na pangalawang kadahilanan (MacBooks ay magiging isang pagbubukod).

Salamat sa pagbabasa hanggang dulo. Natutuwa akong makakita ng mga mungkahi para sa pagpapabuti ng istilo ng pagsusulat ng mga artikulo at hilingin kong mas kaunting error ang mahuli mo kapag nagsusulat ng mga script =)

Listahan ng mga link na maaaring maging kapaki-pakinabang ayon sa tema o simpleng sagot sa mga tanong:

Pinagmulan: www.habr.com

Magdagdag ng komento