Krijimi i përdoruesve të Google nga PowerShell nëpërmjet API

Привет!

Ky artikull do të përshkruajë zbatimin e ndërveprimit të PowerShell me Google API për të manipuluar përdoruesit e G Suite.

Ne përdorim disa shërbime të brendshme dhe cloud në të gjithë organizatën. Në pjesën më të madhe, autorizimi në to zbret në Google ose Active Directory, midis të cilave ne nuk mund të mbajmë një kopje; në përputhje me rrethanat, kur një punonjës i ri largohet, ju duhet të krijoni/aktivizoni një llogari në këto dy sisteme. Për të automatizuar procesin, vendosëm të shkruajmë një skript që mbledh informacion dhe ia dërgon të dy shërbimeve.

Autorizim

Gjatë hartimit të kërkesave, vendosëm të përdorim administratorë të vërtetë njerëzorë për autorizim; kjo thjeshton analizën e veprimeve në rast të ndryshimeve masive aksidentale ose të qëllimshme.

API-të e Google përdorin protokollin OAuth 2.0 për vërtetimin dhe autorizimin. Rastet e përdorimit dhe përshkrimet më të hollësishme mund të gjenden këtu: Përdorimi i OAuth 2.0 për të hyrë në API të Google.

Zgjodha skriptin që përdoret për autorizim në aplikacionet desktop. Ekziston gjithashtu një mundësi për të përdorur një llogari shërbimi, e cila nuk kërkon lëvizje të panevojshme nga përdoruesi.

Fotografia më poshtë është një përshkrim skematik i skenarit të zgjedhur nga faqja e Google.

Krijimi i përdoruesve të Google nga PowerShell nëpërmjet API

  1. Së pari, ne e dërgojmë përdoruesin në faqen e vërtetimit të llogarisë Google, duke specifikuar parametrat GET:
    • ID e aplikacionit
    • zonat ku aplikacioni ka nevojë për qasje
    • adresën në të cilën përdoruesi do të ridrejtohet pas përfundimit të procedurës
    • mënyra se si do të përditësojmë token
    • Kodi i Sigurisë
    • formati i transmetimit të kodit të verifikimit

  2. Pas përfundimit të autorizimit, përdoruesi do të ridrejtohet në faqen e specifikuar në kërkesën e parë, me një gabim ose kod autorizimi të kaluar nga parametrat GET
  3. Aplikacioni (skripti) do të duhet të marrë këto parametra dhe, nëse merret kodi, të bëjë kërkesën e mëposhtme për të marrë argumentet
  4. Nëse kërkesa është e saktë, Google API kthen:
    • Shenja e hyrjes me të cilën mund të bëjmë kërkesa
    • Periudha e vlefshmërisë së këtij tokeni
    • Kërkohet token rifreskimi për të rifreskuar kodin e Access.

Së pari ju duhet të shkoni në tastierën API të Google: Kredencialet - Google API Console, zgjidhni aplikacionin e dëshiruar dhe në seksionin Kredencialet krijoni një identifikues OAuth të klientit. Atje (ose më vonë, në vetitë e identifikuesit të krijuar) duhet të specifikoni adresat në të cilat lejohet ridrejtimi. Në rastin tonë, këto do të jenë disa hyrje localhost me porte të ndryshme (shih më poshtë).

Për ta bërë më të përshtatshëm leximin e algoritmit të skriptit, mund të shfaqni hapat e parë në një funksion të veçantë që do të kthejë Access dhe do të rifreskojë shenjat për aplikacionin:

$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

Ne vendosëm ID-në e klientit dhe sekretin e klientit të marra në vetitë e identifikuesit të klientit OAuth dhe verifikuesi i kodit është një varg prej 43 deri në 128 karaktere që duhet të krijohen rastësisht nga karaktere të parezervuara: [AZ] / [az] / [0-9 ] / "-" / "." / "_" / "~".

Ky kod më pas do të transmetohet përsëri. Ai eliminon cenueshmërinë në të cilën një sulmues mund të përgjojë një përgjigje të kthyer si ridrejtim pas autorizimit të përdoruesit.
Ju mund të dërgoni një verifikues kodi në kërkesën aktuale në tekst të qartë (që e bën atë të pakuptimtë - kjo është e përshtatshme vetëm për sistemet që nuk mbështesin SHA256), ose duke krijuar një hash duke përdorur algoritmin SHA256, i cili duhet të kodohet në BASE64Url (ndryshon nga Base64 nga dy karaktere tabele) dhe duke hequr mbaresat e rreshtave të karaktereve: =.

Më pas, duhet të fillojmë të dëgjojmë http në makinën lokale në mënyrë që të marrim një përgjigje pas autorizimit, e cila do të kthehet si një ridrejtim.

Detyrat administrative kryhen në një server të veçantë, nuk mund të përjashtojmë mundësinë që disa administratorë të ekzekutojnë skriptin në të njëjtën kohë, kështu që ai do të zgjedhë rastësisht një port për përdoruesin aktual, por unë specifikova portet e paracaktuara sepse ato gjithashtu duhet të shtohen si të besuara në tastierën API.

akses_lloji=jashtë linje do të thotë që aplikacioni mund të përditësojë vetë një token të skaduar pa ndërveprim të përdoruesit me shfletuesin,
përgjigje_lloji=kodi vendos formatin se si do të kthehet kodi (një referencë për metodën e vjetër të autorizimit, kur përdoruesi kopjoi kodin nga shfletuesi në skript),
Shtrirja tregon shtrirjen dhe llojin e aksesit. Ato duhet të ndahen me hapësira ose %20 (sipas kodimit të URL-së). Një listë e zonave të aksesit me lloje mund të shihet këtu: Fushat e OAuth 2.0 për API-të e Google.

Pas marrjes së kodit të autorizimit, aplikacioni do të kthejë një mesazh të ngushtë në shfletuesin, do të ndalojë së dëgjuari në port dhe do të dërgojë një kërkesë POST për të marrë tokenin. Ne tregojmë në të ID-në dhe sekretin e specifikuar më parë nga API-ja e tastierës, adresën në të cilën përdoruesi do të ridrejtohet dhe lloji i grantit në përputhje me specifikimet e protokollit.

Si përgjigje, ne do të marrim një shenjë Access, periudhën e vlefshmërisë së tij në sekonda dhe një shenjë Rifreskimi, me të cilin mund të përditësojmë tokenin Access.

Aplikacioni duhet të ruajë argumentet në një vend të sigurt me një jetëgjatësi të gjatë, kështu që derisa të revokojmë aksesin e marrë, aplikacioni nuk do ta kthejë tokenin e rifreskimit. Në fund, shtova një kërkesë për revokimin e tokenit; nëse aplikacioni nuk u plotësua me sukses dhe token i rifreskimit nuk u kthye, ai do të fillojë përsëri procedurën (ne e konsideruam të pasigurt ruajtjen e argumenteve në nivel lokal në terminal, dhe nuk e bëjmë 'dua t'i ndërlikoj gjërat me kriptografinë ose të hap shpesh shfletuesin).

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
}

Siç e keni vënë re tashmë, kur revokoni një token, përdoret Invoke-WebRequest. Ndryshe nga Invoke-RestMethod, ai nuk i kthen të dhënat e marra në një format të përdorshëm dhe tregon statusin e kërkesës.

Më pas, skripti ju kërkon të vendosni emrin dhe mbiemrin e përdoruesit, duke gjeneruar një hyrje + email.

Kërkesat

Kërkesat e ardhshme do të jenë - para së gjithash, ju duhet të kontrolloni nëse një përdorues me të njëjtën hyrje ekziston tashmë në mënyrë që të merrni një vendim për krijimin e një të ri ose aktivizimin e atij aktual.

Vendosa të zbatoj të gjitha kërkesat në formatin e një funksioni me një përzgjedhje, duke përdorur çelësin:

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

Në çdo kërkesë, ju duhet të dërgoni një titull autorizimi që përmban llojin e tokenit dhe vetë tokenin Access. Aktualisht, lloji i tokenit është gjithmonë Bartës. Sepse duhet të kontrollojmë që token nuk ka skaduar dhe ta përditësojmë pas një ore nga momenti i lëshimit, unë specifikova një kërkesë për një funksion tjetër që kthen një shenjë Access. E njëjta pjesë e kodit është në fillim të skriptit kur merr shenjën e parë të 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
}

Kontrollimi i hyrjes për ekzistencë:

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
}

Kërkesa email:$query do t'i kërkojë API-së të kërkojë një përdorues me pikërisht atë email, duke përfshirë pseudonimet. Ju gjithashtu mund të përdorni wildcard: =, :, :{PREFIX}*.

Për të marrë të dhëna, përdorni metodën e kërkesës GET, për të futur të dhëna (krijimi i një llogarie ose shtimi i një anëtari në një grup) - POST, për të përditësuar të dhënat ekzistuese - PUT, për të fshirë një rekord (për shembull, një anëtar nga një grup) - FSHIJE.

Skripti do të kërkojë gjithashtu një numër telefoni (një varg i pavlefshëm) dhe për përfshirje në një grup shpërndarjeje rajonale. Ai vendos se cilën njësi organizative duhet të ketë përdoruesi bazuar në OU të zgjedhur të Active Directory dhe del me një fjalëkalim:

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"

Dhe pastaj ai fillon të manipulojë llogarinë:

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

Funksionet për përditësimin dhe krijimin e një llogarie kanë një sintaksë të ngjashme; nuk kërkohen të gjitha fushat shtesë; në seksionin me numrat e telefonit, duhet të specifikoni një grup që mund të përmbajë deri në një rekord me numrin dhe llojin e tij.

Për të mos marrë një gabim kur shtoni një përdorues në një grup, fillimisht mund të kontrollojmë nëse ai është tashmë anëtar i këtij grupi duke marrë një listë të anëtarëve të grupit ose përbërje nga vetë përdoruesi.

Kërkimi i anëtarësimit në grup të një përdoruesi specifik nuk do të jetë rekursiv dhe do të tregojë vetëm anëtarësimin e drejtpërdrejtë. Përfshirja e një përdoruesi në një grup prind që tashmë ka një grup fëmijësh ku përdoruesi është anëtar do të ketë sukses.

Përfundim

E tëra që mbetet është t'i dërgoni përdoruesit fjalëkalimin për llogarinë e re. Ne e bëjmë këtë me SMS dhe dërgojmë informacione të përgjithshme me udhëzime dhe hyrje në një email personal, i cili, së bashku me një numër telefoni, është dhënë nga departamenti i rekrutimit. Si alternativë, mund të kurseni para dhe të dërgoni fjalëkalimin tuaj në një bisedë sekrete telegrami, i cili gjithashtu mund të konsiderohet faktori i dytë (MacBooks do të jetë një përjashtim).

Faleminderit që lexuat deri në fund. Do të jem i lumtur të shoh sugjerime për përmirësimin e stilit të shkrimit të artikujve dhe uroj që të kapni më pak gabime kur shkruani skriptet =)

Lista e lidhjeve që mund të jenë të dobishme tematikisht ose thjesht t'u përgjigjen pyetjeve:

Burimi: www.habr.com

Shto një koment