Creazione di Google Users da PowerShell via API

Hello!

Questu articulu descriverà l'implementazione di l'interazzione PowerShell cù l'API di Google per manipulà l'utilizatori di G Suite.

Utilizemu parechji servizii interni è cloud in tutta l'urganizazione. Per a maiò parte, l'autorizazione in elli vene à Google o Active Directory, trà quale ùn pudemu micca mantene una replica; per quessa, quandu un novu impiegatu parte, avete bisognu di creà / attivà un contu in questi dui sistemi. Per automatizà u prucessu, avemu decisu di scrive un script chì recullà l'infurmazioni è l'invia à i dui servizii.

Accedi

Quandu si stabiliscenu i requisiti, avemu decisu di utilizà veri amministratori umani per l'autorizazione; questu simplifica l'analisi di l'azzioni in casu di cambiamenti massivi accidentali o intenzionali.

L'API di Google utilizanu u protocolu OAuth 2.0 per l'autentificazione è l'autorizazione. I casi d'usu è e descrizzioni più dettagliate ponu esse truvati quì: Utilizà OAuth 2.0 per accede à l'API di Google.

Aghju sceltu u script chì hè utilizatu per l'autorizazione in l'applicazioni desktop. Ci hè ancu una opzione per utilizà un contu di serviziu, chì ùn hà micca bisognu di movimenti innecessarii da l'utilizatore.

A stampa sottu hè una descrizzione schematica di u scenariu sceltu da a pagina di Google.

Creazione di Google Users da PowerShell via API

  1. Prima, mandemu l'utilizatore à a pagina di autentificazione di u Account Google, specificendu i parametri GET:
    • ID di l'applicazione
    • e aree chì l'applicazione hà bisognu di accessu
    • l'indirizzu à quale l'utilizatore serà redirettuatu dopu à compie a prucedura
    • u modu avemu da aghjurnà u token
    • Codice di sicurezza
    • formatu di trasmissione di codice di verificazione

  2. Dopu chì l'autorizazione hè cumpleta, l'utilizatore serà reindirizzatu à a pagina specificata in a prima dumanda, cù un errore o codice d'autorizazione passatu da i paràmetri GET
  3. L'applicazione (script) duverà riceve sti paràmetri è, se ricevutu u codice, fate a seguente dumanda per ottene tokens
  4. Se a dumanda hè curretta, l'API di Google torna:
    • Token d'accessu cù quale pudemu fà richieste
    • U periodu di validità di stu token
    • Refresh token necessariu per rinfriscà u token Access.

Prima ci vole à andà à a cunsola API di Google: Credenziali - Google API Console, selezziunate l'applicazione desiderata è in a sezione Credenziali creanu un identificatore OAuth di cliente. Ci (o più tardi, in e proprietà di l'identificatore creatu) ​​tu bisognu di specificà l'indirizzi à quale hè permessa a redirezzione. In u nostru casu, questi seranu parechje entrate di l'host locale cù diversi porti (vede quì sottu).

Per fà più còmuda di leghje l'algoritmu di scrittura, pudete vede i primi passi in una funzione separata chì restituverà l'accessu è rinfriscà i tokens per l'applicazione:

$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

Avemu stabilitu l'ID di u Cliente è u Sicretu di Cliente ottenuti in e proprietà di identificatore di u cliente OAuth, è u verificatore di codice hè una stringa di 43 à 128 caratteri chì deve esse generata aleatoriamente da caratteri senza riservazione: [AZ] / [az] / [0-9] / "-" / "". / "_" / "~".

Stu codice sarà tandu trasmessu di novu. Elimina a vulnerabilità in quale un attaccu puderia interceptà una risposta tornata cum'è redirezzione dopu l'autorizazione di l'utilizatori.
Pudete mandà un verificatore di codice in a dumanda attuale in u testu chjaru (chì rende senza significatu - questu hè adattatu solu per i sistemi chì ùn supportanu micca SHA256), o creendu un hash cù l'algoritmu SHA256, chì deve esse codificatu in BASE64Url (differenti). da Base64 da dui caratteri di tabella) è sguassate e terminazioni di a linea di caratteri: =.

In seguitu, avemu bisognu di cumincià à sente http nantu à a macchina lucale per riceve una risposta dopu l'autorizazione, chì serà tornata cum'è redirect.

I travaglii amministrativi sò realizati nantu à un servitore speciale, ùn pudemu micca escludiri a pussibilità chì parechji amministratori eseguiranu u script à u stessu tempu, cusì selezziunà aleatoriamente un portu per l'utilizatore attuale, ma aghju specificatu porti predefiniti perchè anu ancu esse aghjuntu cum'è fiducia in a cunsola API.

access_type=offline significa chì l'applicazione pò aghjurnà un token scadutu da sè senza interazzione di l'utilizatori cù u navigatore,
response_type = codice stabilisce u formatu di cumu u codice serà tornatu (una riferenza à u vechju metudu d'autorizazione, quandu l'utilizatore hà copiatu u codice da u navigatore in u script),
scopre indica u scopu è u tipu di accessu. Deve esse separati da spazii o % 20 (sicondu l'URL Encoding). Una lista di e zone d'accessu cù tipi pò esse vistu quì: Scopes OAuth 2.0 per l'API di Google.

Dopu avè ricivutu u codice d'autorizazione, l'applicazione rinviarà un missaghju vicinu à u navigatore, cessà d'ascoltà u portu è mandà una dumanda POST per ottene u token. Indichemu in questu l'identificatore è u sicretu precedentemente specificatu da l'API di a cunsola, l'indirizzu à quale l'utilizatore serà redirezzione è grant_type in cunfurmità cù a specificazione di u protocolu.

In risposta, riceveremu un token Access, u so periodu di validità in seconde, è un token Refresh, cù quale pudemu aghjurnà u token Access.

L'applicazione deve almacenà i tokens in un locu sicuru cù una longa vita di scaffale, cusì finu à chì revochemu l'accessu ricevutu, l'applicazione ùn restituverà micca u token di rinfrescante. À a fine, aghju aghjustatu una dumanda per revocà u token; se l'applicazione ùn hè micca stata cumpleta cù successu è u token di rinfrescante ùn hè micca statu restituitu, hà da principià a prucedura di novu (avemu cunsideratu micca sicuru per almacenà tokens localmente in u terminal, è ùn avemu micca Ùn vogliu micca cumplicà e cose cù a criptografia o apre u navigatore spessu).

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
}

Cum'è avete digià nutatu, quandu si revoca un token, Invoke-WebRequest hè utilizatu. A cuntrariu di Invoke-RestMethod, ùn torna micca i dati ricevuti in un formatu utilizable è mostra u statutu di a dumanda.

In seguitu, u script vi dumanda di inserisce u nome è u cognome di l'utilizatore, generendu un login + email.

E dumande

E prossime richieste seranu - prima di tuttu, avete bisognu di verificà s'ellu esiste un utilizatore cù u stessu login per ottene una decisione per creà un novu o per attivà l'attuale.

Aghju decisu di implementà tutte e dumande in u formatu di una funzione cù una selezzione, usendu 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'])
      }
    }
  }
}

In ogni dumanda, avete bisognu di mandà un capu d'Autorizazione chì cuntene u tipu di token è u token Access stessu. Attualmente, u tipu di token hè sempre Bearer. Perchè avemu bisognu di verificà chì u token ùn hè micca scadutu è aghjurnà dopu à una ora da u mumentu chì hè statu emessu, aghju specificatu una dumanda per una altra funzione chì torna un token Access. U listessu pezzu di codice hè à u principiu di u script quandu riceve u primu token di Access:

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
}

Verificate u login per l'esistenza:

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
}

A dumanda email:$query dumandà à l'API di circà un utilizatore cù esattamente quellu email, cumpresi alias. Pudete ancu aduprà wildcard: =, :, :{PREFIX}*.

Per uttene dati, utilizate u metudu di dumanda GET, per inserisce dati (creendu un contu o aghjunghje un membru à un gruppu) - POST, per aghjurnà e dati esistenti - PUT, per sguassà un record (per esempiu, un membru da un gruppu) - ELIMINA.

U script hà ancu dumandà un numeru di telefunu (una stringa micca validata) è l'inclusione in un gruppu di distribuzione regiunale. Decide quale unità organizzativa deve avè l'utilizatore basatu annantu à l'OU Active Directory sceltu è vene cun una 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"

È dopu principia à manipulà u contu:

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

E funzioni per l'aghjurnà è a creazione di un contu anu una sintassi simile; micca tutti i campi supplementari sò richiesti; in a sezione cù numeri di telefunu, avete bisognu di specificà un array chì pò cuntene finu à un registru cù u numeru è u so tipu.

Per ùn riceve un errore quandu aghjunghje un utilizatore à un gruppu, pudemu prima verificà s'ellu hè digià un membru di stu gruppu ottenendu una lista di membri di u gruppu o cumpusizioni da l'utilizatore stessu.

L'interrogazione di l'appartenenza à u gruppu di un utilizatore specificu ùn serà micca recursiva è mostrarà solu l'appartenenza diretta. Includendu un utilizatore in un gruppu parentale chì hà digià un gruppu zitellu chì l'utilizatore hè un membru di successu.

cunchiusioni

Tuttu ciò chì resta hè di mandà à l'utilizatore a password per u novu contu. Facemu questu via SMS, è mandà infurmazione generale cù struzzioni è login à un email persunale, chì, cù un numeru di telefunu, hè statu furnitu da u dipartimentu di reclutamentu. Comu alternativa, pudete risparmià soldi è mandà a vostra password à un chat telegram secretu, chì pò ancu esse cunsideratu u sicondu fattore (MacBooks serà una eccezzioni).

Grazie per leghje finu à a fine. Seraghju cuntentu di vede suggerimenti per migliurà u stilu di scrittura d'articuli è vulete catturà menu errori quandu scrivite script =)

Lista di ligami chì ponu esse tematichi utili o solu risponde à e dumande:

Source: www.habr.com

Add a comment