„Google“ naudotojų kūrimas iš „PowerShell“ per API

Sveiki!

Šiame straipsnyje aprašomas „PowerShell“ sąveikos su „Google“ API diegimas, siekiant manipuliuoti „G Suite“ naudotojais.

Visoje organizacijoje naudojame kelias vidines ir debesies paslaugas. Dažniausiai jose suteikiamas įgaliojimas „Google“ arba „Active Directory“, tarp kurių negalime išlaikyti kopijos; atitinkamai, naujam darbuotojui išėjus, reikia susikurti/įgalinti paskyrą šiose dviejose sistemose. Norėdami automatizuoti procesą, nusprendėme parašyti scenarijų, kuris surenka informaciją ir siunčia ją abiem tarnyboms.

Leidimas

Rengdami reikalavimus, autorizavimui nusprendėme naudoti tikrus administratorius, kurie supaprastina veiksmų analizę atsitiktinių ar tyčinių masinių pakeitimų atveju.

„Google“ API autentifikavimui ir autorizacijai naudoja OAuth 2.0 protokolą. Naudojimo atvejus ir išsamesnius aprašymus rasite čia: „OAuth 2.0“ naudojimas norint pasiekti „Google“ API.

Pasirinkau scenarijų, kuris naudojamas autorizuoti darbalaukio programose. Taip pat yra galimybė naudoti paslaugos paskyrą, kuri nereikalauja nereikalingų vartotojo judesių.

Žemiau esančiame paveikslėlyje yra schematiškas pasirinkto scenarijaus aprašymas iš Google puslapio.

„Google“ naudotojų kūrimas iš „PowerShell“ per API

  1. Pirmiausia vartotoją siunčiame į „Google“ paskyros autentifikavimo puslapį, nurodydami GET parametrus:
    • programos id
    • sritis, prie kurių programai reikia prieigos
    • adresas, kuriuo vartotojas bus nukreiptas baigęs procedūrą
    • kaip mes atnaujinsime žetoną
    • Apsaugos kodas
    • patvirtinimo kodo perdavimo formatas

  2. Kai autorizacija bus baigta, vartotojas bus nukreiptas į pirmojoje užklausoje nurodytą puslapį su klaida arba autorizavimo kodu, kurį perdavė GET parametrai
  3. Programa (scenarijus) turės gauti šiuos parametrus ir, gavusi kodą, pateikti šią užklausą, kad gautų žetonus
  4. Jei užklausa teisinga, Google API pateikia:
    • Prieigos prieigos raktas, su kuriuo galime pateikti užklausas
    • Šio žetono galiojimo laikas
    • Atnaujinimo prieigos raktas reikalingas norint atnaujinti prieigos prieigos raktą.

Pirmiausia turite eiti į „Google“ API konsolę: Kredencialai – Google API konsolė, pasirinkite norimą programą ir kredencialų skiltyje sukurkite kliento OAuth identifikatorių. Ten (arba vėliau, sukurto identifikatoriaus savybėse) turite nurodyti adresus, į kuriuos leidžiama nukreipti. Mūsų atveju tai bus keli „localhost“ įrašai su skirtingais prievadais (žr. toliau).

Kad būtų patogiau skaityti scenarijaus algoritmą, pirmuosius veiksmus galite rodyti atskiroje funkcijoje, kuri grąžins programos prieigos ir atnaujinimo prieigos raktus:

$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

Mes nustatome kliento ID ir kliento paslaptį, gautą „OAuth“ kliento identifikatoriaus ypatybėse, o kodo tikrintuvas yra 43–128 simbolių eilutė, kuri turi būti atsitiktinai sugeneruota iš nerezervuotų simbolių: [AZ] / [az] / [0-9] / "-" / "." / "_" / "~".

Tada šis kodas bus perduotas dar kartą. Tai pašalina pažeidžiamumą, dėl kurio užpuolikas gali perimti atsakymą, grąžintą kaip peradresavimą gavus vartotojo įgaliojimą.
Kodo tikrintuvą dabartinėje užklausoje galite atsiųsti aiškiu tekstu (dėl to jis netenka prasmės – tinka tik sistemoms, kurios nepalaiko SHA256), arba sukurdami maišą naudodami SHA256 algoritmą, kuris turi būti užkoduotas BASE64Url (skirtinga iš Base64 dviem lentelės simboliais) ir pašalinant simbolių eilučių pabaigas: =.

Tada turime pradėti klausytis http vietiniame kompiuteryje, kad gautume atsakymą po autorizacijos, kuris bus grąžintas kaip peradresavimas.

Administravimo užduotys atliekamos specialiame serveryje, negalime atmesti galimybės, kad scenarijų vykdys keli administratoriai vienu metu, todėl jis atsitiktinai parinks prievadą dabartiniam vartotojui, tačiau nurodžiau iš anksto nustatytus prievadus, nes jie taip pat turi būti įtraukti kaip patikimi API konsolėje.

access_type=offline reiškia, kad programa gali pati atnaujinti pasibaigusį prieigos raktą be vartotojo sąveikos su naršykle,
atsako_tipas=kodas nustato formatą, kaip bus grąžintas kodas (nuoroda į seną autorizacijos metodą, kai vartotojas nukopijavo kodą iš naršyklės į scenarijų),
sritis nurodo prieigos apimtį ir tipą. Jie turi būti atskirti tarpais arba %20 (pagal URL kodavimą). Prieigos sričių sąrašą su tipais galite pamatyti čia: „OAuth 2.0“ taikymo sritis, skirta „Google“ API.

Gavusi autorizacijos kodą, programa grąžins naršyklei uždarymo pranešimą, nustos klausytis prievado ir išsiųs POST užklausą, kad gautų prieigos raktą. Jame nurodome anksčiau nurodytą ID ir paslaptį iš konsolės API, adresą, į kurį bus nukreiptas vartotojas, ir grant_type pagal protokolo specifikaciją.

Atsakydami gausime prieigos prieigos raktą, jo galiojimo laiką sekundėmis ir atnaujinimo prieigos raktą, su kuriuo galėsime atnaujinti prieigos raktą.

Programa turi saugoti žetonus saugioje vietoje su ilgu galiojimo laiku, todėl kol neatšauksime gautos prieigos, programa negrąžins atnaujinimo prieigos rakto. Pabaigoje pridėjau prašymą atšaukti prieigos raktą; jei paraiška nebuvo sėkmingai užpildyta ir atnaujinimo prieigos raktas nebuvo grąžintas, ji pradės procedūrą iš naujo (manėme, kad nesaugu saugoti žetonus vietoje terminale, ir mes to nedarome nenoriu apsunkinti dalykų su kriptografija arba dažnai atidaryti naršyklę).

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
}

Kaip jau pastebėjote, atšaukiant prieigos raktą naudojamas Invoke-WebRequest. Skirtingai nei Invoke-RestMethod, jis negrąžina gautų duomenų tinkamu formatu ir parodo užklausos būseną.

Tada scenarijus prašo įvesti vartotojo vardą ir pavardę, sugeneruojant prisijungimo vardą + el.

Paklausimai

Sekančios užklausos bus – pirmiausia turite patikrinti, ar vartotojas su tuo pačiu prisijungimu jau egzistuoja, kad priimtumėte sprendimą sukurti naują arba įjungti esamą.

Nusprendžiau įgyvendinti visas užklausas vienos funkcijos formatu pasirinkdamas, naudodamas jungiklį:

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

Kiekvienoje užklausoje turite nusiųsti autorizacijos antraštę, kurioje yra prieigos rakto tipas ir pats prieigos raktas. Šiuo metu žetono tipas visada yra Nešėjas. Nes turime patikrinti ar nepasibaigęs tokeno galiojimas ir atnaujinti po valandos nuo jo išdavimo momento, nurodžiau užklausą kitai funkcijai, kuri grąžina Access tokeną. Ta pati kodo dalis yra scenarijaus pradžioje, kai gaunamas pirmasis prieigos raktas:

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
}

Patikrinkite prisijungimo vardą:

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
}

El. pašto užklausa:$query paprašys API ieškoti vartotojo, turinčio būtent tą el. pašto adresą, įskaitant slapyvardžius. Taip pat galite naudoti pakaitos simbolį: =, :, :{PREFIX}*.

Norėdami gauti duomenis, naudokite GET užklausos metodą, įterpkite duomenis (sukurdami paskyrą arba įtraukdami narį į grupę) - POST, atnaujinti esamus duomenis - PUT, ištrinti įrašą (pavyzdžiui, narį iš grupės) - IŠTRINTI.

Scenarijus taip pat paprašys telefono numerio (nepatvirtintos eilutės) ir įtraukimo į regioninę platinimo grupę. Jis nusprendžia, kurį organizacinį vienetą vartotojas turi turėti pagal pasirinktą „Active Directory“ OU ir pateikia slaptažodį:

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"

Ir tada jis pradeda manipuliuoti paskyra:

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

Paskyros atnaujinimo ir kūrimo funkcijos turi panašią sintaksę, nebūtini visi papildomi laukai, skiltyje su telefonų numeriais reikia nurodyti masyvą, kuriame gali būti iki vieno įrašo su numeriu ir jo tipu.

Kad negautume klaidos pridedant vartotoją į grupę, pirmiausia galime patikrinti, ar jis jau yra šios grupės narys, gaudami grupės narių sąrašą arba sudėtį iš paties vartotojo.

Užklausa dėl konkretaus vartotojo narystės grupėje nebus rekursinė ir bus rodoma tik tiesioginė narystė. Įtraukti vartotoją į pirminę grupę, kuri jau turi antrinę grupę, kurios narys jis yra, bus sėkmingas.

išvada

Belieka nusiųsti vartotojui naujos paskyros slaptažodį. Tai darome SMS žinute, o bendrąją informaciją su instrukcijomis ir prisijungimu siunčiame asmeniniu el. paštu, kurį kartu su telefono numeriu pateikė įdarbinimo skyrius. Kaip alternatyvą galite sutaupyti pinigų ir nusiųsti slaptažodį į slaptą telegramos pokalbį, kuris taip pat gali būti laikomas antruoju veiksniu („MacBooks“ bus išimtis).

Ačiū, kad perskaitėte iki galo. Džiaugsiuosi sulaukęs pasiūlymų, kaip pagerinti straipsnių rašymo stilių, ir linkiu, kad rašant scenarijus būtų mažiau klaidų =)

Sąrašas nuorodų, kurios gali būti naudingos tematiškai arba tiesiog atsakyti į klausimus:

Šaltinis: www.habr.com

Добавить комментарий