Erstellt Google Benotzer vu PowerShell iwwer API

Hallo!

Dësen Artikel wäert d'Ëmsetzung vun der PowerShell Interaktioun mat der Google API beschreiwen fir G Suite Benotzer ze manipuléieren.

Mir benotzen verschidden intern a Cloud Servicer uechter d'Organisatioun. Fir de gréissten Deel kënnt d'Autorisatioun an hinnen erof op Google oder Active Directory, tëscht deenen mir keng Replika kënnen erhalen; deementspriechend, wann en neie Mataarbechter verléisst, musst Dir e Kont an dësen zwee Systemer erstellen/aktivéieren. Fir de Prozess ze automatiséieren, hu mir décidéiert e Skript ze schreiwen, deen Informatioun sammelt a se u béid Servicer schéckt.

Autorisatioun

Beim Opstellung vun den Ufuerderunge hu mir décidéiert fir richteg mënschlech Administrateuren fir d'Autorisatioun ze benotzen; Dëst vereinfacht d'Analyse vun Aktiounen am Fall vun zoufälleg oder virsiichteg massiv Ännerungen.

Google APIs benotzen den OAuth 2.0 Protokoll fir Authentifikatioun an Autorisatioun. Benotzungsfäll a méi detailléiert Beschreiwunge kënnen hei fonnt ginn: Benotzt OAuth 2.0 fir Zougang zu Google APIen.

Ech hunn de Skript gewielt deen fir Autorisatioun an Desktop Uwendungen benotzt gëtt. Et gëtt och eng Optioun fir e Servicekonto ze benotzen, deen net onnéideg Beweegunge vum Benotzer erfuerdert.

D'Bild hei drënner ass eng schematesch Beschreiwung vum ausgewielten Szenario vun der Google Säit.

Erstellt Google Benotzer vu PowerShell iwwer API

  1. Als éischt schécken mir de Benotzer op d'Google Account Authentifikatiounssäit, mat GET Parameteren spezifizéieren:
    • Applikatioun ID
    • Beräicher op déi d'Applikatioun Zougang brauch
    • d'Adress op déi de Benotzer nom Ofschloss vun der Prozedur ëmgeleet gëtt
    • de Wee wäerte mir den Token aktualiséieren
    • Sécherheets Code
    • Verifikatioun Code Transmissioun Format

  2. Nodeems d'Autorisatioun ofgeschloss ass, gëtt de Benotzer op d'Säit, déi an der éischter Ufro spezifizéiert ass, ëmgeleet, mat engem Feeler oder Autorisatiounscode vun GET Parameteren iwwerginn
  3. D'Applikatioun (Skript) muss dës Parameteren kréien an, wann de Code kritt, déi folgend Ufro maachen fir Tokens ze kréien
  4. Wann d'Ufro richteg ass, gëtt d'Google API zréck:
    • Zougang Token mat deem mir Ufroe maache kënnen
    • D'Validitéit Period vun dësem Token
    • Erfrëschen Token erfuerderlech fir den Access Token z'erfrëschen.

Als éischt musst Dir op d'Google API Konsole goen: Umeldungsinformatiounen - Google API Konsol, wielt déi gewënschte Applikatioun an an der Umeldungssektioun erstellt e Client OAuth Identifizéierer. Do (oder spéider, an den Eegeschafte vum erstallten Identifizéierer) musst Dir d'Adressen spezifizéieren, op déi d'Redirectioun erlaabt ass. An eisem Fall sinn dës e puer localhost Entréen mat verschiddene Ports (kuckt hei ënnen).

Fir et méi bequem ze maachen de Skript Algorithmus ze liesen, kënnt Dir déi éischt Schrëtt an enger separater Funktioun weisen, déi den Zougang an d'Tokens fir d'Applikatioun erfrëscht:

$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

Mir setzen d'Client ID an d'Client Secret kritt an den OAuth Client Identifizéierer Eegeschaften, an de Code Verifizéierer ass eng String vun 43 bis 128 Zeechen, déi zoufälleg aus onreservéierte Charaktere generéiert musse ginn: [AZ] / [az] / [0-9] / "-" / "." / "_" / "~".

Dëse Code gëtt dann erëm iwwerdroen. Et eliminéiert d'Schwachheet, an där en Ugräifer eng Äntwert kéint ofbriechen, déi als Viruleedung no der Benotzerautorisatioun zréckkoum.
Dir kënnt e Code Verifizéierer an der aktueller Ufro am Kloertext schécken (wat et sënnlos mécht - dëst ass nëmme gëeegent fir Systemer déi SHA256 net ënnerstëtzen), oder andeems Dir en Hash mam SHA256 Algorithmus erstellt, deen an BASE64Url kodéiert muss ginn (ënnerscheedend). aus Base64 duerch zwee Tabelle Charaktere) an d'Enn vun den Zeechelinnen erofhuelen: =.

Als nächst musse mir ufänken http op der lokaler Maschinn ze lauschteren fir eng Äntwert no der Autorisatioun ze kréien, déi als Viruleedung zréckgeet.

Administrativ Aufgaben ginn op engem speziellen Server ausgefouert, mir kënnen d'Méiglechkeet net ausschléissen datt verschidde Administrateuren de Skript zur selwechter Zäit lafen, sou datt et zoufälleg e Port fir den aktuelle Benotzer wielt, awer ech hunn virdefinéiert Ports spezifizéiert well se mussen och dobäi ginn als vertraut an der API Konsole.

access_type=offline heescht datt d'Applikatioun en ofgelafte Token eleng ouni Benotzerinteraktioun mam Browser aktualiséieren kann,
response_type=code setzt d'Format fest wéi de Code zréckgeet (eng Referenz op déi al Autorisatiounsmethod, wann de Benotzer de Code vum Browser an d'Skript kopéiert huet),
Ëmfang weist den Ëmfang an Aart vum Zougang un. Si musse vu Plazen oder %20 getrennt sinn (no URL Kodéierung). Eng Lëscht vun Zougangsberäicher mat Typen kann hei gesi ginn: OAuth 2.0 Scopes fir Google APIen.

Nodeems Dir den Autorisatiounscode kritt hutt, gëtt d'Applikatioun en enke Message un de Browser zréck, stoppt um Hafen ze lauschteren a schéckt eng POST Ufro fir den Token ze kréien. Mir weisen an der virdru spezifizéierter ID a Geheimnis vun der Konsol API un, d'Adress op déi de Benotzer ëmgeleet gëtt a grant_type am Aklang mat der Protokollspezifikatioun.

Als Äntwert kréie mir en Access Token, seng Validitéitsperiod a Sekonnen, an e Refresh Token, mat deem mir den Access Token aktualiséieren.

D'Applikatioun muss Tokens op enger sécherer Plaz mat enger laanger Haltbarkeet späicheren, also bis mir den Zougang zréckzéien, gëtt d'Applikatioun den Erfrëschungs-Token net zréck. Um Enn hunn ech eng Demande bäigefüügt fir den Token zréckzezéien; wann d'Applikatioun net erfollegräich ofgeschloss gouf an d'Erfrëschungstoken net zréckginn, fänkt d'Prozedur erëm un (mir hunn et als onsécher ugesinn Tokens lokal um Terminal ze späicheren, a mir maachen net Wëllt d'Saachen net mat Kryptografie komplizéiere oder de Browser dacks opmaachen).

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
}

Wéi Dir scho gemierkt hutt, wann Dir en Token zréckzitt, gëtt Invoke-WebRequest benotzt. Am Géigesaz zu Invoke-RestMethod gëtt et déi kritt Donnéeën net an engem benotzbare Format zréck a weist de Status vun der Ufro.

Als nächst freet de Skript Iech den Virnumm an de Familljennumm vum Benotzer anzeginn, a generéiert e Login + E-Mail.

Demanden

Déi nächst Ufroe wäerten sinn - als éischt musst Dir kucken ob e Benotzer mat deemselwechte Login scho existéiert fir eng Entscheedung ze kréien fir en neien ze kreéieren oder deen aktuellen z'aktivéieren.

Ech hu beschloss all Ufroen am Format vun enger Funktioun mat enger Auswiel ëmzesetzen, mat Schalter:

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

An all Ufro musst Dir en Autorisatiouns-Header schécken deen den Token-Typ an den Access Token selwer enthält. De Moment ass den Token Typ ëmmer Bearer. Well mir mussen iwwerpréiwen datt den Token net ofgelaf ass an et no enger Stonn aktualiséieren aus dem Moment wou se erausginn ass, hunn ech eng Ufro fir eng aner Funktioun uginn déi en Access Token zréckkënnt. Datselwecht Stéck Code ass um Ufank vum Skript wann Dir den éischten Access Token kritt:

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
}

Iwwerpréift de Login fir d'Existenz:

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
}

D'E-Mail:$Query Ufro freet d'API fir e Benotzer mat genau deem Email ze sichen, inklusiv Aliasen. Dir kënnt och Wildcard benotzen: =, :, :{PREFIX}*.

Fir Daten ze kréien, benotzt d'GET Ufro Method, fir Daten anzeginn (e Kont erstellen oder e Member an e Grupp bäidroen) - POST, fir existent Donnéeën ze aktualiséieren - PUT, fir e Rekord ze läschen (zum Beispill e Member aus enger Grupp) - LËSCHT.

D'Skript freet och eng Telefonsnummer (eng net validéiert String) a fir Inklusioun an enger regionaler Verdeelungsgrupp. Et entscheet wéi eng organisatoresch Eenheet de Benotzer soll baséieren op der gewielter Active Directory OU a kënnt mat engem Passwuert:

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"

An da fänkt hien un de Kont ze manipuléieren:

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

D'Funktioune fir d'Aktualiséierung an d'Erstelle vun engem Kont hunn eng ähnlech Syntax; net all zousätzlech Felder sinn erfuerderlech; an der Rubrik mat Telefonsnummeren musst Dir eng Array spezifizéieren déi bis zu engem Rekord mat der Nummer a senger Aart enthält.

Fir kee Feeler ze kréien wann Dir e Benotzer an e Grupp bäidréit, kënne mir fir d'éischt kucken ob hien scho Member vun dëser Grupp ass, andeems Dir eng Lëscht vun de Gruppememberen oder d'Zesummesetzung vum Benotzer selwer kritt.

Ufro vun der Grupp Memberschaft vun engem spezifesche Benotzer wäert net rekursiv sinn a weist nëmmen direkt Memberschaft. E Benotzer an enger Elteregrupp abegraff, déi schonn e Kannergrupp huet, vun deem de Benotzer Member ass, wäert erfollegräich sinn.

Konklusioun

Alles wat bleift ass dem Benotzer d'Passwuert fir den neie Kont ze schécken. Mir maachen dat iwwer SMS, a schécken allgemeng Informatioune mat Instruktiounen a Login op eng perséinlech E-Mail, déi zesumme mat enger Telefonsnummer vum Recrutementsdepartement geliwwert gouf. Als Alternativ kënnt Dir Sue spueren an Äert Passwuert op e geheime Telegram Chat schécken, deen och als zweet Faktor ugesi ka ginn (MacBooks wäert eng Ausnam sinn).

Merci fir d'Liesen bis zum Schluss. Ech wäert frou sinn Suggestioune ze gesinn fir de Stil vun Artikelen ze schreiwen a wënschen Iech manner Feeler beim Schreiwen vun Skripte ze fangen =)

Lëscht vu Linken déi thematesch nëtzlech kënne sinn oder einfach Froen beäntweren:

Source: will.com

Setzt e Commentaire