API арқылы PowerShell қолданбасынан Google пайдаланушыларын жасау

Сәлем!

Бұл мақалада G Suite пайдаланушыларын басқару үшін Google API көмегімен PowerShell өзара әрекеттесуінің жүзеге асырылуы сипатталады.

Біз ұйымда бірнеше ішкі және бұлттық қызметтерді қолданамыз. Көбінесе олардағы авторизация Google немесе Active Directory-ге түседі, олардың арасында біз көшірме жасай алмаймыз; сәйкесінше, жаңа қызметкер кеткен кезде осы екі жүйеде тіркелгі жасау/қосу қажет. Процесті автоматтандыру үшін біз ақпаратты жинайтын және оны екі қызметке жіберетін сценарий жазуды шештік.

Авторизация

Талаптарды құрастыру кезінде біз авторизациялау үшін нақты адам әкімшілерін пайдалануды ұйғардық, бұл кездейсоқ немесе әдейі жаппай өзгерістер болған жағдайда әрекеттерді талдауды жеңілдетеді.

Google API интерфейстері аутентификация және авторизация үшін OAuth 2.0 протоколын пайдаланады. Пайдалану жағдайлары мен толығырақ сипаттамаларды мына жерден табуға болады: Google API интерфейстеріне кіру үшін OAuth 2.0 пайдалану.

Мен жұмыс үстелі қолданбаларында авторизациялау үшін пайдаланылатын сценарийді таңдадым. Сондай-ақ, пайдаланушыдан қажетсіз қозғалыстарды қажет етпейтін қызмет тіркелгісін пайдалану мүмкіндігі бар.

Төмендегі суретте Google бетіндегі таңдалған сценарийдің схемалық сипаттамасы берілген.

API арқылы PowerShell қолданбасынан Google пайдаланушыларын жасау

  1. Алдымен біз GET параметрлерін көрсете отырып, пайдаланушыны Google тіркелгісінің аутентификация бетіне жібереміз:
    • қолданба идентификаторы
    • қолданбаға кіру қажет аймақтар
    • процедураны аяқтағаннан кейін пайдаланушы қайта бағытталатын мекенжай
    • таңбалауышты жаңарту жолы
    • Қауіпсіздік коды
    • тексеру кодын жіберу пішімі

  2. Авторизация аяқталғаннан кейін пайдаланушы GET параметрлері арқылы жіберілген қате немесе авторизация коды бар бірінші сұрауда көрсетілген бетке қайта бағытталады.
  3. Қолданба (скрипт) осы параметрлерді алуы керек және кодты алған жағдайда белгілерді алу үшін келесі сұрауды жасау керек.
  4. Сұрау дұрыс болса, Google API қайтарады:
    • Сұраулар жасай алатын рұқсат белгісі
    • Бұл белгінің жарамдылық мерзімі
    • Access таңбалауышын жаңарту үшін жаңарту таңбалауышы қажет.

Алдымен Google API консоліне өту керек: Тіркелгі деректері - Google API консолі, қажетті қолданбаны таңдап, Тіркелгі деректері бөлімінде клиент OAuth идентификаторын жасаңыз. Онда (немесе кейінірек, жасалған идентификатордың сипаттарында) қайта бағыттауға рұқсат етілген мекенжайларды көрсету керек. Біздің жағдайда бұл әртүрлі порттары бар бірнеше жергілікті хост жазбалары болады (төменде қараңыз).

Сценарий алгоритмін оқуды ыңғайлы ету үшін Access қайтаратын және қолданбаға арналған таңбалауыштарды жаңартатын бөлек функциядағы алғашқы қадамдарды көрсетуге болады:

$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

Біз OAuth клиент идентификаторының сипаттарында алынған Клиент идентификаторын және Клиент құпиясын орнатамыз және кодты тексеруші резервтелмеген таңбалардан кездейсоқ түрде жасалуы тиіс 43-128 таңбалар жолы болып табылады: [AZ] / [az] / [0-9 ] / "-" / "." / "_" / "~".

Содан кейін бұл код қайтадан жіберіледі. Ол пайдаланушы авторизациясынан кейін қайта бағыттау ретінде қайтарылған жауапты шабуылдаушы ұстап алуы мүмкін осалдықты жояды.
Ағымдағы сұрауда код тексерушісін анық мәтінде (бұл оны мағынасыз етеді - бұл тек SHA256 қолдамайтын жүйелер үшін жарамды) немесе BASE256Url кодында (әртүрлі) кодталуы керек SHA64 алгоритмін пайдаланып хэш жасау арқылы жіберуге болады. Base64 ішінен екі кесте таңбасымен) және таңба жолының соңын алып тастау: =.

Әрі қарай, қайта бағыттау ретінде қайтарылатын авторизациядан кейін жауап алу үшін жергілікті құрылғыда http тыңдауды бастау керек.

Әкімшілік тапсырмалар арнайы серверде орындалады, біз сценарийді бірнеше әкімшінің бір уақытта іске қосу мүмкіндігін жоққа шығара алмаймыз, сондықтан ол ағымдағы пайдаланушы үшін портты кездейсоқ таңдайды, бірақ мен алдын ала анықталған порттарды көрсеттім, себебі олар сонымен қатар API консолінде сенімді ретінде қосылуы керек.

access_type=офлайн бұл қолданба мерзімі өткен таңбалауышты пайдаланушының браузермен әрекеттесуінсіз өздігінен жаңарта алатынын білдіреді,
жауап_түрі=код кодты қайтару пішімін орнатады (қолданушы кодты браузерден сценарийге көшірген кездегі ескі авторизация әдісіне сілтеме),
ауқымы қол жеткізу көлемі мен түрін көрсетеді. Олар бос орындармен немесе % 20 (URL кодтауына сәйкес) арқылы бөлінуі керек. Түрлері бар кіру аймақтарының тізімін мына жерден көруге болады: Google API интерфейстеріне арналған OAuth 2.0 аумақтары.

Авторизация кодын алғаннан кейін қолданба браузерге жақын хабарды қайтарады, портты тыңдауды тоқтатады және таңбалауышты алу үшін POST сұрауын жібереді. Біз онда алдын ала көрсетілген идентификаторды және консоль API құпиясын, пайдаланушы қайта бағытталатын мекенжайды және протокол спецификациясына сәйкес grant_type көрсетеміз.

Жауап ретінде біз Access таңбалауышын, оның жарамдылық мерзімін секундтарда және Жаңарту таңбалауышын аламыз, оның көмегімен Access таңбалауышын жаңартуға болады.

Қолданба токендерді ұзақ сақтау мерзімі бар қауіпсіз жерде сақтауы керек, сондықтан біз алынған рұқсатты жоймайынша, қолданба жаңарту таңбалауышын қайтармайды. Соңында мен маркерді қайтарып алу туралы сұрауды қостым; егер қолданба сәтті аяқталмаса және жаңарту таңбалауышы қайтарылмаса, ол процедураны қайта бастайды (жетондарды терминалда жергілікті түрде сақтау қауіпті деп санадық және біз қолданбаймыз. криптографиямен қиындатқыңыз келмейді немесе браузерді жиі ашқыңыз келмейді).

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
}

Байқағаныңыздай, таңбалауышты жою кезінде Invoke-WebRequest пайдаланылады. Invoke-RestMethod-тен айырмашылығы, ол алынған деректерді қолдануға жарамды пішімде қайтармайды және сұраудың күйін көрсетеді.

Әрі қарай, сценарий пайдаланушының атын және тегін енгізуді сұрайды, логин + электрондық поштаны жасайды.

сұраулар

Келесі сұраулар келесідей болады - ең алдымен, жаңасын жасау немесе ағымдағыны қосу туралы шешімді алу үшін бірдей логині бар пайдаланушының бұрыннан бар-жоғын тексеру керек.

Мен барлық сұрауларды бір функция пішіміндегі таңдау арқылы ауыстырып қосуды шештім:

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

Әрбір сұрауда таңбалауыш түрі мен Access таңбалауышының өзін қамтитын Авторизация тақырыбын жіберу керек. Қазіргі уақытта таңбалауыш түрі әрқашан Bearer болып табылады. Өйткені біз таңбалауыштың жарамдылық мерзімінің аяқталмағанын тексеруіміз керек және оны шығарылған сәттен бастап бір сағаттан кейін жаңартуымыз керек, мен Access таңбалауышын қайтаратын басқа функцияға сұрауды көрсеттім. Бірдей код бөлігі бірінші 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
}

Логиннің бар-жоғын тексеру:

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
}

Электрондық пошта:$query сұрауы API-дан дәл сол электрондық поштасы бар пайдаланушыны, соның ішінде бүркеншік аттары бар пайдаланушыны іздеуді сұрайды. Сондай-ақ қойылмалы таңбаны пайдалануға болады: =, :, :{PREFIX}*.

Деректерді алу үшін GET сұрау әдісін пайдаланыңыз, деректерді енгізу үшін (тіркелгі жасау немесе топқа мүше қосу) - POST, бар деректерді жаңарту үшін - PUT, жазбаны жою (мысалы, топтағы мүше) - ЖОЮ.

Сондай-ақ сценарий телефон нөмірін (тексерілмеген жол) және аймақтық тарату тобына қосуды сұрайды. Ол таңдалған Active Directory OU негізінде пайдаланушыда қандай ұйымдық бөлімше болуы керектігін шешеді және құпия сөзді ұсынады:

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"

Содан кейін ол есептік жазбаны манипуляциялай бастайды:

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

Тіркелгіні жаңарту және құру функциялары ұқсас синтаксиске ие; барлық қосымша өрістер талап етілмейді; телефон нөмірлері бар бөлімде нөмір және оның түрі бар бір жазбаға дейін болуы мүмкін массивді көрсету керек.

Пайдаланушыны топқа қосу кезінде қатені алмау үшін алдымен пайдаланушының өзінен топ мүшелерінің тізімін немесе құрамын алу арқылы оның осы топтың мүшесі екенін тексере аламыз.

Белгілі бір пайдаланушының топ мүшелігін сұрау рекурсивті болмайды және тек тікелей мүшелікті көрсетеді. Пайдаланушы мүшесі болып табылатын еншілес тобы бар пайдаланушыны негізгі топқа қосу сәтті болады.

қорытынды

Пайдаланушыға жаңа тіркелгі үшін құпия сөзді жіберу ғана қалады. Біз мұны SMS арқылы жасаймыз және нұсқаулықпен және жеке электрондық поштаға логинмен жалпы ақпаратты жібереміз, оны телефон нөмірімен бірге жұмысқа қабылдау бөлімі берген. Балама ретінде сіз ақша үнемдей аласыз және құпия телеграмма чатына құпия сөзіңізді жібере аласыз, оны екінші фактор деп санауға болады (MacBooks ерекшелік болады).

Соңына дейін оқығаныңызға рахмет. Мақалаларды жазу стилін жақсарту бойынша ұсыныстарды көруге қуаныштымын және сценарий жазу кезінде қателерді азайтуды тілеймін =)

Тақырыптық тұрғыдан пайдалы немесе жай ғана сұрақтарға жауап беретін сілтемелер тізімі:

Ақпарат көзі: www.habr.com

пікір қалдыру