Google'i kasutajate loomine PowerShelli kaudu API abil

Tere!

Selles artiklis kÀsitletakse PowerShelli ja Google API koostööd G Suite'i kasutajate haldamiseks.

Meie organisatsioonis kasutame mitmeid sisemisi ja pilveteenuseid. Suurel mÀÀral kĂ”ikide teenuste autoriseerimine toimub Google'i vĂ”i Active Directory kaudu, mille vahel me ei saa replikatsiooni toetada, seega tuleb uue töötaja puhul luua/aktiveerida konto nendes kahes sĂŒsteemis. Protsessi automatiseerimiseks otsustasime kirjutada skripti, mis kogub teavet ja saadab selle mĂ”lemasse teenusesse.

Autoriseerimine

NĂ”udeid koostades otsustasime kasutada autoriseerimiseks tegelikke administraatoreid, et lihtsustada juhtumite analĂŒĂŒsimist juhuslike vĂ”i tahtlike massiliste muutuste korral.

Google API kasutab autentimiseks ja autoriseerimiseks protokolli OAuth 2.0. Kasutusskeeme ja ĂŒksikasjalikku kirjeldust saab vaadata siin: Using OAuth 2.0 to Access Google APIs.

Valisin stsenaariumi, mida kasutatakse lauaarvuti rakendustesse autoriseerimisel. Samuti on vÔimalus kasutada teenusekontot, mis ei nÔua kasutajalt tÀiendavaid toiminguid.

Allolev joonis on valitud stsenaariumi visand lehelt Google.

Google'i kasutajate loomine PowerShelli kaudu API abil

  1. Esmalt suuname kasutaja Google'i konto autentimise lehele, mÀÀrates GET parameetrid:
    • rakenduse identifikaator
    • valdkonnad, millele rakendus vajab ligipÀÀsu
    • aadress, kuhu kasutaja suunatakse pĂ€rast protseduuri lĂ”petamist
    • meetod, millega me vĂ€rskendame tokenit
    • kontrollikood
    • kontrollikoodi edastamise formaat

  2. PÀrast autoriseerimise lÔpetamist suunatakse kasutaja tagasi esimeses pÀringus mÀÀratud lehele, koos vea vÔi autoriseerimiskoodiga, edastatuna GET parameetrites.
  3. Rakendusel (skriptil) tuleb need parameetrid kÀtte saada ja, kui kood on saadud, teha jÀrgmine pÀring tokenite saamiseks.
  4. Korrektselt esitatud pÀringu korral tagastab Google API:
    • Access token, millega me saame pĂ€ringuid esitada
    • Selle tokeni kehtivusaeg
    • Refresh token, mis on vajalik Access tokeni uuendamiseks.

Esmalt tuleb minna Google API konsooli: Credentials — Google API Console, vali sobiv rakendus ja sektsioonis Credentials loo OAuth kliendi ID. Seal (vĂ”i hiljem, loodud ID omadustes) tuleb nĂ€idata aadresse, kuhu suunamine on lubatud. Meie puhul on need mitu localhost'i kirjet erinevate portidega (vt edasi).

Selleks, et skripti algoritmi oleks mugavam lugeda, saab esimesed sammud eraldi funktsiooni vÀlja tuua, mis tagastab juurdepÀÀsu- ja vÀrskenduse tokenid rakenduse jaoks:

$client_secret = 'Meie Klientide SalasÔna'
$client_id = 'Meie Klientide ID'
function Get-GoogleAuthToken {
  if (-not [System.Net.HttpListener]::IsSupported) {
    "HttpListener ei ole toetatud."
    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 "KĂ€ivitame brauseri..."
  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 {
    "Klistenerit ei Ônnestu kÀivitada."
    exit 1
  }
  while (($code -eq $null)) {
    $context = $listener.GetContext()
    Write-Host "Ühendus aktsepteeritud" -f 'mag'
    $url = $context.Request.RawUrl
    $code = $url.split('?')[1].split('=')[1].split('&')[0]
    if ($url.split('?')[1].split('=')[0] -eq 'error') {
      Write-Host "Viga!"$code -f 'red'
      $buffer = [System.Text.Encoding]::UTF8.GetBytes("Viga!"+$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("NĂŒĂŒd vĂ”id selle brauseri vahelehe sulgeda.")
    $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

Me seadistame Client ID ja Client Secret, mis on saadud OAuth kliendi ID omadustes, ja code verifier – see on 43 kuni 128 sĂŒmboli pikkune string, mis tuleb genereerida juhuslikult mittereserveeritud sĂŒmbolitest: [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~".

Edasi antakse see kood uuesti. See vÀlistab haavatavuse, kus pahatahtlik inimene vÔib vastuse, mis tagastati pÀrast kasutaja autentimist, ummistada.
Saada koodivahendi praegusesse pĂ€ringusse vĂ”ib saata avatud kujul (mis muudab selle mĂ”ttetuks - see sobib ainult sĂŒsteemidele, mis ei toeta SHA256) vĂ”i luua SHA256 algoritmiga hash, mis tuleb kodeerida BASE64Url (erineb Base64 kahe sĂŒmboli poolest) ja eemaldada reavahetusmĂ€rk: =.

SeejĂ€rel peame alustama http kuulamist lokaalses masinas, et saada vastus autoriseerimise jĂ€rel, mis naaseb ĂŒmbersuunamisega.

HaldusĂŒlesanded toimuvad spetsiaalsel serveril, me ei saa vĂ€listada tĂ”enĂ€osust, et mitu administraatorit kĂ€ivitab skripti samaaegselt, seetĂ”ttu valib see juhuslikult porti praeguse kasutaja jaoks, kuid ma mÀÀrasin ette antud pordid, kuna need tuleb samuti lisada usaldusvÀÀrseteks API konsoolis.

access_type=offline tÀhendab, et rakendus vÔib iseseisvalt uuendada aegunud tokeni ilma, et kasutaja peaks brauseriga suhtlema.
response_type=code mÀÀrab formaadi, kuidas kood naaseb (viide vanale autoriseerimise meetodile, kus kasutaja kopeeris koodi brauserist skripti),
scope nĂ€itab alasid ja juurdepÀÀsu tĂŒĂŒpe. Need peavad olema eraldatud tĂŒhikute vĂ”i (vastavalt URL-kodeeringule). JuurdepÀÀsu alade ja tĂŒĂŒpide loetelu saab nĂ€ha siit: OAuth 2.0 ulatused Google'i API-de jaoks.

PÀrast autoriseerimise koodi saamist saadab rakendus brauserisse sulgemisettepaneku, lÔpetab pordi kuulamise ja saadab POST-pÀringu tokeni saamiseks. Me mÀÀrame selles antud id ja secret API konsoolist, aadress, kuhu kasutaja suunatakse ja grant_type vastavalt protokolli spetsifikatsioonile.

Vastuseks saame Access tokeni, selle kehtivuse aja sekundites ja Refresh tokeni, millega saame Access tokeni uuendada.

Rakendus peab hoidma tokeneid turvalises kohas pikaajalise sĂ€ilitamisega, seega, kuni me ei tĂŒhista saadud juurdepÀÀsu, ei tagastata rakendusele refresh tokenit. LĂ”puks lisasin pĂ€ringu tokeni tĂŒhistamiseks, kui rakendus ei olnud edukas ja refresh tokenit ei tagastatud, alustab see protseduuri uuesti (me pidasime ebaturvaliseks hoida tokeneid kohalikult terminalis ning ei soovi keerulisemaks muuta krĂŒptograafiaga vĂ”i tihti brauserit avada).

do {
  $token_result = Get-GoogleAuthToken
  $token = $token_result.access_token
  if ($token_result.refresh_token -eq $null) {
    Write-Host ("Seanss ei ole hĂ€vitatud. TĂŒhistatakse 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 olete juba mĂ€rganud, kasutatakse tokeni tĂŒhistamiseks Invoke-WebRequest'i. Erinevalt Invoke-RestMethod'ist ei naita see saadud andmeid mugavas vormingus ning nĂ€itab pĂ€ringu staatust.

SeejÀrel palub skript sisestada kasutaja ees- ja perekonnanime, genereerides sisenemisnime + e-posti.

PĂ€ringud

JĂ€rgmised toimingud on pĂ€ringud – kĂ”igepealt tuleb kontrollida, kas kasutajaga, kellel on selline sisenemisnimi, on juba olemas, et teha otsus uue loomise vĂ”i olemasoleva aktiveerimise ĂŒle.

Otsustasin kĂ”ik pĂ€ringud ellu viia ĂŒhe funktsiooni formaadis, kasutades switch'i:

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 taotluses tuleb saata Authorization pĂ€is, mis sisaldab tokeni tĂŒĂŒpi ja ise Access tokenit. Praegu on tokeni tĂŒĂŒp alati Bearer. Kuna meile on vajalik kontrollida, et token pole aegunud ja vĂ€rskendada seda tunni jooksul pĂ€rast vĂ€ljaandmist, olen mÀÀranud pĂ€ringu teise funktsioonile, mis tagastab Access tokeni. See sama koodijupp on skripti alguses, kui saadakse esimene Access token:

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 on aegunud. Uuendamine..."
    $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
}

Kontrolli kasutajanime olemasolu:

function Check_Google {
  $query = (GoogleQuery 'SearchAccount' $username)
  if ($query.users -ne $null) {
    $user = $query.users[0]
    Write-Host $user.name.fullName' - '$user.PrimaryEmail' - peatatud: '$user.Suspended
    $GAresult = $user
  }
  if ($GAresult) {
      $return = $GAresult
  } else {$return = 'gg'}
  return $return
}

KĂŒsi email: $query palub API-l otsida kasutajat just sellise emailiga, sealhulgas leidub ka alias'e. Samuti vĂ”id kasutada wildcard'i: =, :, :{PREFIX}*.

Andmete saamiseks kasutatakse meetodit GET, andmete sisestamiseks (konto loomine vĂ”i grupi liikme lisamine) – POST, olemasolevate andmete uuendamiseks – PUT, kirje kustutamiseks (nĂ€iteks grupist liikme eemaldamiseks) – DELETE.

Kood kĂŒsib ka telefoninumbrit (valideerimata string) ja kas liituda regionaalse postitusgrupiga. See otsustab, milline organisatsiooniline ĂŒksus kasutajal olema peaks vastavalt valitud OU Active Directory-s ja loob parooli:

do {
  $phone = Read-Host "Telefon vormingus +7хххххххх"
} while (-not $phone)
do {
    $moscow = Read-Host "Moskva kontorisse? (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 "Luua aadressile /Team delivery"
    $orgunit = "/Team delivery"
}
$Password =  -join ( 48..57 + 65..90 + 97..122 | Get-Random -Count 12 | % {[char]$_})+"*Ba"

Ja siis alustab see konto manipuleerimist:

$query = @{
  email = $email
  givenName = $firstname
  familyName = $lastname
  password = $password
  phone = $phone
  orgunit = $orgunit
}
if ($GMailExist) {
  Write-Host "KĂ€ivitame konto muutmise" -f mag
  (GoogleQuery 'UpdateAccount' $query) | fl
  write-host "Ära unusta kontrollida gruppe, kuhu '$Username' Google'is kuulub."
} else {
  Write-Host "KĂ€ivitame konto loomise" -f mag
  (GoogleQuery 'CreateAccount' $query) | fl
}
if ($moscow -eq "y"){
  write-host "Lisame moscowoffice gruppi"
  $query = @{
    groupkey = 'moscowoffice@rocketguys.com'
    email = $email
  }
  (GoogleQuery 'AddMember' $query) | fl
}

Konto loomise ja uuendamise funktsioonidel on sarnane sĂŒntaks, mitte kĂ”ik tĂ€iendavad vĂ€ljad ei ole kohustuslikud, telefoninumbrite jaotises tuleb esitada massiiv, mis vĂ”ib sisaldada vĂ€hemalt ĂŒhte kirjet koos numbri ja selle tĂŒĂŒbiga.

Kuna vĂ”ib esineda viga kasutaja lisamisel rĂŒhma, saame eelnevalt kontrollida, kas ta kuulub juba sellesse rĂŒhma, saades rĂŒhmaliikmete nimekirja vĂ”i muud kasutaja andmed.

Teatud kasutaja rĂŒhmade koosseisu pĂ€ring ei ole rekursiivne ja nĂ€itab ainult vahetut kuuluvust. Kasutaja lisamine ĂŒlemrĂŒhma, milles juba eksisteerib alamrĂŒhm, mille liige ta on, Ă”nnestub.

KokkuvÔte

JÀÀnud on saata kasutajale uus konto parool. Teeme seda SMS-iga ning ĂŒldise teabe koos juhiste ja sisselogimisega saadame isiklikule e-postile, mille andis personaliosakond koos telefoninumeroga. Alternatiivina saame raha sÀÀsta ja saata parooli salajasesse Telegrami vestlusesse, mis vĂ”ib samuti arvestada teise tegurina (vĂ€lja arvatud MacBookid).

AitÀh, et lugesite lÔpuni. Olen avatud ettepanekutele artiklite kirjutamisstiili parandamiseks ja soovin, et te teeks, et teie skriptide kirjutamises vÀhem vigu =)

Nimekiri lingitud, mis vĂ”ivad olla temaatiliselt kasulikud vĂ”i lihtsalt vastata tekkinud kĂŒsimustele:

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster