PowerShell-etik Google erabiltzaileak sortzea API bidez

Hi!

Artikulu honetan PowerShell-ek G Suite erabiltzaileak manipulatzeko Google APIarekin duen interakzioa ezartzea deskribatuko du.

Erakunde osoan barneko eta hodeiko hainbat zerbitzu erabiltzen ditugu. Gehienetan, horien baimena Google-ri edo Active Directoryri dagokio, eta horien artean ezin dugu erreplikarik mantendu; horregatik, langile berri bat uzten denean, kontu bat sortu/gaitu behar duzu bi sistema hauetan. Prozesua automatizatzeko, informazioa bildu eta bi zerbitzuetara bidaltzen duen script bat idaztea erabaki dugu.

baimen

Baldintzak egiterakoan, benetako giza administratzaileak erabiltzea erabaki dugu baimena lortzeko; horrek ekintzen azterketa errazten du ustekabeko edo nahita aldaketa masiboak gertatuz gero.

Google APIek OAuth 2.0 protokoloa erabiltzen dute autentifikaziorako eta baimenerako. Erabilera kasuak eta deskribapen zehatzagoak hemen aurki daitezke: OAuth 2.0 erabiltzea Google APIetara sartzeko.

Mahaigaineko aplikazioetan baimentzeko erabiltzen den script-a aukeratu dut. Zerbitzu-kontu bat erabiltzeko aukera ere badago, erabiltzailearen alferrikako mugimendurik behar ez duena.

Beheko irudia Google orrialdean hautatutako eszenatokiaren deskribapen eskematiko bat da.

PowerShell-etik Google erabiltzaileak sortzea API bidez

  1. Lehenik eta behin, erabiltzailea Google kontuaren autentifikazio orrira bidaltzen dugu, GET parametroak zehaztuz:
    • aplikazioaren id
    • aplikazioak atzitu behar dituen eremuak
    • prozedura amaitu ondoren erabiltzailea birbideratuko den helbidea
    • tokena eguneratuko dugun modua
    • Segurtasun kodea
    • egiaztapen-kodearen transmisio-formatua

  2. Baimena amaitu ondoren, erabiltzailea lehen eskaeran zehaztutako orrira birbideratuko da, GET parametroek errore edo baimen-kode batekin.
  3. Aplikazioak (gidoia) parametro hauek jaso beharko ditu eta, kodea jasoz gero, honako eskaera egin beharko du tokenak lortzeko
  4. Eskaera zuzena bada, Google APIa itzultzen da:
    • Sarbide-tokenarekin eskaerak egin ditzakegu
    • Token honen balio-epea
    • Freskatu tokena behar da Access tokena freskatzeko.

Lehenik eta behin, Google API kontsolara joan behar duzu: Kredentzialak - Google API kontsola, hautatu nahi duzun aplikazioa eta Kredentzialak atalean sortu bezero OAuth identifikatzaile bat. Bertan (edo geroago, sortutako identifikatzailearen propietateetan) birbideratzea onartzen den helbideak zehaztu behar dituzu. Gure kasuan, portu ezberdineko lokalhost sarrera batzuk izango dira (ikus behean).

Script-algoritmoa irakurtzea erosoagoa izan dadin, aplikaziorako sarbidea eta freskatu tokenak itzuliko dituen funtzio bereizi batean bistaratu ditzakezu lehen urratsak:

$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

OAuth bezeroaren identifikatzailearen propietateetan lortutako Bezero IDa eta Bezeroaren sekretua ezartzen ditugu, eta kode egiaztatzailea 43 eta 128 karaktere arteko kate bat da, erreserbarik gabeko karaktereetatik ausaz sortu behar dena: [AZ] / [az] / [0-9 ] / "-" / "." / "_" / "~".

Kode hau berriro transmitituko da. Erasotzaileak baimendu ondoren birbideratze gisa itzultzen den erantzuna atzemateko duen ahultasuna ezabatzen du.
Uneko eskaeran kode-egiaztapena testu argian bidal dezakezu (horrek zentzurik gabekoa da - SHA256 onartzen ez duten sistemetarako bakarrik da egokia), edo SHA256 algoritmoa erabiliz hash bat sortuz, BASE64Url-en kodetu behar dena (desberdina). Base64-tik taulako bi karaktereekin) eta karaktere lerroaren amaierak kenduz: =.

Ondoren, tokiko makinan http entzuten hasi behar dugu baimenaren ondoren erantzun bat jasotzeko, birbideratze gisa itzuliko dena.

Administrazio zereginak zerbitzari berezi batean egiten dira, ezin dugu baztertu hainbat administratzailek gidoia aldi berean exekutatzeko aukera, beraz, ausaz hautatuko du uneko erabiltzailearentzat ataka bat, baina aurrez definitutako atakak zehaztu ditut, fidagarri gisa ere gehitu behar dira API kontsolan.

access_type=lineaz kanpo esan nahi du aplikazioak iraungitako token bat eguneratu dezakeela bere kabuz, erabiltzaileak arakatzailearekin interakziorik izan gabe,
erantzun_mota=kodea kodea nola itzuliko den formatua ezartzen du (baimen-metodo zaharraren erreferentzia, erabiltzaileak kodea arakatzailetik script-era kopiatu zuenean),
esparrua sarbide-esparrua eta mota adierazten ditu. Zuriunez edo %20z bereizi behar dira (URL kodeketaren arabera). Motak dituzten sarbide-eremuen zerrenda hemen ikus daiteke: Google APIetarako OAuth 2.0 esparruak.

Baimen-kodea jaso ondoren, aplikazioak itxiko mezu bat itzuliko dio arakatzaileari, portuan entzuteari utziko dio eta tokena lortzeko POST eskaera bidaliko du. Bertan adieraziko dugu aurretik zehaztutako id eta sekretua kontsolaren APItik, erabiltzailea birbideratuko den helbidea eta grant_type protokoloaren zehaztapenaren arabera.

Horren harira, Access token bat jasoko dugu, bere balio-epea segundotan eta Fresh token bat, zeinekin Access token eguneratu ahal izateko.

Aplikazioak tokenak gorde behar ditu iraupen luzeko leku seguru batean, beraz, jasotako sarbidea ezeztatu arte, aplikazioak ez du freskatze tokena itzuliko. Amaieran, tokena baliogabetzeko eskaera bat gehitu dut; aplikazioa ez bada behar bezala osatu eta freskatzeko tokena itzuli ez bada, prozedura berriro hasiko da (ezin segurutzat jo dugu tokenak terminalean lokalean gordetzea, eta ez dugu ez da kriptografiarekin gauzak zaildu nahi edo arakatzailea maiz ireki).

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
}

Dagoeneko ohartu zaren bezala, token bat baliogabetzean, Invoke-WebRequest erabiltzen da. Invoke-RestMethod ez bezala, ez ditu jasotako datuak formatu erabilgarri batean itzultzen eta eskaeraren egoera erakusten du.

Ondoren, scriptak erabiltzailearen izena eta abizena sartzeko eskatzen dizu, saioa hasteko + posta elektronikoa sortuz.

Eskaerak

Hurrengo eskaerak izango dira: lehenik eta behin, egiaztatu behar duzu saio-hasiera bera duen erabiltzailerik dagoen ala ez, berri bat sortzeko edo oraingoa gaitzeko erabakia hartzeko.

Eskaera guztiak aukeraketa batekin funtzio baten formatuan ezartzea erabaki nuen, etengailua erabiliz:

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

Eskaera bakoitzean, Token mota eta Access token bera dituen Baimen goiburu bat bidali behar duzu. Gaur egun, token mota beti Bearer da. Zeren tokena iraungi ez dela egiaztatu eta ordubete igaro ondoren eguneratu behar dugu jaulki zenetik, Access token bat itzultzen duen beste funtzio baterako eskaera bat zehaztu nuen. Kode zati bera scriptaren hasieran dago lehen Access tokena jasotzean:

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
}

Saioaren existentzia egiaztatzea:

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
}

Email:$query eskaerak APIari eskatuko dio posta elektroniko hori duen erabiltzaile bat bilatzeko, aliasak barne. Komodinak ere erabil ditzakezu: =, :, :{AURREFIXO}*.

Datuak lortzeko, erabili GET eskaera metodoa, datuak txertatzeko (kontu bat sortu edo talde batean kide bat gehitzeko) - POST, dauden datuak eguneratzeko - PUT, erregistro bat ezabatzeko (adibidez, talde bateko kide bat) - EZABATU.

Gidoiak telefono-zenbaki bat ere eskatuko du (baliogabeko kate bat) eta eskualdeko banaketa talde batean sartzeko. Erabiltzaileak zein antolakuntza-unitate izan behar duen erabakitzen du hautatutako Active Directory OU-n oinarrituta eta pasahitz bat ateratzen du:

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"

Eta orduan kontua manipulatzen hasten da:

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

Kontua eguneratzeko eta sortzeko funtzioek antzeko sintaxia dute; eremu gehigarri guztiak ez dira beharrezkoak; telefono-zenbakien atalean, erregistro bat gehienez eduki dezakeen matrize bat zehaztu behar duzu zenbakia eta bere motarekin.

Erabiltzaile bat talde batean gehitzean errorerik jaso ez dadin, lehenik eta behin talde horretako kide den ala ez egiazta dezakegu, taldekideen zerrenda edo konposizioa erabiltzaileak berak lortuz.

Erabiltzaile zehatz baten talde-kidetasuna kontsultatzea ez da errekurtsiboa izango eta kide zuzena soilik erakutsiko du. Erabiltzailea kide den talde seme-alaba duen talde nagusi batean sartzeak arrakasta izango du.

Ondorioa

Kontu berriaren pasahitza erabiltzaileari bidaltzea besterik ez da geratzen. SMS bidez egiten dugu, eta informazio orokorra bidaltzen dugu argibideekin eta saioa hasteko posta elektroniko pertsonal batera, zeina, telefono zenbaki batekin batera, kontratazio sailak eman zuena. Alternatiba gisa, dirua aurreztu eta pasahitza telegram txat sekretu batera bidali dezakezu, bigarren faktoretzat har daitekeena ere (MacBooks salbuespena izango da).

Eskerrik asko bukaeraraino irakurtzeagatik. Pozik egongo naiz artikuluak idazteko estiloa hobetzeko iradokizunak ikusteaz eta gidoiak idaztean errore gutxiago harrapatzea nahi dut =)

Gaika erabilgarriak izan daitezkeen esteken zerrenda edo galderak besterik gabe erantzuten dituztenak:

Iturria: www.habr.com

Gehitu iruzkin berria