Il-ħolqien ta' Utenti ta' Google minn PowerShell permezz tal-API

Hi!

Dan l-artikolu se jiddeskrivi l-implimentazzjoni tal-interazzjoni ta 'PowerShell mal-API ta' Google biex jimmanipula l-utenti tal-G Suite.

Aħna nużaw diversi servizzi interni u cloud madwar l-organizzazzjoni. Fil-biċċa l-kbira tagħhom, l-awtorizzazzjoni fihom taqa’ għal Google jew Active Directory, li bejniethom ma nistgħux inżommu replika; għalhekk, meta jitlaq impjegat ġdid, trid toħloq/tippermetti kont f’dawn iż-żewġ sistemi. Biex nawtomatizza l-proċess, iddeċidejna li niktbu skript li jiġbor informazzjoni u jibgħatha liż-żewġ servizzi.

Awtorizzazzjoni

Meta nfasslu r-rekwiżiti, iddeċidejna li nużaw amministraturi umani reali għall-awtorizzazzjoni; dan jissimplifika l-analiżi tal-azzjonijiet fil-każ ta 'bidliet massivi aċċidentali jew intenzjonati.

Google APIs jużaw il-protokoll OAuth 2.0 għall-awtentikazzjoni u l-awtorizzazzjoni. Każijiet ta' użu u deskrizzjonijiet aktar dettaljati jistgħu jinstabu hawn: L-użu ta' OAuth 2.0 biex taċċessa l-APIs ta' Google.

Għażilt l-iskrittura li tintuża għall-awtorizzazzjoni fl-applikazzjonijiet tad-desktop. Hemm ukoll għażla li tuża kont tas-servizz, li ma jeħtieġx movimenti bla bżonn mill-utent.

L-istampa hawn taħt hija deskrizzjoni skematika tax-xenarju magħżul mill-paġna Google.

Il-ħolqien ta' Utenti ta' Google minn PowerShell permezz tal-API

  1. L-ewwel, aħna nibagħtu lill-utent fil-paġna tal-awtentikazzjoni tal-Kont Google, billi nispeċifikaw il-parametri GET:
    • applikazzjoni id
    • oqsma li l-applikazzjoni teħtieġ aċċess għalihom
    • l-indirizz li lejh l-utent se jiġi ridirett wara li titlesta l-proċedura
    • il-mod kif se naġġornaw it-token
    • Kodiċi tas-Sigurtà
    • format tat-trażmissjoni tal-kodiċi tal-verifika

  2. Wara li titlesta l-awtorizzazzjoni, l-utent jiġi ridirett lejn il-paġna speċifikata fl-ewwel talba, bi żball jew kodiċi ta’ awtorizzazzjoni mgħoddi mill-parametri GET
  3. L-applikazzjoni (kitba) trid tirċievi dawn il-parametri u, jekk tirċievi l-kodiċi, tagħmel it-talba li ġejja biex tikseb tokens
  4. Jekk it-talba hija korretta, il-Google API jirritorna:
    • Token ta' aċċess li bih nistgħu nagħmlu talbiet
    • Il-perjodu ta' validità ta' dan it-token
    • Aġġorna token meħtieġ biex jġedded it-token tal-Aċċess.

L-ewwel trid tmur fil-console Google API: Kredenzjali - Google API Console, agħżel l-applikazzjoni mixtieqa u fit-taqsima Kredenzjali oħloq identifikatur OAuth tal-klijent. Hemm (jew aktar tard, fil-proprjetajiet tal-identifikatur maħluq) trid tispeċifika l-indirizzi li għalihom hija permessa direzzjoni mill-ġdid. Fil-każ tagħna, dawn se jkunu diversi entrati localhost b'portijiet differenti (ara hawn taħt).

Biex tagħmilha aktar konvenjenti li taqra l-algoritmu tal-iskrittura, tista 'turi l-ewwel passi f'funzjoni separata li tirritorna Aċċess u tokens ta' aġġornament għall-applikazzjoni:

$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

Aħna nissettjaw l-ID tal-Klijent u s-Sigriet tal-Klijent miksuba fil-proprjetajiet tal-identifikatur tal-klijent OAuth, u l-verifikatur tal-kodiċi huwa sensiela ta’ 43 sa 128 karattru li jridu jiġu ġġenerati b’mod każwali minn karattri mhux riżervati: [AZ] / [az] / [0-9 ] / "-" / "." / "_" / "~".

Dan il-kodiċi mbagħad jerġa' jiġi trażmess. Jelimina l-vulnerabbiltà li fiha attakkant jista' jinterċetta tweġiba rritornata bħala direzzjoni mill-ġdid wara l-awtorizzazzjoni tal-utent.
Tista 'tibgħat verifikatur tal-kodiċi fit-talba attwali f'test ċar (li jagħmilha bla sens - dan huwa adattat biss għal sistemi li ma jappoġġawx SHA256), jew billi toħloq hash bl-użu tal-algoritmu SHA256, li għandu jkun kodifikat f'BASE64Url (differenti minn Base64 b'żewġ karattri tat-tabella) u t-tneħħija tat-truf tal-linja tal-karattri: =.

Sussegwentement, irridu nibdew nisimgħu http fuq il-magna lokali sabiex nirċievu tweġiba wara l-awtorizzazzjoni, li tiġi rritornata bħala redirect.

Ħidmiet amministrattivi huma mwettqa fuq server speċjali, ma nistgħux neskludu l-possibbiltà li diversi amministraturi se jmexxu l-iskrittura fl-istess ħin, għalhekk se tagħżel b'mod każwali port għall-utent attwali, imma jien speċifikat portijiet predefiniti minħabba għandhom jiġu miżjuda wkoll bħala fdati fil-console API.

access_type=offline ifisser li l-applikazzjoni tista' taġġorna token skadut waħedha mingħajr interazzjoni tal-utent mal-browser,
response_type=kodiċi jistabbilixxi l-format ta 'kif il-kodiċi se jiġi rritornat (referenza għall-metodu ta' awtorizzazzjoni l-antik, meta l-utent ikkopja l-kodiċi mill-browser għall-iskript),
ambitu jindika l-ambitu u t-tip ta’ aċċess. Għandhom ikunu separati bi spazji jew %20 (skond l-Encoding tal-URL). Lista ta’ żoni ta’ aċċess bit-tipi tista’ tidher hawn: OAuth 2.0 Ambiti għal Google APIs.

Wara li tirċievi l-kodiċi ta 'awtorizzazzjoni, l-applikazzjoni se terġa' lura messaġġ mill-qrib lill-browser, tieqaf tisma 'fuq il-port u tibgħat talba POST biex tikseb it-token. Aħna nindikaw fiha l-id u sigriet speċifikati qabel mill-API tal-console, l-indirizz li għalih l-utent se jiġi ridirett u grant_type skont l-ispeċifikazzjoni tal-protokoll.

Bi tweġiba, se nirċievu token ta’ Aċċess, il-perjodu ta’ validità tiegħu f’sekondi, u token ta’ Aġġorna, li bih nistgħu naġġornaw it-token ta’ Aċċess.

L-applikazzjoni trid taħżen it-tokens f'post sigur b'ħajja twila fuq l-ixkaffa, għalhekk sakemm nirrevokaw l-aċċess riċevut, l-applikazzjoni ma tirritornax it-token tal-aġġornament. Fl-aħħar, żidt talba biex nirrevoka t-token; jekk l-applikazzjoni ma tlestietx b’suċċess u t-token ta’ aġġornament ma ġiex ritornat, se terġa’ tibda l-proċedura (konna nqisuha mhux sikur li nħażen it-tokens lokalment fuq it-terminal, u aħna don? ma tridx tikkomplika l-affarijiet bil-kriptografija jew tiftaħ il-browser ta 'spiss).

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
}

Kif diġà innotajt, meta tirrevoka token, tintuża Invoke-WebRequest. B'differenza mill-Invoke-RestMethod, ma jirritornax id-dejta riċevuta f'format użabbli u juri l-istatus tat-talba.

Sussegwentement, l-iskrittura titlobek biex iddaħħal l-isem u l-kunjom tal-utent, u tiġġenera login + email.

Talbiet

It-talbiet li jmiss se jkunu - l-ewwelnett, trid tiċċekkja jekk utent bl-istess login jeżistix diġà sabiex tikseb deċiżjoni dwar il-ħolqien ta 'wieħed ġdid jew l-abilitazzjoni ta' dik attwali.

Iddeċidejt li nimplimenta t-talbiet kollha fil-format ta' funzjoni waħda b'għażla, billi tuża swiċċ:

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

F'kull talba, trid tibgħat header tal-Awtorizzazzjoni li jkun fiha t-tip ta' token u t-token tal-Aċċess innifsu. Bħalissa, it-tip ta 'token huwa dejjem Bearer. Għax għandna bżonn niċċekkjaw li t-token ma skadiex u naġġornawh wara siegħa mill-mument li nħareġ, speċifikajt talba għal funzjoni oħra li tirritorna token ta 'Access. L-istess biċċa ta 'kodiċi tinsab fil-bidu tal-iskrittura meta tirċievi l-ewwel token ta' Aċċess:

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
}

Iċċekkja l-login għall-eżistenza:

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
}

L-email:$query talba se titlob lill-API biex tfittex utent b'dik l-email eżattament, inklużi psewdonimi. Tista' wkoll tuża wildcard: =, :, :{PREFISS}*.

Biex tikseb dejta, uża l-metodu ta 'talba GET, biex daħħal dejta (ħolqien ta' kont jew iżżid membru ma 'grupp) - POST, biex taġġorna dejta eżistenti - PUT, biex tħassar rekord (per eżempju, membru minn grupp) - Ħassar.

L-iskritt se jitlob ukoll numru tat-telefon (sekwenza mhux validata) u għall-inklużjoni fi grupp ta 'distribuzzjoni reġjonali. Jiddeċiedi liema unità organizzattiva l-utent għandu jkollu abbażi tal-OU Active Directory magħżula u toħroġ b'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"

U mbagħad jibda jimmanipula l-kont:

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

Il-funzjonijiet għall-aġġornament u l-ħolqien ta 'kont għandhom sintassi simili; mhux l-oqsma addizzjonali kollha huma meħtieġa; fit-taqsima bin-numri tat-telefon, għandek bżonn tispeċifika firxa li jista' jkun fiha sa rekord wieħed bin-numru u t-tip tiegħu.

Sabiex ma nirċievux żball meta nżidu utent ma’ grupp, l-ewwel nistgħu niċċekkjaw jekk hux diġà membru ta’ dan il-grupp billi niksbu lista ta’ membri tal-grupp jew kompożizzjoni mingħand l-utent innifsu.

L-istħarriġ dwar is-sħubija fil-grupp ta' utent speċifiku mhux se jkun rikorsiv u juri biss sħubija diretta. L-inklużjoni ta' utent fi grupp ġenitur li diġà għandu grupp tifel li l-utent huwa membru tiegħu jirnexxi.

Konklużjoni

Li jibqa' biss li tibgħat lill-utent il-password għall-kont il-ġdid. Nagħmlu dan permezz ta' SMS, u nibagħtu informazzjoni ġenerali bi struzzjonijiet u login f'email personali, li, flimkien ma' numru tat-telefon, ġiet ipprovduta mid-dipartiment tar-reklutaġġ. Bħala alternattiva, tista 'tiffranka l-flus u tibgħat il-password tiegħek għal chat telegramma sigrieta, li tista' titqies ukoll it-tieni fattur (MacBooks se tkun eċċezzjoni).

Grazzi talli qrajt sal-aħħar. Inkun ferħan nara suġġerimenti biex intejjeb l-istil tal-kitba ta' artikli u nixtieq li taqbad inqas żbalji meta tikteb skripts =)

Lista ta' links li jistgħu jkunu tematikament utli jew sempliċiment iwieġbu mistoqsijiet:

Sors: www.habr.com

Żid kumment