Tere!
Selles artiklis kÀsitletakse PowerShelli ja Google API vaheline integreerimist G Suite'i kasutajate haldamiseks.
Organisatsioonis kasutame mitmeid sisemisi ja pilves teenuseid. Enamasti piirdub autoriseerimine Google'i vĂ”i Active Directoryga, mille vahel ei saa me sĂŒnkroonimist tagada. Seega tuleb uue töötaja puhul luua/aktiveerida konto mĂ”lemas sĂŒsteemis. Protsessi automatiseerimiseks otsustasime kirjutada skripti, mis kogub vajalikku teavet ja saadab selle mĂ”lemasse teenusesse.
Autoriseerimine
NÔudeid koostades otsustasime kasutada autoriseerimiseks reaalseid administraatoreid, mis lihtsustab tegevuste tÔlgendamist juhuslike vÔi tahtlike massiliste muutuste korral.
Google API autentimiseks ja autoriseerimiseks kasutatakse OAuth 2.0 protokolli. Kasutusskenaarid ja detailne kirjeldus on saadaval siin: .
Valisin skripti, mida kasutatakse desktop-rakendustes autoriseerimise jaoks. Samuti on olemas teenusekonto kasutamise vÔimalus, mis ei nÔua kasutajalt tÀiendavaid samme.
Allpool on joon allpool â see on valitud stsenaariumi skeem Google'i lehelt.

- Esialgu suuname kasutaja Google'i konto autentimislehele, edastades GET parameetrites:
- rakenduse identifikaator
- tegevusvaldkonnad, millele rakendus vajab ligipÀÀsu
- aadress, kuhu kasutaja suunatakse pÀrast protseduuri lÔppu
- meetod, millega me uuendame tokenit
- kontrollikood
- kontrollikoodi edastamise formaat
- PÀrast autentimise lÔpetamist suunatakse kasutaja tagasi esimeses pÀringus nÀidatud lehele, koos veateate vÔi autoriseerimisoodisega, mis edastatakse GET parameetrites.
- Rakendusel (skriptil) on vaja need parameetrid hankida ja, kui kood on saadud, teha jÀrgmine pÀring tokenite hankimiseks.
- Korraliku pÀringu korral tagastab Google API:
- Access token, millega saame teha pÀringuid
- Selle tokeni kehtivusaeg
- Refresh token, mis on vajalik Access tokeni uuendamiseks.
Esmalt tuleb kÀia Google API konsoolis: , valige vajalik rakendus ja jaotises Credentials looge OAuth kliendi identifikaator. Seal (vÔi hiljem, loodud identifikaatori omadustes) tuleb mÀrkida aadressid, kuhu suunamine on lubatud. Meie puhul on need mitu localhost'i sissekannet erinevate portidega (vt allpool).
Kuna skripti algoritmi on mugavam lugeda, saab esimesed sammud vÀlja tuua eraldi funktsiooni, mis tagastab rakenduse Access ja refresh tokenid:
$client_secret = 'Meie kliendi salajane kood'
$client_id = 'Meie kliendi ID'
function Get-GoogleAuthToken {
if (-not [System.Net.HttpListener]::IsSupported) {
"HttpListener ei ole toetatud."
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 "Avatud brauser..."
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 {
"Listeneri kÀivitamine ei Ônnestunud."
exit 1
}
while (($code -eq $null)) {
$context = $listener.GetContext()
Write-Host "Ăhendus aktsepteeritud" -f 'mag'
$url = $context.Request.RawUrl
$code = $url.split('?')[1].split('=')[1].split('&')[0]
if ($url.split('?')[1].split('=')[0] -eq 'error') {
Write-Host "Viga!"$code -f 'red'
$buffer = [System.Text.Encoding]::UTF8.GetBytes("Viga!"+$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("NĂŒĂŒd saate selle brauseri vahekaardi sulgeda.")
$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
Me seadistame Client ID ja Client Secret, mis on saadud OAuth kliendi identifikaatori omadustes, ja code verifier â see on 43 kuni 128 mĂ€rki pikk string, mis peab olema juhuslikult genereeritud, kasutades reservatsioonivabu mĂ€rke: [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~".
SeejĂ€rel saadetakse see kood uuesti. See vĂ€listab haavatavuse, kus kurjategija vĂ”ib pealt kuulata vastuse, mis naaseb ĂŒmber suunamise kaudu pĂ€rast kasutaja autoriseerimist.
Saada code verifier praeguses pĂ€ringus vĂ”ib avatud kujul (mis muudab selle mĂ”ttetuks â see sobib ainult sĂŒsteemidele, mis ei toeta SHA256), vĂ”i luues rĂ€singu SHA256 algoritmi kaudu, mis tuleb kodeerida BASE64Url (erineb Base64-st kahe tabelisĂŒmboli poolest) ja eemaldada realĂ”pu mĂ€rk: =.
SeejĂ€rel peame alustama http kuulamist lokaatorkonfiguratsioonis, et saada vastus pĂ€rast autoriseerimist, mis naaseb ĂŒmber suunamise kaudu.
Halduse ĂŒlesandeid tĂ€idetakse spetsiaalsel serveril, seetĂ”ttu ei saa me vĂ€listada tĂ”enĂ€osust, et mitu administraatorit kĂ€ivitavad skripti samal ajal, mistĂ”ttu valitakse sadam praeguse kasutaja jaoks juhuslikult. Olen eelnevalt mÀÀranud kindlad sadamad, kuna need tuleb ka usaldatud nimekirja lisada API konsoolis.
access_type=offline tÀhendab, et rakendus saab aegunud tokenit automaatselt vÀrskendada ilma kasutaja sekkumiseta brauserisse.
response_type=code mÀÀrab formaadi, kuidas kood tagasi antakse (viidates vana autoriseerimise meetodile, kus kasutaja kopeeris koodi brauserist skripti),
scope nĂ€itab alasid ja juurdepÀÀsu tĂŒĂŒpe. Need peavad olema eraldatud tĂŒhikute vĂ”i -ga (vastavalt URL kodeerimisele). Juhtimise alasid ja tĂŒĂŒpe saab vaadata siit: .
PÀrast autoriseerimiskoodi saamist tagastab rakendus brauseris sulgem sÔnumi, lÔpetab sadama kuulamise ja saadab POST-i pÀringu tokeni saamiseks. Me mÀÀrame selles eelnevalt mÀÀratud ID ja salajase vÔtme API konsoolist, aadressi, kuhu kasutaja suunatakse, ja grant_type vastavalt protokolli spetsifikatsioonile.
Vastusena saame Access-tokeni, selle kehtivuse aja sekundites ja Refresh-tokeni, mille abil saame Access-tokeni uuendada.
Rakendus peab hoidma tokeneid turvalises kohas pikaajalise sĂ€ilitamisega. SeetĂ”ttu ei returnita rakendusele refresh-tokenit, kuni me saadud juurdepÀÀsu tagasi ei vĂ”ta. LĂ”pus lisasin tokeni tĂŒhistamise pĂ€ringu, kui rakendus lĂ”petatakse ebaĂ”nnestunult ja refresh-tokenit ei tagastata, algab see protseduur uuesti (peame ebaohutuks hoida tokeneid kohalikus terminalis, ning ei soovi krĂŒptograafiaga keeruliseks muuta vĂ”i pidevalt brauserit avada).
do {
$token_result = Get-GoogleAuthToken
$token = $token_result.access_token
if ($token_result.refresh_token -eq $null) {
Write-Host ("Sessioon ei ole hĂ€vitatud. TĂŒhistan tokeni...")
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
}
Nagu juba mĂ€rkisite, kasutatakse tokeni tĂŒhistamisel Invoke-WebRequesti. Erinevalt Invoke-RestMethodist ei tagasta see andmeid kasutamiseks mugavas formaadis ning nĂ€itab pĂ€ringu staatust.
KĂŒsimiseks palub skript sisestada kasutaja ees- ja perekonnanimi, genereerides sisselogimise + e-posti.
PĂ€ringud
JĂ€rgmised kĂŒsimused â esmajĂ€rjekorras tuleb kontrollida, kas sellise sisselogimisega kasutaja juba eksisteerib, et teha otsus uue loomise vĂ”i praeguse aktiveerimise osas.
Otsustasin kĂ”ik pĂ€ringud rakendada ĂŒhe funktsiooni formaadis, kasutades valikustruktuuri (switch):
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'])
}
}
}
}Igas pĂ€ringus tuleb saata Authorization pĂ€is, mis sisaldab tokeni tĂŒĂŒpi ja ise Access tokenit. Hetkel on tokeni tĂŒĂŒp alati Bearer. Kuna peame kontrollima, et token ei ole aegunud ja uuendama seda tunni jooksul alates vĂ€ljastamisest, viisin ma sisse pĂ€ringu teise funktsiooni, mis tagastab Access tokeni. Sama koodijupp on ka skripti alguses, kui saadakse esimene Access token:
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 on aegunud. Uuendamine..."
$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
}Logi olemasolu kontroll:
function Check_Google {
$query = (GoogleQuery 'SearchAccount' $username)
if ($query.users -ne $null) {
$user = $query.users[0]
Write-Host $user.name.fullName' - '$user.PrimaryEmail' - peatatud: '$user.Suspended
$GAresult = $user
}
if ($GAresult) {
$return = $GAresult
} else {$return = 'gg'}
return $return
}Emaili pÀring: $query palub API-l otsida kasutajat selle e-posti aadressiga, sealhulgas leidub ka alias. Samuti on vÔimalik kasutada wildcard'i: =, :, :{PREFIX}*.
Andmete saamiseks kasutatakse GET pÀringut, andmete lisamiseks (kasutajakonto loomine vÔi grupi liikme lisamine) - POST, olemasolevate andmete uuendamiseks - PUT, kirje kustutamiseks (nÀiteks grupi liikme eemaldamiseks) - DELETE.
Skript kĂŒsib ka telefoninumbrit (kehtetu string) ja kas liituda piirkondliku jaotuse gruppiga. See otsustab, milline organisatsiooniline ĂŒksus peaks kasutajal olema, tuginedes valitud OU Active Directoryle, ja genereerib parooli:
do {
$phone = Read-Host "Telefon vormingus +7Ń
Ń
Ń
Ń
Ń
Ń
Ń
Ń
"
} while (-not $phone)
do {
$moscow = Read-Host "Moskva kontorisse? (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 "Loo ma: /Team delivery"
$orgunit = "/Team delivery"
}
$Password = -join (48..57 + 65..90 + 97..122 | Get-Random -Count 12 | % {[char]$_}) + "*Ba"
Ja seejÀrel alustab konto manipuleerimist:
$query = @{
email = $email
givenName = $firstname
familyName = $lastname
password = $password
phone = $phone
orgunit = $orgunit
}
if ($GMailExist) {
Write-Host "Konto muutmine on kÀimas" -f mag
(GoogleQuery 'UpdateAccount' $query) | fl
write-host "Ăra unusta kontrollida gruppide olekut Google'is kasutaja $Username jaoks."
} else {
Write-Host "Konto loomine on kÀimas" -f mag
(GoogleQuery 'CreateAccount' $query) | fl
}
if ($moscow -eq "y"){
write-host "Lisame Moskva kontori gruppi"
$query = @{
groupkey = 'moscowoffice@rocketguys.com'
email = $email
}
(GoogleQuery 'AddMember' $query) | fl
}
Konto uuendamise ja loomise funktsioonidel on sarnane sĂŒntaks, mitte kĂ”ik tĂ€iendavad vĂ€ljad ei ole kohustuslikud; telefoninumbri osas tuleb nĂ€idata massiivi, mis vĂ”ib sisaldada vĂ€hemalt ĂŒhte numbrit koos selle tĂŒĂŒbiga.
Kasutaja grupi lisamisel vea vÀltimiseks saame eelnevalt kontrollida, kas ta juba kuulub sellesse gruppi, saades kas grupi liikmete nimekirja vÔi kasutaja endi koosseisu.
Teatud kasutaja grupi koosseisu pÀrimine ei ole rekursiivne ja nÀitab vaid vahetut kuuluvust. Kasutaja lisamine vanemgruppi, kuhu juba kuulub alamgrupp, mille liige kasutaja on, Ônnestub.
KokkuvÔte
Kasutajale jÀÀb saata uut kontot puudutav salasĂ”na. Teeme seda SMS-iga, samas saadame ĂŒldise teabe juhiste ja sisselogimisega isiklikule meilile, mille koos telefoninumbriga esitas personalivaliku osakond. Alternatiivina saab raha sÀÀsta ja saata salasĂ”na Telegrami salajasesse vestlusesse, mida vĂ”ib samuti pidada teiseks teguriks (vĂ€lja arvatud MacBook'id).
AitÀh, et lugesite lÔpuni. Olen huvitatud teie ettepanekutest artiklite kirjutamisstiili parandamiseks ja soovin, et te kirjutamisel vÀhem vigu teeks.
Siin on loetelu linkidest, mis vĂ”ivad olla temaatiliselt kasulikud vĂ”i lihtsalt vastata tekkivatele kĂŒsimustele:
Allikas: habr.com
