Google lietotāju izveide no PowerShell, izmantojot API

Hi!

Šajā rakstā ir aprakstīta PowerShell mijiedarbības ieviešana ar Google API, lai manipulētu ar G Suite lietotājiem.

Mēs izmantojam vairākus iekšējos un mākoņpakalpojumus visā organizācijā. Lielākoties autorizācija tajos ir Google vai Active Directory, starp kurām mēs nevaram uzturēt kopiju; attiecīgi, jaunam darbiniekam aizejot, jums ir jāizveido/jāiespējo konts šajās divās sistēmās. Lai automatizētu procesu, mēs nolēmām uzrakstīt skriptu, kas apkopo informāciju un nosūta to abiem pakalpojumiem.

Atļauja

Izstrādājot prasības, nolēmām autorizācijai izmantot reālus cilvēku administratorus, kas atvieglo darbību analīzi nejaušu vai tīšu lielu izmaiņu gadījumā.

Google API izmanto OAuth 2.0 protokolu autentifikācijai un autorizācijai. Lietošanas gadījumi un sīkāki apraksti atrodami šeit: OAuth 2.0 izmantošana, lai piekļūtu Google API.

Es izvēlējos skriptu, kas tiek izmantots autorizācijai darbvirsmas lietojumprogrammās. Ir arī iespēja izmantot pakalpojuma kontu, kas neprasa lietotājam nevajadzīgas kustības.

Zemāk redzamajā attēlā ir shematisks Google lapas izvēlētā scenārija apraksts.

Google lietotāju izveide no PowerShell, izmantojot API

  1. Vispirms mēs nosūtām lietotāju uz Google konta autentifikācijas lapu, norādot GET parametrus:
    • pieteikuma id
    • apgabaliem, kuriem lietojumprogrammai ir jāpiekļūst
    • adrese, uz kuru lietotājs tiks novirzīts pēc procedūras pabeigšanas
    • veids, kā mēs atjaunināsim marķieri
    • Drošības kods
    • verifikācijas koda pārraides formāts

  2. Pēc autorizācijas pabeigšanas lietotājs tiks novirzīts uz pirmajā pieprasījumā norādīto lapu ar kļūdu vai autorizācijas kodu, ko nosūtījuši GET parametri.
  3. Lietojumprogrammai (skriptam) būs jāsaņem šie parametri un, ja tiks saņemts kods, jāiesniedz šāds pieprasījums, lai iegūtu marķierus
  4. Ja pieprasījums ir pareizs, Google API atgriež:
    • Piekļuves marķieris, ar kuru mēs varam veikt pieprasījumus
    • Šī marķiera derīguma termiņš
    • Lai atsvaidzinātu piekļuves pilnvaru, ir nepieciešama atsvaidzināšanas pilnvara.

Vispirms jums jādodas uz Google API konsoli: Akreditācijas dati — Google API konsole, atlasiet vajadzīgo lietojumprogrammu un sadaļā Akreditācijas dati izveidojiet klienta OAuth identifikatoru. Tur (vai vēlāk izveidotā identifikatora rekvizītos) jānorāda adreses, uz kurām ir atļauta novirzīšana. Mūsu gadījumā tie būs vairāki localhost ieraksti ar dažādiem portiem (skatiet tālāk).

Lai skripta algoritma lasīšana būtu ērtāka, pirmās darbības varat parādīt atsevišķā funkcijā, kas atgriezīs lietojumprogrammas piekļuves un atsvaidzināšanas pilnvaras:

$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

Mēs iestatām klienta ID un klienta noslēpumu, kas iegūts OAuth klienta identifikatora rekvizītos, un koda pārbaudītājs ir 43–128 rakstzīmju virkne, kas ir jāģenerē nejauši no nerezervētām rakstzīmēm: [AZ] / [az] / [0-9] / "-" / "." / "_" / "~".

Pēc tam šis kods tiks pārsūtīts vēlreiz. Tas novērš ievainojamību, kurā uzbrucējs var pārtvert atbildi, kas tiek atgriezta kā novirzīšana pēc lietotāja autorizācijas.
Pašreizējā pieprasījumā varat nosūtīt koda verifikatoru skaidrā tekstā (tas padara to bezjēdzīgu — tas ir piemērots tikai sistēmām, kas neatbalsta SHA256), vai izveidojot jaucēju, izmantojot SHA256 algoritmu, kam jābūt iekodētam BASE64Url (atšķiras no Base64 ar divām tabulas rakstzīmēm) un noņemot rakstzīmju rindu galotnes: =.

Pēc tam mums ir jāsāk klausīties http vietējā datorā, lai pēc autorizācijas saņemtu atbildi, kas tiks atgriezta kā novirzīšana.

Administratīvie uzdevumi tiek veikti uz speciāla servera, nevaram izslēgt iespēju, ka skriptu izpildīs vairāki administratori vienlaicīgi, tāpēc tas nejauši izvēlēsies portu pašreizējam lietotājam, bet es norādīju iepriekš definētus portus, jo tie ir jāpievieno kā uzticami arī API konsolē.

access_type=bezsaistē nozīmē, ka lietojumprogramma var atjaunināt marķieri, kuram beidzies derīguma termiņš, bez lietotāja mijiedarbības ar pārlūkprogrammu,
atbildes_tips=kods iestata formātu, kā kods tiks atgriezts (atsauce uz veco autorizācijas metodi, kad lietotājs kopēja kodu no pārlūkprogrammas skriptā),
joma norāda piekļuves apjomu un veidu. Tie ir jāatdala ar atstarpēm vai %20 (saskaņā ar URL kodējumu). Piekļuves apgabalu sarakstu ar veidiem var redzēt šeit: OAuth 2.0 darbības jomas Google API.

Pēc autorizācijas koda saņemšanas lietojumprogramma atgriezīs pārlūkprogrammas aizvēršanas ziņojumu, pārtrauks klausīšanos portā un nosūtīs POST pieprasījumu, lai iegūtu pilnvaru. Mēs tajā norādām iepriekš norādīto id un noslēpumu no konsoles API, adresi, uz kuru lietotājs tiks novirzīts, un grant_type saskaņā ar protokola specifikāciju.

Atbildot uz to, mēs saņemsim piekļuves pilnvaru, tās derīguma termiņu sekundēs un atsvaidzināšanas pilnvaru, ar kuru varēsim atjaunināt piekļuves pilnvaru.

Lietojumprogrammai ir jāglabā marķieri drošā vietā ar ilgu glabāšanas laiku, tāpēc, kamēr mēs neatcelsim saņemto piekļuvi, lietojumprogramma neatgriezīs atsvaidzināšanas pilnvaru. Beigās es pievienoju pieprasījumu atsaukt marķieri; ja lietojumprogramma netika veiksmīgi pabeigta un atsvaidzināšanas pilnvara netika atgriezta, tā atsāks procedūru (mēs uzskatījām, ka nav droši glabāt marķierus lokāli terminālī, un mēs to nedarām Nevēlos sarežģīt lietas ar kriptogrāfiju vai bieži atvērt pārlūkprogrammu).

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
}

Kā jūs jau pamanījāt, atsaucot pilnvaru, tiek izmantots Invoke-WebRequest. Atšķirībā no Invoke-RestMethod, tas neatgriež saņemtos datus lietojamā formātā un parāda pieprasījuma statusu.

Pēc tam skripts lūdz ievadīt lietotāja vārdu un uzvārdu, ģenerējot pieteikšanos + e-pastu.

pieprasījumi

Nākamie pieprasījumi būs - vispirms ir jāpārbauda, ​​vai lietotājs ar tādu pašu pieteikumvārdu jau nepastāv, lai pieņemtu lēmumu par jauna izveidošanu vai esošā iespējošanu.

Es nolēmu īstenot visus pieprasījumus vienas funkcijas formātā ar atlasi, izmantojot slēdzi:

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

Katrā pieprasījumā jums ir jānosūta autorizācijas galvene, kas satur pilnvaras veidu un pašu piekļuves pilnvaru. Pašlaik marķiera veids vienmēr ir Nesējs. Jo mums ir jāpārbauda, ​​vai marķiera derīguma termiņš nav beidzies, un jāatjaunina tas pēc stundas no tā izdošanas brīža, es norādīju pieprasījumu citai funkcijai, kas atgriež piekļuves pilnvaru. Tas pats koda fragments ir skripta sākumā, saņemot pirmo piekļuves pilnvaru:

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
}

Pieteikumvārda esamības pārbaude:

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
}

E-pasts:$query pieprasījums lūgs API meklēt lietotāju ar tieši šo e-pasta adresi, tostarp aizstājvārdus. Varat arī izmantot aizstājējzīmi: =, :, :{PREFIX}*.

Lai iegūtu datus, izmantojiet GET pieprasījuma metodi, datu ievietošanai (konta izveide vai dalībnieka pievienošana grupai) - POST, esošo datu atjaunināšana - PUT, ieraksta dzēšana (piemēram, dalībnieks no grupas) - DZĒST.

Skripts arī prasīs tālruņa numuru (neapstiprinātu virkni) un iekļaušanu reģionālajā izplatīšanas grupā. Tas izlemj, kura organizatoriskā vienība lietotājam ir jābūt, pamatojoties uz atlasīto Active Directory OU, un izstrādā paroli:

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"

Un tad viņš sāk manipulēt ar kontu:

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

Konta atjaunināšanas un izveides funkcijām ir līdzīga sintakse, visi papildu lauki nav obligāti, sadaļā ar tālruņu numuriem jānorāda masīvs, kurā var būt līdz vienam ierakstam ar numuru un tā veidu.

Lai, pievienojot lietotāju grupai, netiktu parādīta kļūda, vispirms varam pārbaudīt, vai viņš jau ir šīs grupas dalībnieks, iegūstot no paša lietotāja grupas dalībnieku sarakstu vai sastāvu.

Vaicājums par konkrēta lietotāja dalību grupā nebūs rekursīvs, un tiks rādīta tikai tieša dalība. Izdosies iekļaut lietotāju vecākgrupā, kurai jau ir pakārtota grupa, kuras dalībnieks lietotājs ir.

Secinājums

Atliek tikai nosūtīt lietotājam jaunā konta paroli. Mēs to darām, izmantojot SMS, un nosūtām vispārīgu informāciju ar instrukcijām un pieteikšanos uz personīgo e-pastu, kuru kopā ar tālruņa numuru sniedza personāla atlases nodaļa. Kā alternatīvu jūs varat ietaupīt naudu un nosūtīt savu paroli uz slepenu telegrammas čatu, ko arī var uzskatīt par otro faktoru (MacBooks būs izņēmums).

Paldies, ka izlasījāt līdz beigām. Priecāšos saņemt ieteikumus rakstu rakstīšanas stila uzlabošanai un novēlu, lai skriptu rakstīšanas laikā pieļautu mazāk kļūdu =)

Saišu saraksts, kas var būt tematiski noderīgas vai vienkārši atbildēt uz jautājumiem:

Avots: www.habr.com

Pievieno komentāru