Google'i kasutajate loomine PowerShellist API kaudu

Tere!

Selles artiklis kirjeldatakse PowerShelli interaktsiooni rakendamist Google API-ga G Suite'i kasutajate manipuleerimiseks.

Kasutame kogu organisatsioonis mitmeid sise- ja pilveteenuseid. Enamasti taandub nendes autoriseerimine Google'ile või Active Directoryle, mille vahel me koopiat hooldada ei saa; vastavalt sellele tuleb uue töötaja lahkumisel nendes kahes süsteemis konto luua/lubada. Protsessi automatiseerimiseks otsustasime kirjutada skripti, mis kogub teavet ja saadab selle mõlemale teenusele.

luba

Nõuete koostamisel otsustasime autoriseerimiseks kasutada reaalseid inimadministraatoreid, mis lihtsustab tegevuste analüüsi juhuslike või tahtlike suurte muudatuste korral.

Google'i API-d kasutavad autentimiseks ja autoriseerimiseks OAuth 2.0 protokolli. Kasutusjuhtumid ja täpsemad kirjeldused leiate siit: OAuth 2.0 kasutamine Google'i API-dele juurdepääsuks.

Valisin skripti, mida kasutatakse autoriseerimiseks töölauarakendustes. Samuti on võimalus kasutada teenusekontot, mis ei nõua kasutajalt asjatuid liigutusi.

Allolev pilt on valitud stsenaariumi skemaatiline kirjeldus Google'i lehelt.

Google'i kasutajate loomine PowerShellist API kaudu

  1. Esiteks saadame kasutaja Google'i konto autentimise lehele, täpsustades GET-i parameetrid:
    • rakenduse id
    • alad, millele rakendus vajab juurdepääsu
    • aadress, kuhu kasutaja pärast protseduuri lõpetamist ümber suunatakse
    • viis, kuidas me märgi värskendame
    • Turvakood
    • kinnituskoodi edastamise vorming

  2. Pärast autoriseerimise lõpetamist suunatakse kasutaja GET parameetrite poolt edastatud vea või autoriseerimiskoodiga esimeses päringus määratud lehele
  3. Rakendus (skript) peab need parameetrid vastu võtma ja koodi saamisel esitama žetoonide saamiseks järgmise päringu
  4. Kui päring on õige, tagastab Google API:
    • Juurdepääsuluba, millega saame taotlusi esitada
    • Selle märgi kehtivusaeg
    • Juurdepääsuloa värskendamiseks on vajalik värskendusluba.

Kõigepealt peate minema Google API konsooli: Mandaat – Google API konsool, valige soovitud rakendus ja looge jaotises Mandaat kliendi OAuthi identifikaator. Seal (või hiljem, loodud identifikaatori atribuutides) peate määrama aadressid, kuhu ümbersuunamine on lubatud. Meie puhul on need mitmed erinevate portidega kohaliku hosti kirjed (vt allpool).

Skriptialgoritmi lugemise mugavamaks muutmiseks saate kuvada esimesed sammud eraldi funktsioonis, mis tagastab rakendusele juurdepääsu- ja värskendusmärgid:

$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äärame OAuthi kliendi identifikaatori atribuutides saadud kliendi ID ja kliendi saladuse ning koodi kinnitaja on 43–128 märgist koosnev string, mis tuleb juhuslikult genereerida reserveerimata tähemärkidest: [AZ] / [az] / [0-9 ] / "-" / "." / "_" / "~".

See kood edastatakse seejärel uuesti. See kõrvaldab haavatavuse, mille korral ründaja võib pärast kasutaja autoriseerimist ümbersuunamisena tagastatud vastuse vahele jätta.
Koodi kinnitaja saate praeguses päringus saata selge tekstina (mis muudab selle mõttetuks - see sobib ainult süsteemidele, mis ei toeta SHA256) või luues SHA256 algoritmi abil räsi, mis peab olema kodeeritud BASE64Url-is (erinev Base64-st kahe tabelimärgi võrra) ja eemaldades märgirea lõpud: =.

Järgmiseks peame hakkama kohalikus masinas kuulama http-d, et saada pärast autoriseerimist vastus, mis tagastatakse ümbersuunamisena.

Administratiivseid ülesandeid tehakse spetsiaalses serveris, me ei saa välistada võimalust, et skripti käivitavad korraga mitu administraatorit, nii et see valib juhuslikult praegusele kasutajale pordi, kuid määrasin etteantud pordid, kuna need tuleb lisada ka API-konsoolis usaldusväärsetena.

access_type=offline tähendab, et rakendus saab aegunud märgi ise värskendada, ilma et kasutaja brauseriga suhtleks,
vastuse_tüüp=kood määrab koodi tagastamise vormingu (viide vanale autoriseerimismeetodile, kui kasutaja kopeeris koodi brauserist skripti),
ulatus näitab juurdepääsu ulatust ja tüüpi. Need tuleb eraldada tühikutega või %20-ga (vastavalt URL-i kodeeringule). Juurdepääsualade loendit tüüpidega näete siin: OAuth 2.0 ulatus Google'i API-de jaoks.

Pärast autoriseerimiskoodi saamist saadab rakendus brauserile sulgemisteate, lõpetab pordi kuulamise ja saadab loa hankimiseks POST-i päringu. Selles märgime konsooli API-st eelnevalt määratud ID ja saladuse, aadressi, kuhu kasutaja suunatakse, ja grant_type vastavalt protokolli spetsifikatsioonile.

Vastuseks saame juurdepääsuluba, selle kehtivusaja sekundites ja värskendamisloa, millega saame juurdepääsuluba värskendada.

Rakendus peab hoidma žetoone kindlas kohas ja pika säilivusajaga, nii et kuni me saadud juurdepääsu tühistame, ei tagasta rakendus värskendusluba. Lõppu lisasin loa tühistamise taotluse; kui rakendust ei lõpetatud edukalt ja värskendusluba ei tagastatud, alustab see toimingut uuesti (me pidasime žetoonide terminalis lokaalset salvestamist ebaturvaliseks ja me ei tee seda ei taha asju krüptograafiaga keeruliseks ajada ega brauserit sageli avada).

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
}

Nagu juba märkasite, kasutatakse loa tühistamisel Invoke-WebRequesti. Erinevalt Invoke-RestMethodist ei tagasta see saadud andmeid kasutatavas vormingus ja näitab päringu olekut.

Järgmisena palub skript teil sisestada kasutaja ees- ja perekonnanime, genereerides sisselogimise + e-posti.

taotlused

Järgmised päringud on - kõigepealt peate kontrollima, kas sama sisselogimisega kasutaja on juba olemas, et saada otsus uue loomise või praeguse lubamise kohta.

Otsustasin rakendada kõik taotlused ühe funktsiooni vormingus valikuga, kasutades lülitit:

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

Iga päringu puhul peate saatma autoriseerimispäise, mis sisaldab loa tüüpi ja juurdepääsuluba ennast. Praegu on märgi tüüp alati kandja. Sest peame kontrollima, et žetoon pole aegunud, ja värskendama seda tunni pärast selle väljastamise hetkest, täpsustasin taotluse teise funktsiooni jaoks, mis tagastab juurdepääsuluba. Sama koodijupp on skripti alguses, kui saate esimest juurdepääsuluba:

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
}

Sisselogimise kontrollimine:

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-posti aadress:$query päring palub API-l otsida kasutajat, kellel on täpselt see e-kiri, sealhulgas varjunimed. Võite kasutada ka metamärki: =, :, :{PREFIX}*.

Andmete hankimiseks kasutage päringumeetodit GET, andmete sisestamiseks (konto loomine või liikme lisamine gruppi) - POST, olemasolevate andmete värskendamiseks - PUT, kirje kustutamiseks (näiteks grupi liige) - KUSTUTA.

Skript küsib ka telefoninumbrit (valideerimata string) ja piirkondlikku levitusrühma kaasamist. See otsustab valitud Active Directory OU põhjal, milline organisatsiooniüksus kasutajal peaks olema, ja esitab parooli:

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"

Ja siis hakkab ta kontoga manipuleerima:

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

Konto värskendamise ja loomise funktsioonid on sarnase süntaksiga, kõiki lisavälju pole vaja, telefoninumbrite jaotises tuleb määrata massiiv, mis võib sisaldada kuni ühte kirjet koos numbri ja selle tüübiga.

Selleks, et kasutaja gruppi lisamisel veateadet ei tekiks, saame esmalt kontrollida, kas ta on juba selle grupi liige, hankides kasutajalt endalt grupiliikmete nimekirja või koosseisu.

Konkreetse kasutaja grupiliikmesuse päringu esitamine ei ole rekursiivne ja näitab ainult otsest liikmelisust. Kasutaja kaasamine vanemgruppi, millel on juba alamrühm, mille liige kasutaja on, õnnestub.

Järeldus

Jääb üle vaid saata kasutajale uue konto parool. Teeme seda SMS-i teel ning saadame üldise teabe koos juhiste ja sisselogimisega isiklikule meilile, mille koos telefoninumbriga edastas värbamisosakond. Alternatiivina saate säästa raha ja saata oma parooli salajasele telegrammivestlusele, mida võib samuti pidada teiseks teguriks (erandiks on MacBookid).

Täname, et lugesite lõpuni. Mul on hea meel näha ettepanekuid artiklite kirjutamise stiili parandamiseks ja soovin, et skriptide kirjutamisel vähem vigu tabaksite =)

Linkide loend, mis võivad olla temaatiliselt kasulikud või vastata lihtsalt küsimustele:

Allikas: www.habr.com

Lisa kommentaar