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 Каттоо эсебинин аныктыгын текшерүү барагына жөнөтөбүз:
    • колдонмо ID
    • Колдонмого кирүүгө муктаж болгон аймактар
    • процедура аяктагандан кийин колдонуучу багыттала турган дарек
    • токенди жаңыртуу жолу
    • Коопсуздук коду
    • текшерүү кодун өткөрүү форматы

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

Алгач сиз Google API консолуна барышыңыз керек: Каттоо маалыматтары - Google API Console, керектүү тиркемени тандап, Каттоо маалыматтары бөлүмүндө кардар 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=offline Колдонмо колдонуучунун браузер менен өз ара аракеттешүүсүз мөөнөтү өтүп кеткен белгини өз алдынча жаңырта алат дегенди билдирет,
жооп_түрү=код кодду кайтарып берүүнүн форматын белгилейт (колдонуучу кодду браузерден скриптке көчүргөндө эски авторизация ыкмасына шилтеме),
масштабы кирүүнүн көлөмүн жана түрүн көрсөтөт. Алар боштук же %20 (URL коддоосуна ылайык) менен бөлүнүшү керек. түрлөрү менен кирүү аймактарынын тизмесин бул жерден көрүүгө болот: Google API'лери үчүн OAuth 2.0 Scopes.

Авторизация кодун алгандан кийин, колдонмо браузерге жакын билдирүүнү кайтарып берет, портту угууну токтотот жана белгини алуу үчүн POST суроо-талапты жөнөтөт. Биз анда консол API'ден мурда көрсөтүлгөн идентификаторду жана сырды, колдонуучу багыттала турган даректи жана протоколдун спецификациясына ылайык grant_type көрсөтөбүз.

Жооп катары биз мүмкүндүк алуу токенин, анын жарактуу мөөнөтүн секунддарда жана Жаңыртуу белгисин алабыз, анын жардамы менен биз 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 өзгөчө болот).

Аягына чейин окуганыңыз үчүн рахмат. Макала жазуу стилин жакшыртуу боюнча сунуштарды көргөнүмө кубанычтамын жана скрипттерди жазууда каталарды азыраак болушуңуздарды каалайм =)

Тематикалык жактан пайдалуу же жөн гана суроолорго жооп бере турган шилтемелердин тизмеси:

Source: www.habr.com

Комментарий кошуу