Google-ի օգտատերերի ստեղծում PowerShell-ից API-ի միջոցով

Hi!

Այս հոդվածը նկարագրելու է PowerShell-ի փոխազդեցության իրականացումը Google API-ի հետ՝ G Suite-ի օգտատերերին շահարկելու համար:

Մենք օգտագործում ենք մի քանի ներքին և ամպային ծառայություններ ամբողջ կազմակերպությունում: Դրանցում թույլտվությունը մեծ մասամբ հասնում է Google-ին կամ Active Directory-ին, որոնց միջև մենք չենք կարող կրկնօրինակ պահել, համապատասխանաբար, երբ նոր աշխատակիցը հեռանում է, դուք պետք է ստեղծեք/միացնեք հաշիվ այս երկու համակարգերում: Գործընթացը ավտոմատացնելու համար մենք որոշեցինք գրել սկրիպտ, որը հավաքում է տեղեկատվություն և ուղարկում այն ​​երկու ծառայություններին:

Լիազորություն

Պահանջները կազմելիս մենք որոշեցինք օգտագործել իրական մարդկային ադմինիստրատորներ թույլտվության համար, ինչը հեշտացնում է գործողությունների վերլուծությունը պատահական կամ միտումնավոր զանգվածային փոփոխությունների դեպքում:

Google API-ներն օգտագործում են OAuth 2.0 արձանագրությունը նույնականացման և թույլտվության համար: Օգտագործման դեպքերը և ավելի մանրամասն նկարագրությունները կարող եք գտնել այստեղ՝ Օգտագործելով OAuth 2.0՝ Google API-ներ մուտք գործելու համար.

Ես ընտրեցի սցենարը, որն օգտագործվում է աշխատասեղանի հավելվածներում թույլտվության համար: Կա նաև ծառայության հաշիվ օգտագործելու տարբերակ, որն օգտատիրոջից ավելորդ շարժումներ չի պահանջում։

Ստորև բերված նկարը Google-ի էջից ընտրված սցենարի սխեմատիկ նկարագրությունն է:

Google-ի օգտատերերի ստեղծում PowerShell-ից API-ի միջոցով

  1. Նախ, մենք օգտվողին ուղարկում ենք Google Հաշվի նույնականացման էջ՝ նշելով GET պարամետրերը.
    • դիմումի ID
    • այն տարածքները, որոնց մուտքն անհրաժեշտ է հավելվածին
    • հասցեն, որին օգտատերը կվերահղվի ընթացակարգն ավարտելուց հետո
    • ինչպես մենք կթարմացնենք նշանը
    • Անվտանգության կոդ
    • հաստատման կոդի փոխանցման ձևաչափ

  2. Թույլտվության ավարտից հետո օգտատերը կվերահղվի առաջին հարցումում նշված էջին՝ GET պարամետրերով փոխանցված սխալով կամ թույլտվության կոդով։
  3. Հավելվածը (սկրիպտը) պետք է ստանա այս պարամետրերը և, եթե ստացել է կոդը, կատարել հետևյալ հարցումը՝ նշաներ ստանալու համար
  4. Եթե ​​հարցումը ճիշտ է, Google API-ն վերադարձնում է.
    • Մուտքի նշան, որով մենք կարող ենք հարցումներ կատարել
    • Այս նշանի վավերականության ժամկետը
    • Մուտքի նշանը թարմացնելու համար անհրաժեշտ է թարմացման նշան:

Նախ անհրաժեշտ է գնալ Google API կոնսոլ. Հավատարմագրեր - Google API Console, ընտրեք ցանկալի հավելվածը և հավատարմագրերի բաժնում ստեղծեք հաճախորդի OAuth նույնացուցիչ: Այնտեղ (կամ ավելի ուշ, ստեղծված նույնացուցիչի հատկություններում) դուք պետք է նշեք այն հասցեները, որոնց վերահղումը թույլատրվում է: Մեր դեպքում դրանք կլինեն մի քանի localhost մուտքեր տարբեր նավահանգիստներով (տես ստորև):

Սցենարների ալգորիթմը կարդալն ավելի հարմար դարձնելու համար կարող եք առաջին քայլերը ցուցադրել առանձին գործառույթով, որը կվերադարձնի 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

Մենք սահմանել ենք Հաճախորդի ID-ն և Հաճախորդի Գաղտնիքը, որոնք ստացվել են OAuth հաճախորդի նույնացուցիչի հատկություններում, իսկ կոդերի ստուգիչը 43-ից 128 նիշերից բաղկացած տող է, որը պետք է պատահականորեն ստեղծվի չպահված նիշերից՝ [AZ] / [az] / [0-9 ] / "-" / "." / "_" / "~".

Այս կոդը այնուհետև նորից կփոխանցվի: Այն վերացնում է խոցելիությունը, որի դեպքում հարձակվողը կարող է ընդհատել պատասխանը, որը վերադարձվել է որպես վերահղում օգտվողի թույլտվությունից հետո:
Ընթացիկ հարցման մեջ կարող եք ուղարկել կոդի ստուգիչ հստակ տեքստով (ինչն այն անիմաստ է դարձնում. սա հարմար է միայն այն համակարգերի համար, որոնք չեն աջակցում SHA256), կամ ստեղծելով հաշ՝ օգտագործելով SHA256 ալգորիթմը, որը պետք է կոդավորված լինի BASE64Url-ում (տարբեր Base64-ից երկու աղյուսակի նիշերով) և հեռացնելով նիշերի տողերի վերջավորությունները՝ =.

Հաջորդը, մենք պետք է սկսենք լսել http-ը տեղական մեքենայի վրա, որպեսզի ստանանք պատասխան լիազորումից հետո, որը կվերադարձվի որպես վերահղում:

Ադմինիստրատիվ առաջադրանքները կատարվում են հատուկ սերվերի վրա, մենք չենք կարող բացառել, որ մի քանի ադմինիստրատորներ միաժամանակ կաշխատեն սկրիպտը, ուստի այն պատահականորեն կընտրի մի նավահանգիստ ընթացիկ օգտագործողի համար, բայց ես նշել եմ նախապես սահմանված նավահանգիստները, քանի որ դրանք պետք է նաև ավելացվեն որպես վստահելի API վահանակում:

access_type=անցանց նշանակում է, որ հավելվածը կարող է ինքնուրույն թարմացնել ժամկետանց թոքենը՝ առանց օգտատիրոջ փոխգործակցության բրաուզերի հետ,
answer_type=կոդ սահմանում է կոդը վերադարձնելու ձևաչափը (հղում թույլտվության հին մեթոդին, երբ օգտատերը զննարկիչից պատճենել է կոդը սցենարի մեջ),
շրջանակ ցույց է տալիս մուտքի շրջանակը և տեսակը: Դրանք պետք է բաժանվեն բացատներով կամ %20 (ըստ URL կոդավորման): Տեսակներով մուտքի տարածքների ցանկը կարելի է տեսնել այստեղ. OAuth 2.0 Scopes Google API-ների համար.

Թույլտվության կոդը ստանալուց հետո հավելվածը փակ հաղորդագրություն կվերադարձնի դիտարկիչին, կդադարի լսել պորտում և կուղարկի POST հարցում՝ նշանը ստանալու համար: Դրանում մենք նշում ենք կոնսոլի API-ից նախկինում նշված ID-ն և գաղտնիքը, հասցեն, որին կվերահղվի օգտվողը և շնորհի_տիպը՝ արձանագրության բնութագրին համապատասխան:

Ի պատասխան՝ մենք կստանանք 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 նշանը: Ներկայումս նշանի տեսակը միշտ կրող է: Որովհետեւ մենք պետք է ստուգենք, որ նշանի ժամկետը չի լրացել և թարմացնել այն թողարկվելու պահից մեկ ժամ հետո, ես նշել եմ մեկ այլ գործառույթի հարցում, որը վերադարձնում է 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
}

Email:$query հարցումը API-ին կխնդրի փնտրել հենց այդ էլփոստով օգտվողին, ներառյալ փոխանունները: Կարող եք նաև օգտագործել wildcard. =, :, :{ՊՐԵՖԻՔՍ}*.

Տվյալներ ստանալու համար օգտագործեք 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

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