API orqali PowerShell'dan Google foydalanuvchilarini yaratish

Salom!

Ushbu maqola G Suite foydalanuvchilarini manipulyatsiya qilish uchun Google API bilan PowerShell o'zaro ta'sirini ta'riflaydi.

Biz tashkilot bo'ylab bir nechta ichki va bulutli xizmatlardan foydalanamiz. Ko'pincha, ulardagi avtorizatsiya Google yoki Active Directory-ga tushadi, biz ularning o'rtasida nusxa ko'rsata olmaymiz; shunga ko'ra, yangi xodim ketganda, siz ushbu ikki tizimda hisob yaratish/yoqishingiz kerak. Jarayonni avtomatlashtirish uchun biz ma'lumot to'playdigan va uni ikkala xizmatga yuboradigan skript yozishga qaror qildik.

Avtorizatsiya

Talablarni ishlab chiqishda biz avtorizatsiya uchun haqiqiy inson ma'murlaridan foydalanishga qaror qildik, bu tasodifiy yoki qasddan katta o'zgarishlar yuz bergan taqdirda harakatlar tahlilini soddalashtiradi.

Google API’lari autentifikatsiya va avtorizatsiya uchun OAuth 2.0 protokolidan foydalanadi. Foydalanish holatlari va batafsil tavsiflarni bu yerda topishingiz mumkin: Google API-ga kirish uchun OAuth 2.0 dan foydalanish.

Ish stoli ilovalarida avtorizatsiya uchun ishlatiladigan skriptni tanladim. Bundan tashqari, foydalanuvchidan keraksiz harakatlarni talab qilmaydigan xizmat hisobidan foydalanish imkoniyati mavjud.

Quyidagi rasmda Google sahifasidan tanlangan stsenariyning sxematik tavsifi berilgan.

API orqali PowerShell'dan Google foydalanuvchilarini yaratish

  1. Birinchidan, biz foydalanuvchini GET parametrlarini ko'rsatgan holda Google hisobi autentifikatsiya sahifasiga yuboramiz:
    • ilova identifikatori
    • ilova kirishga muhtoj bo'lgan joylar
    • protsedurani tugatgandan so'ng foydalanuvchi yo'naltiriladigan manzil
    • tokenni yangilash usuli
    • Xavfsizlik kodi
    • tasdiqlash kodini uzatish formati

  2. Avtorizatsiya tugagandan so'ng, foydalanuvchi GET parametrlari orqali xato yoki avtorizatsiya kodi bilan birinchi so'rovda ko'rsatilgan sahifaga yo'naltiriladi.
  3. Ilova (skript) ushbu parametrlarni olishi va agar kodni olgan bo'lsa, tokenlarni olish uchun quyidagi so'rovni bajarishi kerak.
  4. Agar so'rov to'g'ri bo'lsa, Google API quyidagilarni qaytaradi:
    • Biz so'rov yuborishimiz mumkin bo'lgan kirish tokeni
    • Ushbu tokenning amal qilish muddati
    • Access tokenini yangilash uchun tokenni yangilash kerak.

Avval siz Google API konsoliga o'tishingiz kerak: Hisob ma'lumotlari - Google API konsoli, kerakli dasturni tanlang va Hisob ma'lumotlari bo'limida mijoz OAuth identifikatorini yarating. U erda (yoki keyinroq yaratilgan identifikatorning xususiyatlarida) siz qayta yo'naltirishga ruxsat berilgan manzillarni ko'rsatishingiz kerak. Bizning holatda, bu turli xil portlarga ega bo'lgan bir nechta localhost yozuvlari bo'ladi (pastga qarang).

Skript algoritmini o'qishni qulayroq qilish uchun siz Access-ni qaytaradigan va ilova uchun tokenlarni yangilaydigan alohida funksiyada birinchi qadamlarni ko'rsatishingiz mumkin:

$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 mijoz identifikatori xususiyatlarida olingan mijoz identifikatori va mijoz sirini o'rnatdik va kod tekshiruvi ajratilmagan belgilardan tasodifiy yaratilishi kerak bo'lgan 43 dan 128 gacha belgilar qatoridir: [AZ] / [az] / [0-9 ] / "-" / "." / "_" / "~".

Keyin bu kod yana uzatiladi. Bu tajovuzkor foydalanuvchi avtorizatsiyasidan so'ng qayta yo'naltirish sifatida qaytarilgan javobni ushlab qolishi mumkin bo'lgan zaiflikni yo'q qiladi.
Joriy soʻrovda kod tekshirgichni aniq matnda (bu uni maʼnosiz qiladi - bu faqat SHA256-ni qoʻllab-quvvatlamaydigan tizimlar uchun mos keladi) yoki BASE256Url-da kodlangan boʻlishi kerak boʻlgan SHA64 algoritmidan foydalanib xeshni yaratish orqali yuborishingiz mumkin (farqli Base64 dan ikkita jadval belgisi bilan) va belgilar qatorining oxirini olib tashlash: =.

Keyinchalik, avtorizatsiyadan so'ng javob olish uchun mahalliy mashinada http ni tinglashni boshlashimiz kerak, u qayta yo'naltirish sifatida qaytariladi.

Ma'muriy vazifalar maxsus serverda amalga oshiriladi, biz bir vaqtning o'zida bir nechta ma'murlar skriptni ishga tushirish imkoniyatini istisno qila olmaymiz, shuning uchun u joriy foydalanuvchi uchun portni tasodifiy tanlaydi, lekin men oldindan belgilangan portlarni ko'rsatdim, chunki ular API konsolida ishonchli sifatida qo'shilishi kerak.

access_type=oflayn Bu shuni anglatadiki, ilova muddati o'tgan tokenni foydalanuvchi brauzer bilan o'zaro aloqasisiz mustaqil ravishda yangilashi mumkin,
javob_turi=kod kodni qanday qaytarish formatini o'rnatadi (foydalanuvchi kodni brauzerdan skriptga ko'chirgan eski avtorizatsiya usuliga havola),
doirasi kirish doirasi va turini ko'rsatadi. Ular bo'shliqlar yoki %20 (URL kodlash bo'yicha) bilan ajratilishi kerak. Turlari bo'lgan kirish joylari ro'yxatini bu erda ko'rish mumkin: Google API uchun OAuth 2.0 Scopes.

Avtorizatsiya kodini olgandan so'ng, dastur brauzerga yaqin xabarni qaytaradi, portni tinglashni to'xtatadi va tokenni olish uchun POST so'rovini yuboradi. Biz unda oldindan ko'rsatilgan identifikatorni va konsol API-dan sirni, foydalanuvchi qayta yo'naltiriladigan manzilni va protokol spetsifikatsiyasiga muvofiq grant_type ni ko'rsatamiz.

Bunga javoban biz Access tokenini, uning amal qilish muddatini soniyalarda va yangilash tokenini olamiz, uning yordamida Access tokenini yangilashimiz mumkin.

Ilova tokenlarni yaroqlilik muddati uzoq bo'lgan xavfsiz joyda saqlashi kerak, shuning uchun biz qabul qilingan ruxsatni bekor qilmagunimizcha, dastur yangilanish tokenini qaytarmaydi. Oxirida men tokenni bekor qilish soʻrovini qoʻshdim; agar dastur muvaffaqiyatli bajarilmasa va yangilash tokeni qaytarilmasa, u protsedurani qaytadan boshlaydi (biz tokenlarni terminalda mahalliy saqlash xavfli deb hisobladik va biz buni amalga oshirmaymiz. kriptografiya bilan ishlarni murakkablashtirish yoki brauzerni tez-tez ochishni xohlamayman).

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
}

Siz allaqachon sezganingizdek, tokenni bekor qilishda Invoke-WebRequest ishlatiladi. Invoke-RestMethod-dan farqli o'laroq, u qabul qilingan ma'lumotlarni foydali formatda qaytarmaydi va so'rovning holatini ko'rsatadi.

Keyinchalik, skript foydalanuvchining ismi va familiyasini kiritishingizni so'raydi, login + elektron pochta manzilini yaratadi.

so'rovlar

Keyingi so'rovlar bo'ladi - birinchi navbatda, yangisini yaratish yoki joriyini yoqish to'g'risida qaror qabul qilish uchun bir xil loginga ega foydalanuvchi allaqachon mavjudligini tekshirishingiz kerak.

Men barcha so'rovlarni bitta funktsiya formatida tanlash bilan, switch yordamida amalga oshirishga qaror qildim:

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

Har bir so'rovda siz token turi va Access tokenining o'zini o'z ichiga olgan Avtorizatsiya sarlavhasini yuborishingiz kerak. Hozirgi vaqtda token turi har doim Bearer hisoblanadi. Chunki biz tokenning amal qilish muddati tugamaganligini tekshirishimiz va u chiqarilgan paytdan boshlab bir soat o'tgach uni yangilashimiz kerak, men Access tokenini qaytaradigan boshqa funksiya uchun so'rovni ko'rsatdim. Xuddi shu kod qismi birinchi Access tokenini qabul qilishda skriptning boshida joylashgan:

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
}

Login mavjudligini tekshirish:

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 soʻrovi APIdan aynan shu elektron pochta manziliga ega foydalanuvchini, jumladan taxalluslarni izlashni soʻraydi. Shuningdek, joker belgidan foydalanishingiz mumkin: =, :, :{PREFIX}*.

Ma'lumotlarni olish uchun GET so'rov usulidan foydalaning, ma'lumotlarni kiritish (hisob qaydnomasini yaratish yoki guruhga a'zo qo'shish) - POST, mavjud ma'lumotlarni yangilash - PUT, yozuvni o'chirish (masalan, guruh a'zosi) - OʻCHIRISH.

Skript shuningdek, telefon raqamini (tasdiqlanmagan qator) va mintaqaviy tarqatish guruhiga qo'shishni so'raydi. U tanlangan Active Directory OU asosida foydalanuvchi qaysi tashkiliy birlikka ega bo'lishi kerakligini hal qiladi va parol bilan chiqadi:

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"

Va keyin u hisobni boshqarishni boshlaydi:

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

Hisob qaydnomasini yangilash va yaratish funktsiyalari o'xshash sintaksisga ega; barcha qo'shimcha maydonlar talab qilinmaydi, telefon raqamlari bo'limida raqam va uning turi bilan bittagacha yozuvni o'z ichiga olishi mumkin bo'lgan massivni ko'rsatish kerak.

Guruhga foydalanuvchi qo‘shishda xatolikka yo‘l qo‘ymaslik uchun avvalo foydalanuvchining o‘zidan guruh a’zolari ro‘yxatini yoki kompozitsiyani olish orqali uning ushbu guruh a’zosi yoki yo‘qligini tekshirishimiz mumkin.

Muayyan foydalanuvchining guruh a'zoligini so'rash rekursiv bo'lmaydi va faqat bevosita a'zolikni ko'rsatadi. Foydalanuvchi a'zosi bo'lgan bolalar guruhiga ega bo'lgan foydalanuvchini ota-ona guruhiga kiritish muvaffaqiyatli bo'ladi.

xulosa

Qolgan narsa foydalanuvchiga yangi hisob parolini yuborishdir. Biz buni SMS orqali amalga oshiramiz va umumiy ma'lumotlarni ko'rsatmalar bilan yuboramiz va shaxsiy elektron pochtaga kirishni amalga oshiramiz, bu telefon raqami bilan birga ishga qabul qilish bo'limi tomonidan taqdim etilgan. Shu bilan bir qatorda siz pulni tejashingiz va maxfiy telegram chatiga parolingizni yuborishingiz mumkin, bu ham ikkinchi omil hisoblanishi mumkin (MacBooks bundan mustasno).

Oxirigacha o'qiganingiz uchun rahmat. Maqola yozish uslubini yaxshilash bo'yicha takliflarni ko'rishdan xursand bo'laman va skriptlarni yozishda kamroq xatolarga yo'l qo'yishingizni tilayman =)

Tematik jihatdan foydali bo'lishi mumkin bo'lgan yoki oddiygina savollarga javob beradigan havolalar ro'yxati:

Manba: www.habr.com

a Izoh qo'shish