Tere!
Selles artiklis kÀsitletakse PowerShelli ja Google API koostööd G Suite'i kasutajate haldamiseks.
Meie organisatsioonis kasutame mitmeid sisemisi ja pilveteenuseid. Suurel mÀÀral kĂ”ikide teenuste autoriseerimine toimub Google'i vĂ”i Active Directory kaudu, mille vahel me ei saa replikatsiooni toetada, seega tuleb uue töötaja puhul luua/aktiveerida konto nendes kahes sĂŒsteemis. Protsessi automatiseerimiseks otsustasime kirjutada skripti, mis kogub teavet ja saadab selle mĂ”lemasse teenusesse.
Autoriseerimine
NĂ”udeid koostades otsustasime kasutada autoriseerimiseks tegelikke administraatoreid, et lihtsustada juhtumite analĂŒĂŒsimist juhuslike vĂ”i tahtlike massiliste muutuste korral.
Google API kasutab autentimiseks ja autoriseerimiseks protokolli OAuth 2.0. Kasutusskeeme ja ĂŒksikasjalikku kirjeldust saab vaadata siin: .
Valisin stsenaariumi, mida kasutatakse lauaarvuti rakendustesse autoriseerimisel. Samuti on vÔimalus kasutada teenusekontot, mis ei nÔua kasutajalt tÀiendavaid toiminguid.
Allolev joonis on valitud stsenaariumi visand lehelt Google.

- Esmalt suuname kasutaja Google'i konto autentimise lehele, mÀÀrates GET parameetrid:
- rakenduse identifikaator
- valdkonnad, millele rakendus vajab ligipÀÀsu
- aadress, kuhu kasutaja suunatakse pÀrast protseduuri lÔpetamist
- meetod, millega me vÀrskendame tokenit
- kontrollikood
- kontrollikoodi edastamise formaat
- PÀrast autoriseerimise lÔpetamist suunatakse kasutaja tagasi esimeses pÀringus mÀÀratud lehele, koos vea vÔi autoriseerimiskoodiga, edastatuna GET parameetrites.
- Rakendusel (skriptil) tuleb need parameetrid kÀtte saada ja, kui kood on saadud, teha jÀrgmine pÀring tokenite saamiseks.
- Korrektselt esitatud pÀringu korral tagastab Google API:
- Access token, millega me saame pÀringuid esitada
- Selle tokeni kehtivusaeg
- Refresh token, mis on vajalik Access tokeni uuendamiseks.
Esmalt tuleb minna Google API konsooli: , vali sobiv rakendus ja sektsioonis Credentials loo OAuth kliendi ID. Seal (vÔi hiljem, loodud ID omadustes) tuleb nÀidata aadresse, kuhu suunamine on lubatud. Meie puhul on need mitu localhost'i kirjet erinevate portidega (vt edasi).
Selleks, et skripti algoritmi oleks mugavam lugeda, saab esimesed sammud eraldi funktsiooni vÀlja tuua, mis tagastab juurdepÀÀsu- ja vÀrskenduse tokenid rakenduse jaoks:
$client_secret = 'Meie Klientide SalasÔna'
$client_id = 'Meie Klientide 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 "KĂ€ivitame brauseri..."
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 {
"Klistenerit ei Ônnestu kÀivitada."
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 vĂ”id selle brauseri vahelehe 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 ID omadustes, ja code verifier â see on 43 kuni 128 sĂŒmboli pikkune string, mis tuleb genereerida juhuslikult mittereserveeritud sĂŒmbolitest: [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~".
Edasi antakse see kood uuesti. See vÀlistab haavatavuse, kus pahatahtlik inimene vÔib vastuse, mis tagastati pÀrast kasutaja autentimist, ummistada.
Saada koodivahendi praegusesse pĂ€ringusse vĂ”ib saata avatud kujul (mis muudab selle mĂ”ttetuks - see sobib ainult sĂŒsteemidele, mis ei toeta SHA256) vĂ”i luua SHA256 algoritmiga hash, mis tuleb kodeerida BASE64Url (erineb Base64 kahe sĂŒmboli poolest) ja eemaldada reavahetusmĂ€rk: =.
SeejĂ€rel peame alustama http kuulamist lokaalses masinas, et saada vastus autoriseerimise jĂ€rel, mis naaseb ĂŒmbersuunamisega.
HaldusĂŒlesanded toimuvad spetsiaalsel serveril, me ei saa vĂ€listada tĂ”enĂ€osust, et mitu administraatorit kĂ€ivitab skripti samaaegselt, seetĂ”ttu valib see juhuslikult porti praeguse kasutaja jaoks, kuid ma mÀÀrasin ette antud pordid, kuna need tuleb samuti lisada usaldusvÀÀrseteks API konsoolis.
access_type=offline tÀhendab, et rakendus vÔib iseseisvalt uuendada aegunud tokeni ilma, et kasutaja peaks brauseriga suhtlema.
response_type=code mÀÀrab formaadi, kuidas kood naaseb (viide vanale 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 (vastavalt URL-kodeeringule). JuurdepÀÀsu alade ja tĂŒĂŒpide loetelu saab nĂ€ha siit: .
PÀrast autoriseerimise koodi saamist saadab rakendus brauserisse sulgemisettepaneku, lÔpetab pordi kuulamise ja saadab POST-pÀringu tokeni saamiseks. Me mÀÀrame selles antud id ja secret API konsoolist, aadress, kuhu kasutaja suunatakse ja grant_type vastavalt protokolli spetsifikatsioonile.
Vastuseks saame Access tokeni, selle kehtivuse aja sekundites ja Refresh tokeni, millega saame Access tokeni uuendada.
Rakendus peab hoidma tokeneid turvalises kohas pikaajalise sĂ€ilitamisega, seega, kuni me ei tĂŒhista saadud juurdepÀÀsu, ei tagastata rakendusele refresh tokenit. LĂ”puks lisasin pĂ€ringu tokeni tĂŒhistamiseks, kui rakendus ei olnud edukas ja refresh tokenit ei tagastatud, alustab see protseduuri uuesti (me pidasime ebaturvaliseks hoida tokeneid kohalikult terminalis ning ei soovi keerulisemaks muuta krĂŒptograafiaga vĂ”i tihti brauserit avada).
do {
$token_result = Get-GoogleAuthToken
$token = $token_result.access_token
if ($token_result.refresh_token -eq $null) {
Write-Host ("Seanss ei ole hĂ€vitatud. TĂŒhistatakse 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
}
Nagu olete juba mĂ€rganud, kasutatakse tokeni tĂŒhistamiseks Invoke-WebRequest'i. Erinevalt Invoke-RestMethod'ist ei naita see saadud andmeid mugavas vormingus ning nĂ€itab pĂ€ringu staatust.
SeejÀrel palub skript sisestada kasutaja ees- ja perekonnanime, genereerides sisenemisnime + e-posti.
PĂ€ringud
JĂ€rgmised toimingud on pĂ€ringud â kĂ”igepealt tuleb kontrollida, kas kasutajaga, kellel on selline sisenemisnimi, on juba olemas, et teha otsus uue loomise vĂ”i olemasoleva aktiveerimise ĂŒle.
Otsustasin kĂ”ik pĂ€ringud ellu viia ĂŒhe funktsiooni formaadis, kasutades switch'i:
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'])
}
}
}
}Iga taotluses tuleb saata Authorization pĂ€is, mis sisaldab tokeni tĂŒĂŒpi ja ise Access tokenit. Praegu on tokeni tĂŒĂŒp alati Bearer. Kuna meile on vajalik kontrollida, et token pole aegunud ja vĂ€rskendada seda tunni jooksul pĂ€rast vĂ€ljaandmist, olen mÀÀranud pĂ€ringu teise funktsioonile, mis tagastab Access tokeni. See sama koodijupp on 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
}Kontrolli kasutajanime olemasolu:
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
}KĂŒsi email: $query palub API-l otsida kasutajat just sellise emailiga, sealhulgas leidub ka alias'e. Samuti vĂ”id kasutada wildcard'i: =, :, :{PREFIX}*.
Andmete saamiseks kasutatakse meetodit GET, andmete sisestamiseks (konto loomine vĂ”i grupi liikme lisamine) â POST, olemasolevate andmete uuendamiseks â PUT, kirje kustutamiseks (nĂ€iteks grupist liikme eemaldamiseks) â DELETE.
Kood kĂŒsib ka telefoninumbrit (valideerimata string) ja kas liituda regionaalse postitusgrupiga. See otsustab, milline organisatsiooniline ĂŒksus kasutajal olema peaks vastavalt valitud OU Active Directory-s ja loob 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 "Luua aadressile /Team delivery"
$orgunit = "/Team delivery"
}
$Password = -join ( 48..57 + 65..90 + 97..122 | Get-Random -Count 12 | % {[char]$_})+"*Ba"
Ja siis alustab see konto manipuleerimist:
$query = @{
email = $email
givenName = $firstname
familyName = $lastname
password = $password
phone = $phone
orgunit = $orgunit
}
if ($GMailExist) {
Write-Host "KĂ€ivitame konto muutmise" -f mag
(GoogleQuery 'UpdateAccount' $query) | fl
write-host "Ăra unusta kontrollida gruppe, kuhu '$Username' Google'is kuulub."
} else {
Write-Host "KĂ€ivitame konto loomise" -f mag
(GoogleQuery 'CreateAccount' $query) | fl
}
if ($moscow -eq "y"){
write-host "Lisame moscowoffice gruppi"
$query = @{
groupkey = 'moscowoffice@rocketguys.com'
email = $email
}
(GoogleQuery 'AddMember' $query) | fl
}
Konto loomise ja uuendamise funktsioonidel on sarnane sĂŒntaks, mitte kĂ”ik tĂ€iendavad vĂ€ljad ei ole kohustuslikud, telefoninumbrite jaotises tuleb esitada massiiv, mis vĂ”ib sisaldada vĂ€hemalt ĂŒhte kirjet koos numbri ja selle tĂŒĂŒbiga.
Kuna vĂ”ib esineda viga kasutaja lisamisel rĂŒhma, saame eelnevalt kontrollida, kas ta kuulub juba sellesse rĂŒhma, saades rĂŒhmaliikmete nimekirja vĂ”i muud kasutaja andmed.
Teatud kasutaja rĂŒhmade koosseisu pĂ€ring ei ole rekursiivne ja nĂ€itab ainult vahetut kuuluvust. Kasutaja lisamine ĂŒlemrĂŒhma, milles juba eksisteerib alamrĂŒhm, mille liige ta on, Ă”nnestub.
KokkuvÔte
JÀÀnud on saata kasutajale uus konto parool. Teeme seda SMS-iga ning ĂŒldise teabe koos juhiste ja sisselogimisega saadame isiklikule e-postile, mille andis personaliosakond koos telefoninumeroga. Alternatiivina saame raha sÀÀsta ja saata parooli salajasesse Telegrami vestlusesse, mis vĂ”ib samuti arvestada teise tegurina (vĂ€lja arvatud MacBookid).
AitÀh, et lugesite lÔpuni. Olen avatud ettepanekutele artiklite kirjutamisstiili parandamiseks ja soovin, et te teeks, et teie skriptide kirjutamises vÀhem vigu =)
Nimekiri lingitud, mis vĂ”ivad olla temaatiliselt kasulikud vĂ”i lihtsalt vastata tekkinud kĂŒsimustele:
Allikas: habr.com
