API vasitəsilə PowerShell-dən Google İstifadəçilərinin yaradılması

Привет!

Bu məqalə G Suite istifadəçilərini manipulyasiya etmək üçün Google API ilə PowerShell qarşılıqlı əlaqəsinin həyata keçirilməsini təsvir edəcək.

Biz təşkilat daxilində bir neçə daxili və bulud xidmətlərindən istifadə edirik. Əksər hallarda, onlarda avtorizasiya Google və ya Active Directory-yə düşür, onların arasında biz replika saxlaya bilmirik; müvafiq olaraq, yeni işçi ayrıldıqda, bu iki sistemdə hesab yaratmalı/aktiv etməlisiniz. Prosesi avtomatlaşdırmaq üçün məlumat toplayan və hər iki xidmətə göndərən skript yazmaq qərarına gəldik.

Icazə

Tələbləri tərtib edərkən, avtorizasiya üçün həqiqi insan idarəçilərindən istifadə etmək qərarına gəldik; bu, təsadüfi və ya qəsdən kütləvi dəyişikliklər zamanı hərəkətlərin təhlilini asanlaşdırır.

Google API-ləri autentifikasiya və avtorizasiya üçün OAuth 2.0 protokolundan istifadə edir. İstifadə halları və daha ətraflı təsvirləri burada tapa bilərsiniz: Google API-lərə daxil olmaq üçün OAuth 2.0 istifadə edin.

Desktop proqramlarında avtorizasiya üçün istifadə olunan skripti seçdim. İstifadəçidən lazımsız hərəkətlər tələb etməyən xidmət hesabından istifadə imkanı da var.

Aşağıdakı şəkil Google səhifəsindən seçilmiş ssenarinin sxematik təsviridir.

API vasitəsilə PowerShell-dən Google İstifadəçilərinin yaradılması

  1. Əvvəlcə istifadəçini GET parametrlərini göstərərək Google Hesabının autentifikasiyası səhifəsinə göndəririk:
    • proqram id
    • tətbiqin girişə ehtiyac duyduğu sahələr
    • proseduru tamamladıqdan sonra istifadəçinin yönləndiriləcəyi ünvan
    • tokeni necə yeniləyəcəyik
    • Təhlükəsizlik kodu
    • doğrulama kodunun ötürülməsi formatı

  2. Avtorizasiya tamamlandıqdan sonra istifadəçi səhv və ya GET parametrləri tərəfindən ötürülən icazə kodu ilə ilk sorğuda göstərilən səhifəyə yönləndiriləcək.
  3. Tətbiq (skript) bu parametrləri almalı və kodu aldıqda, tokenləri əldə etmək üçün aşağıdakı sorğunu göndərməlidir.
  4. Sorğu düzgündürsə, Google API qaytarır:
    • Sorğular edə biləcəyimiz giriş nişanı
    • Bu tokenin etibarlılıq müddəti
    • Access tokenini yeniləmək üçün yeniləmə nişanı tələb olunur.

Əvvəlcə Google API konsoluna keçməlisiniz: Etibarnamələr - Google API Konsolu, istədiyiniz proqramı seçin və Etibarnamələr bölməsində müştəri OAuth identifikatorunu yaradın. Orada (və ya daha sonra yaradılmış identifikatorun xüsusiyyətlərində) yönləndirməyə icazə verilən ünvanları göstərməlisiniz. Bizim vəziyyətimizdə bunlar müxtəlif portları olan bir neçə localhost girişi olacaq (aşağıya bax).

Skript alqoritmini oxumağı daha rahat etmək üçün siz Access-i qaytaracaq və proqram üçün nişanları yeniləyəcək ayrıca funksiyada ilk addımları göstərə bilərsiniz:

$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

Biz OAuth müştəri identifikatoru xassələrində əldə edilmiş Müştəri ID-sini və Müştəri Sirrini təyin etdik və kod yoxlayıcısı ehtiyatsız simvollardan təsadüfi yaradılmalı olan 43-128 simvoldan ibarət sətirdir: [AZ] / [az] / [0-9 ] / "-" / "." / "_" / "~".

Bu kod daha sonra yenidən ötürüləcək. Bu, təcavüzkarın istifadəçi icazəsindən sonra yönləndirmə kimi qaytarılan cavaba müdaxilə edə biləcəyi zəifliyi aradan qaldırır.
Siz cari sorğuda kod yoxlayıcısını aydın mətnlə (bu onu mənasız edir - bu, yalnız SHA256-nı dəstəkləməyən sistemlər üçün uyğundur) və ya BASE256Url-də kodlaşdırılmalı olan SHA64 alqoritmindən istifadə edərək hash yarada bilərsiniz (fərqli Base64-dən iki cədvəl simvolu ilə) və simvol sətir sonlarının çıxarılması: =.

Sonra, avtorizasiyadan sonra yönləndirmə kimi qaytarılacaq cavabı almaq üçün yerli maşında http dinləməyə başlamalıyıq.

İnzibati tapşırıqlar xüsusi serverdə yerinə yetirilir, biz bir neçə administratorun skripti eyni vaxtda işlətməsini istisna edə bilmərik, ona görə də o, təsadüfi olaraq cari istifadəçi üçün port seçəcək, lakin mən əvvəlcədən təyin edilmiş portları göstərdim, çünki onlar həmçinin API konsolunda etibarlı kimi əlavə edilməlidir.

access_type=offline o deməkdir ki, tətbiq istifadəçinin brauzerlə əlaqəsi olmadan vaxtı keçmiş nişanı təkbaşına yeniləyə bilər,
cavab_tipi=kod kodun necə qaytarılacağı formatını təyin edir (istifadəçi kodu brauzerdən skriptə köçürdüyü zaman köhnə avtorizasiya metoduna istinad),
vüsət girişin əhatə dairəsini və növünü göstərir. Onlar boşluq və ya %20 ilə ayrılmalıdır (URL Kodlamasına uyğun olaraq). Növləri olan giriş sahələrinin siyahısını burada görmək olar: Google API üçün OAuth 2.0 Scopes.

Avtorizasiya kodunu aldıqdan sonra proqram brauzerə yaxın mesaj qaytaracaq, portu dinləməyi dayandıracaq və nişanı əldə etmək üçün POST sorğusu göndərəcək. Biz orada əvvəlcədən müəyyən edilmiş id və konsol API-dən sirri, istifadəçinin yönləndiriləcəyi ünvanı və protokol spesifikasiyasına uyğun olaraq grant_type qeyd edirik.

Cavab olaraq biz Access tokenini, onun etibarlılıq müddətini saniyələrlə və Refresh tokenini alacağıq, onun köməyi ilə Access tokenini yeniləyə bilərik.

Tətbiq tokenləri uzun rəf ömrü ilə etibarlı yerdə saxlamalıdır, buna görə də qəbul edilmiş girişi ləğv edənə qədər proqram yeniləmə nişanını qaytarmayacaq. Sonda mən tokeni ləğv etmək üçün sorğu əlavə etdim; ərizə uğurla tamamlanmayıbsa və yeniləmə nişanı geri qaytarılmasa, o, proseduru yenidən başlayacaq (biz terminalda tokenləri yerli olaraq saxlamağı təhlükəli hesab etdik və biz bunu etmirik. kriptoqrafiya ilə işləri çətinləşdirmək və ya brauzeri tez-tez açmaq istəmirəm).

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
}

Artıq qeyd etdiyiniz kimi, token ləğv edilərkən, Invoke-WebRequest istifadə olunur. Invoke-RestMethod-dan fərqli olaraq, o, qəbul edilmiş məlumatları lazımlı formatda qaytarmır və sorğunun vəziyyətini göstərir.

Sonra, skript sizdən giriş + e-poçt yaradaraq istifadəçinin adını və soyadını daxil etməyinizi xahiş edir.

İstək

Növbəti sorğular olacaq - ilk növbədə, yenisini yaratmaq və ya carisini aktivləşdirmək barədə qərar qəbul etmək üçün eyni girişə malik istifadəçinin artıq mövcud olub-olmadığını yoxlamaq lazımdır.

Bütün sorğuları keçiddən istifadə edərək seçimlə bir funksiya formatında həyata keçirmək qərarına gəldim:

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

Hər sorğuda siz işarə növünü və Giriş nişanının özündən ibarət Avtorizasiya başlığını göndərməlisiniz. Hal-hazırda, token növü həmişə Daşıyıcıdır. Çünki tokenin istifadə müddəti bitmədiyini yoxlamaq və buraxıldığı andan bir saat sonra onu yeniləmək lazımdır, mən Access tokenini qaytaran başqa funksiya üçün sorğu göstərmişəm. İlk Access tokenini qəbul edərkən eyni kod parçası skriptin əvvəlindədir:

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
}

Girişin mövcudluğu yoxlanılır:

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-poçt:$query sorğusu API-dən ləqəblər daxil olmaqla, məhz həmin e-poçtu olan istifadəçini axtarmağı xahiş edəcək. Siz həmçinin joker işarədən istifadə edə bilərsiniz: =, :, :{PREFIX}*.

Məlumat əldə etmək üçün GET sorğu metodundan istifadə edin, məlumat daxil edin (hesab yaratmaq və ya qrupa üzv əlavə etmək) - POST, mövcud məlumatları yeniləmək üçün - PUT, qeydi silmək (məsələn, qrupdan üzv) - SİLİN.

Skript həmçinin telefon nömrəsini (təsdiqlənməmiş sətir) və regional paylama qrupuna daxil edilməsini tələb edəcək. O, seçilmiş Active Directory OU əsasında istifadəçinin hansı təşkilat bölməsinə malik olacağına qərar verir və parol ilə çıxış edir:

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"

Və sonra hesabı manipulyasiya etməyə başlayır:

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

Hesabın yenilənməsi və yaradılması funksiyaları oxşar sintaksisə malikdir; bütün əlavə sahələr tələb olunmur; telefon nömrələri olan bölmədə nömrə və onun növü ilə bir qədər qeydi ehtiva edə biləcək bir sıra göstərməlisiniz.

Qrupa istifadəçi əlavə edərkən xəta almamaq üçün əvvəlcə istifadəçinin özündən qrup üzvlərinin siyahısını və ya tərkibini əldə etməklə onun artıq bu qrupa üzv olub-olmadığını yoxlaya bilərik.

Xüsusi istifadəçinin qrup üzvlüyünün sorğulanması rekursiv olmayacaq və yalnız birbaşa üzvlük göstərəcək. İstifadəçinin artıq üzvü olduğu alt qrupa malik olan istifadəçinin ana qrupa daxil edilməsi uğur qazanacaq.

Nəticə

Qalan yalnız istifadəçiyə yeni hesab üçün parol göndərməkdir. Biz bunu SMS vasitəsilə edirik və təlimatlarla ümumi məlumat göndəririk və telefon nömrəsi ilə birlikdə işə qəbul şöbəsi tərəfindən verilmiş şəxsi e-poçta daxil oluruq. Alternativ olaraq, siz pula qənaət edə və şifrənizi gizli teleqram çatına göndərə bilərsiniz, bu da ikinci amil sayıla bilər (MacBooks istisna olacaq).

Sona qədər oxuduğunuz üçün təşəkkür edirəm. Məqalələrin yazılma tərzini yaxşılaşdırmaq üçün təklifləri görməkdən şad olaram və skript yazarkən daha az səhvə yol vermənizi arzulayıram =)

Tematik cəhətdən faydalı ola biləcək və ya sadəcə suallara cavab verə biləcək bağlantıların siyahısı:

Mənbə: www.habr.com

Добавить комментарий