Að búa til Google notendur frá PowerShell í gegnum API

РџСЂРІРμы!

Þessi grein mun lýsa útfærslu PowerShell samskipta við Google API til að vinna með G Suite notendur.

Við notum nokkrar innri og skýjaþjónustur víðs vegar um stofnunina. Að mestu leyti kemur heimild í þeim niður á Google eða Active Directory, þar á milli getum við ekki haldið eftirmynd; í samræmi við það, þegar nýr starfsmaður hættir, þarftu að búa til/virkja reikning í þessum tveimur kerfum. Til að gera ferlið sjálfvirkt ákváðum við að skrifa handrit sem safnar upplýsingum og sendir þær til beggja þjónustunnar.

Innskráning

Við gerð krafnanna ákváðum við að nota raunverulega mannlega stjórnendur fyrir heimildir; þetta einfaldar greiningu á aðgerðum ef um stórfelldar breytingar verða fyrir slysni eða af ásetningi.

Google API nota OAuth 2.0 samskiptareglur fyrir auðkenningu og heimild. Notkunartilvik og ítarlegri lýsingar má finna hér: Notkun OAuth 2.0 til að fá aðgang að Google API.

Ég valdi handritið sem er notað fyrir heimild í skjáborðsforritum. Einnig er möguleiki á að nota þjónustureikning, sem krefst ekki óþarfa hreyfinga frá notanda.

Myndin hér að neðan er skýringarmynd af völdu atburðarásinni af Google síðunni.

Að búa til Google notendur frá PowerShell í gegnum API

  1. Fyrst sendum við notandann á auðkenningarsíðu Google reiknings og tilgreinum GET færibreytur:
    • umsóknarkenni
    • svæði sem forritið þarf aðgang að
    • heimilisfangið sem notandanum verður vísað á eftir að ferlinu er lokið
    • hvernig við munum uppfæra táknið
    • Öryggiskóði
    • sendingarsnið staðfestingarkóða

  2. Eftir að heimild er lokið verður notanda vísað á síðuna sem tilgreind er í fyrstu beiðninni, með villu eða heimildarkóða sem GET færibreytur sendu
  3. Forritið (handritið) mun þurfa að fá þessar færibreytur og, ef þú færð kóðann, gera eftirfarandi beiðni um að fá tákn
  4. Ef beiðnin er rétt skilar Google API:
    • Aðgangslykil sem við getum lagt fram beiðnir með
    • Gildistími þessa tákns
    • Refresh token þarf til að endurnýja Access token.

Fyrst þarftu að fara í Google API stjórnborðið: Skilríki - Google API stjórnborð, veldu forritið sem þú vilt og búðu til OAuth auðkenni viðskiptavinar í hlutanum Skilríki. Þar (eða síðar, í eiginleikum stofnaðs auðkennis) þarftu að tilgreina heimilisföngin sem framsending er leyfð til. Í okkar tilviki verða þetta nokkrar staðbundnar hýsingarfærslur með mismunandi höfnum (sjá hér að neðan).

Til að gera það þægilegra að lesa forskriftaralgrímið geturðu birt fyrstu skrefin í sérstakri aðgerð sem mun skila aðgangi og endurnýja tákn fyrir forritið:

$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

Við stillum auðkenni viðskiptavinarins og leyndarmál viðskiptavinarins sem fæst í eiginleikum OAuth biðlaraauðkennis og kóða sannprófandi er strengur með 43 til 128 stöfum sem verður að búa til af handahófi úr óafteknum stöfum: [AZ] / [az] / [0-9 ] / "-" / "." / "_" / "~".

Þessi kóði verður síðan sendur aftur. Það útilokar varnarleysið þar sem árásarmaður gæti stöðvað svar sem skilað er sem tilvísun eftir notandaheimild.
Þú getur sent kóða sannprófanda í núverandi beiðni í skýrum texta (sem gerir hana tilgangslausa - þetta hentar aðeins fyrir kerfi sem styðja ekki SHA256), eða með því að búa til kjötkássa með SHA256 reikniritinu, sem verður að kóða í BASE64Url (mismunandi frá Base64 með tveimur töflustöfum) og fjarlægja stafalínuendingar: =.

Næst þurfum við að byrja að hlusta á http á staðbundinni vél til að fá svar eftir heimild, sem verður skilað sem tilvísun.

Stjórnunarverkefni eru unnin á sérstökum netþjóni, við getum ekki útilokað að nokkrir stjórnendur keyri scriptið á sama tíma, þannig að það velur af handahófi port fyrir núverandi notanda, en ég tilgreindi fyrirfram skilgreind port vegna þess að þeim verður einnig að bæta við sem traust í API stjórnborðinu.

access_type=ótengdur þýðir að forritið getur uppfært útrunnið tákn á eigin spýtur án samskipta notenda við vafrann,
response_type=kóði stillir sniðið á hvernig kóðanum verður skilað (tilvísun í gömlu heimildaraðferðina, þegar notandinn afritaði kóðann úr vafranum yfir í forskriftina),
umfang gefur til kynna umfang og tegund aðgangs. Þau verða að vera aðskilin með bilum eða %20 (samkvæmt URL kóðun). Lista yfir aðgangssvæði með gerðum má sjá hér: OAuth 2.0 gildissvið fyrir Google API.

Eftir að hafa fengið heimildarkóðann mun forritið skila lokuðu skilaboðum í vafrann, hætta að hlusta á tengið og senda POST beiðni til að fá táknið. Við tilgreinum þar áður tilgreint auðkenni og leyndarmál frá stjórnborðs API, heimilisfangið sem notandinn verður vísað til og grant_type í samræmi við samskiptareglur.

Til að bregðast við, munum við fá aðgangslykil, gildistíma þess í sekúndum og endurnýjunartákn, sem við getum uppfært aðgangslykilinn með.

Forritið verður að geyma tákn á öruggum stað með langan geymsluþol, þannig að þangað til við afturkallum aðganginn sem berast mun forritið ekki skila endurnýjunartákninu. Í lokin bætti ég við beiðni um að afturkalla táknið; ef ekki tókst að klára forritið og endurnýjunartákninu var ekki skilað mun það hefja málsmeðferðina aftur (við töldum óöruggt að geyma tákn á staðnum í flugstöðinni og við gerum það ekki vil ekki flækja hlutina með dulmáli eða opna vafrann oft).

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
}

Eins og þú hefur þegar tekið eftir, þegar þú afturkallar tákn, er Invoke-WebRequest notað. Ólíkt Invoke-RestMethod, skilar það ekki mótteknum gögnum á nothæfu sniði og sýnir stöðu beiðninnar.

Næst biður handritið þig um að slá inn fornafn og eftirnafn notandans og myndar innskráningu + tölvupóst.

Beiðnir

Næstu beiðnir verða - fyrst og fremst þarftu að athuga hvort notandi með sama innskráningu sé þegar til staðar til að fá ákvörðun um að búa til nýjan eða virkja núverandi.

Ég ákvað að útfæra allar beiðnir á formi einnar aðgerðar með vali, með því að nota rofa:

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

Í hverri beiðni þarftu að senda heimildarhaus sem inniheldur auðkennistegundina og aðgangslykilinn sjálfan. Eins og er er tákntegundin alltaf Bearer. Vegna þess að við þurfum að athuga hvort táknið sé ekki útrunnið og uppfæra það eftir klukkutíma frá því að það var gefið út, ég tilgreindi beiðni um aðra aðgerð sem skilar Access token. Sami kóðastykki er í upphafi skriftunnar þegar fyrsta aðgangslykillinn er móttekinn:

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
}

Athugaðu hvort innskráningin sé til:

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
}

Email:$query beiðnin mun biðja API um að leita að notanda með nákvæmlega það netfang, þar á meðal samnefni. Þú getur líka notað jokertákn: =, :, :{PREFIX}*.

Til að fá gögn, notaðu GET beiðniaðferðina, til að setja inn gögn (að búa til reikning eða bæta meðlim í hóp) - POST, til að uppfæra fyrirliggjandi gögn - PUT, til að eyða færslu (til dæmis meðlim úr hópi) - EYÐA.

Handritið mun einnig biðja um símanúmer (óstaðfestan streng) og að vera tekinn inn í svæðisbundinn dreifingarhóp. Það ákveður hvaða skipulagsheild notandinn ætti að hafa byggt á völdum Active Directory OU og kemur upp með lykilorð:

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"

Og svo byrjar hann að hagræða reikningnum:

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

Aðgerðir til að uppfæra og búa til reikning hafa svipaða setningafræði; ekki eru allir viðbótarreitir nauðsynlegir; í hlutanum með símanúmerum þarftu að tilgreina fylki sem getur innihaldið allt að eina færslu með númerinu og gerð þess.

Til þess að fá ekki villu þegar notandi er bætt við hóp getum við fyrst athugað hvort hann sé nú þegar meðlimur í þessum hópi með því að fá lista yfir hópmeðlimi eða samsetningu frá notandanum sjálfum.

Spurning um hópaðild tiltekins notanda mun ekki vera endurhverf og mun aðeins sýna beina aðild. Það tekst að taka notanda með í foreldrahóp sem er þegar með undirhóp sem notandinn er meðlimur í.

Ályktun

Það eina sem er eftir er að senda notandanum lykilorðið fyrir nýja reikninginn. Þetta gerum við með SMS og sendum almennar upplýsingar með leiðbeiningum og innskráningu á persónulegan tölvupóst sem ásamt símanúmeri var gefið upp af ráðningardeild. Í staðinn geturðu sparað peninga og sent lykilorðið þitt í leynilegt símskeytispjall, sem getur einnig talist annar þátturinn (MacBooks verða undantekning).

Þakka þér fyrir að lesa til enda. Ég mun vera feginn að sjá tillögur til að bæta stíl við að skrifa greinar og óska ​​þess að þú náir færri villum þegar þú skrifar handrit =)

Listi yfir tengla sem gætu verið gagnlegir í þema eða einfaldlega svarað spurningum:

Heimild: www.habr.com

Bæta við athugasemd