Përshëndetje!
Ky artikull do të përshkruajë implementimin e bashkëpunimit PowerShell me Google API për të kryer manipulime me përdoruesit e G Suite.
Në organizatën tonë përdorim disa shërbime të brendshme dhe në anën e re. Kryesisht, autorizimi në to përfshin Google ose Active Directory, mes të cilave nuk mundemi të mbajmë një replikë, prandaj, kur një punonjës i ri punësohet, duhet të krijojmë/aktivizojmë llogarinë në këto dy sisteme. Për të automatizuar procesin, vendosëm të shkruajmë një skenar që mbledh informacion dhe e dërgon në të dy shërbimet.
Autorizimi
Duke përgatitur kërkesat, vendosëm të përdorim për autorizimin persona-adorues të vërtetë, kjo e thjeshton analizimin e veprimeve në rast ndryshimesh masive aksidentale ose të qëllimshme.
Për autentikimin dhe autorizimin, Google API përdor protokollin OAuth 2.0. Skenarët e përdorimit dhe një përshkrim më të detajuar mund të shikohen këtu: .
Kam zgjedhur skenarin që përdoret për autorizimin në aplikacionet desktop. Ka gjithashtu mundësinë e përdorimit të një llogarie shërbimi, e cila nuk kërkon veprime të tepërta nga përdoruesi.
Imazhi më poshtë është një përshkrim schematic i skenarit të zgjedhur nga faqja e Google.

- Fillimisht, ne e dërgojmë përdoruesin në faqen e identifikimit në llogarinë Google, duke specifikuar parametra GET:
- identifikuesin e aplikacionit
- fushat, për të cilat aplikacioni ka nevojë për qasje
- adresën në të cilën përdoruesi do të ridrejtohet pas përfundimit të procedurës
- metodën për të përditësuar token-in
- kodin e verifikimit
- formatin e kodit të verifikimit
- Pas përfundimit të autorizimit, përdoruesi do të ridrejtohet në faqen e specifikuar në kërkesën e parë, me një gabim ose kod autorizimi të dërguar si parametra GET
- Aplikacioni (skripti) do të ketë nevojë të marrë këta parametra dhe, në rast se merr kodin, të kryejë kërkesën e mëposhtme për të marrë token-at
- Nëse kërkesa është e saktë, Google API kthen:
- Token-in e aksesit, me të cilin mund të bëjmë kërkesa
- Kohëzgjatjen e këtij token-i
- Token-in e rifreskimit, i nevojshëm për të përditësuar token-in e aksesit.
Fillimisht, duhet të shkojmë në konsolën Google API: , zgjidhni aplikacionin e duhur dhe në seksionin Credentials krijoni një identifikues klienti OAuth. Po aty (ose më vonë, në pronat e identifikuesit të krijuar) duhet të tregoni adresat ku lejohen ridrejtimet. Në rastin tonë, kjo do të jetë disa regjistrime localhost me porte të ndryshme (shih më poshtë).
Për ta bërë më të lehtë leximin e algoritmit të skriptit, mund të nxjerrim hapat e parë në një funksion të veçantë, i cili do të kthejë Access dhe refresh tokenet për aplikacionin:
$client_secret = 'Sekreti ynë i klientit'
$client_id = 'ID-ja jonë e klientit'
function Get-GoogleAuthToken {
if (-not [System.Net.HttpListener]::IsSupported) {
"HttpListener nuk mbështetet."
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 "Fill browserin..."
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 {
"Nuk mund të nisim dëgjuesin."
exit 1
}
while (($code -eq $null)) {
$context = $listener.GetContext()
Write-Host "Konfigurimi pranuar" -f 'mag'
$url = $context.Request.RawUrl
$code = $url.split('?')[1].split('=')[1].split('&')[0]
if ($url.split('?')[1].split('=')[0] -eq 'error') {
Write-Host "Gabim!"$code -f 'red'
$buffer = [System.Text.Encoding]::UTF8.GetBytes("Gabim!"+$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("Tani mund të mbyllni këtë ngonë të shfletuesit.")
$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
Ne jemi në vend Client ID dhe Client Secret, që janë marrë në pronat e identifikuesit të klientit OAuth, dhe code verifier – kjo është një varg me një gjatësi nga 43 deri në 128 karaktere, i cili duhet të gjenerohet rastësisht nga karaktere të papër rezervuara: [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~".
Më pas ky kod do të përcillet përsëri. Ai përjashton vulnerabilitetin, ku një sulmues mund të kapë përgjigjen, e cila kthehet si ridirekt pas autorizimit të përdoruesit.
Të dërgosh code verifier në kërkesën aktuale mund të bëhet në mënyrë të hapur (çka e bën atë pa kuptim – kjo është e përshtatshme vetëm për sistemet që nuk mbështesin SHA256), ose duke krijuar një hash sipas algoritmit SHA256, i cili duhet të kodifikohet në BASE64Url (ndryshe nga Base64 me dy karaktere të ndryshme) dhe të hiqet karakteri i përfundimit të linjës: =.
Më pas na nevojitet të fillojmë të dëgjojmë http në makinë lokale, për të marrë përgjigjen pas autorizimit, e cila do të kthehet si ridirekt.
Detajet administrativë kryhen në një server të specializuar, nuk mund të përjashtojmë mundësinë që disa administratorë të ekzekutojnë një skript në të njëjtën kohë, për këtë arsye ai do të zgjedhë rastësisht një port për përdoruesin aktual, por unë kam specifikuar portet e përcaktuara më parë, pasi ato duhet të aggiughen gjithashtu si të besuara në konsolën e API-së.
access_type=offline do të thotë se aplikacioni mund të përditësojë tokenin e skaduar në mënyrë autonome pa ndërveprimin e përdoruesit me shfletuesin,
response_type=code përcakton formatin se si do të kthehet kodi (referencë për metodën e vjetër të autentifikimit, kur përdoruesi kopjonte dhe ngjiste kodin nga shfletuesi në skript),
scope tregon fushat dhe tipin e aksesit. Ato duhet të ndahen me hapësira ose (sipas URL Encoding). Mund të shihni listën e fushave të aksesit me llojet këtu: .
Pas marrjes së kodit të autorizimit, aplikacioni do të kthejë një mesazh në shfletues për mbyllje, do të ndalë dëgjimin e portit dhe do të dërgojë një kërkesë POST për të marrë tokenin. Ne e specifikojmë në të id dhe sekretin e caktuar më parë nga konsola e API-së, adresën në të cilën do të ridrejtohet përdoruesi dhe grant_type sipas specifikimit të protokollit.
Në përgjigje, ne do të marrim një token Access, koha e tij e vlefshmërinë në sekonda dhe një token Refresh, me ndihmën e të cilit mund ta azhurnojmë tokenin Access.
Aplikacioni duhet të ruajë tokenet në një vend të sigurt me një afat të gjatë skadimi, kështu që, për sa kohë që nuk do ta revokojmë aksesin e marrë, aplikacioni nuk do të marrë një token refresh. Në fund, kam shtuar një kërkesë për revokimin e tokenit, nëse aplikacioni përfundon me sukses, ai do të fillojë përsëri procedurën (ne e konsiderojmë të pasigurt ruajtjen e tokenëve lokal në terminal, dhe nuk dojmë ta komplikojmë me kriptografi ose të hapim shpesh shfletuesin).
do {
$token_result = Get-GoogleAuthToken
$token = $token_result.access_token
if ($token_result.refresh_token -eq $null) {
Write-Host ("Seanca nuk është shkatërruar. Revokimi i tokenit...")
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
}
Si e keni vërejtur, gjatë tërheqjes së tokenit përdoret Invoke-WebRequest. Ndryshe nga Invoke-RestMethod, ai nuk kthen të dhënat në një format të përshtatshëm për përdorim dhe tregon statusin e kërkesës.
Më pas skripta do t'ju kërkojë të jepni emrin dhe mbiemrin e përdoruesit, duke gjeneruar një login + email.
Kërkesat
Kërkesat e radhës do të jenë – së pari duhet të verifikohet nëse përdoruesi me këtë login ekziston, për të marrë një vendim mbi formimin e një të ri apo aktivizimin e atij aktual.
Kam vendosur të realizoj të gjitha kërkesat në formatin e një funksioni me zgjedhje, duke përdorur switch:
funksioni GoogleQuery {
param (
$type,
$query
)
switch ($type) {
"SearchAccount" {
Kthej Invoke-RestMethod -Metod Merr -Uri "https://www.googleapis.com/admin/directory/v1/users" -Kopje @{Autorizimi = "Bearë"+(Merr-GoogleToken)} -Trupi @{
domain = 'rocketguys.com'
query = "email:$query"
}
}
"UpdateAccount" {
$trupi = @{
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']
}
Kthej Invoke-RestMethod -Metod Vendos -Uri ("https://www.googleapis.com/admin/directory/v1/users/"+$query['email']) -Kopje @{Autorizimi = "Bearë"+(Merr-GoogleToken)} -Trupi (ConvertTo-Json $trupi) -ContentType 'application/json; charset=utf-8'
}
"CreateAccount" {
$trupi = @{
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']
}
Kthej Invoke-RestMethod -Metod Post -Uri "https://www.googleapis.com/admin/directory/v1/users" -Kopje @{Autorizimi = "Bearë"+(Merr-GoogleToken)} -Trupi (ConvertTo-Json $trupi) -ContentType 'application/json; charset=utf-8'
}
"AddMember" {
$trupi = @{
userKey = $query['email']
}
$ifrequest = Invoke-RestMethod -Metod Merr -Uri "https://www.googleapis.com/admin/directory/v1/groups" -Kopje @{Autorizimi = "Bearë"+(Merr-GoogleToken)} -Trupi $trupi
$array = @()
foreach ($group in $ifrequest.groups) {$array += $group.email}
if ($array -notcontains $query['groupkey']) {
$trupi = @{
email = $query['email']
role = "ANËTAR"
}
Kthej Invoke-RestMethod -Metod Post -Uri ("https://www.googleapis.com/admin/directory/v1/groups/"+$query['groupkey']+"/members") -Kopje @{Autorizimi = "Bearë"+(Merr-GoogleToken)} -Trupi (ConvertTo-Json $trupi) -ContentType 'application/json; charset=utf-8'
} else {
Kthej ($query['email']+" tani është një anëtar i "+$query['groupkey'])
}
}
}
}Çdo kërkesë duhet të dërgojë një titull Authorization, që përmban tipin e tokenit dhe vetë tokenin e Access. Në këtë moment, tipi i tokenit është gjithmonë Bearer. Pasi na nevojitet të verifikojmë se tokeni nuk është skaduar dhe ta rifreskojmë atë pas një ore nga lëshimi, kam përfshirë një kërkesë për një funksion tjetër që kthen tokenin e Access. Ky pjesë kodi është gjithashtu në fillim të skriptit kur merret tokeni i parë 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 "Tokeni skadoi. Po e rifreskoj..."
$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
}Kontrollimi i ekzistencës së llogarisë:
function Check_Google {
$query = (GoogleQuery 'SearchAccount' $username)
if ($query.users -ne $null) {
$user = $query.users[0]
Write-Host $user.name.fullName' - '$user.PrimaryEmail' - i pezulluar: '$user.Suspended
$GAresult = $user
}
if ($GAresult) {
$return = $GAresult
} else {$return = 'gg'}
return $return
}Kërkesa për email: $query do të bëjë që API të kërkojë një përdorues me këtë email, duke përfshirë aleatët. Mund të përdoren gjithashtu wildcards: =, :, :{PREFIX}*.
Për të marrë të dhënat përdoret metoda GET, për të futur të dhëna (krijimin e një llogarie ose shtimin e një anëtari në grup) – POST, për të përditësuar të dhëna ekzistuese – PUT, për të fshirë një rekord (p.sh., një anëtar nga grupi) – DELETE.
Skrypti gjithashtu do të kërkojë numrin e telefonit (një varg i pavlefshëm) dhe përfshirjen në grupin rajonal të shpërndarjes. Ai përcakton se cila njësinë organizative duhet të ketë përdoruesi në bazë të OU të zgjedhur në Active Directory dhe krijon një fjalëkalim:
do {
$phone = Read-Host "Telefoni në formatin +7ххххххххх"
} while (-not $phone)
do {
$moscow = Read-Host "Në zyrën e Moskës? (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 "Do të krijohet në /Team delivery"
$orgunit = "/Team delivery"
}
$Password = -join ( 48..57 + 65..90 + 97..122 | Get-Random -Count 12 | % {[char]$_}) + "*Ba"
Dhe më pas fillon manipulimet me llogarinë:
$query = @{
email = $email
givenName = $firstname
familyName = $lastname
password = $password
phone = $phone
orgunit = $orgunit
}
if ($GMailExist) {
Write-Host "Duke ne ndryshimin e llogarisë" -f mag
(GoogleQuery 'UpdateAccount' $query) | fl
write-host "Mos harro të kontrollosh grupet e përfshira për $Username në Google."
} else {
Write-Host "Duke krijuar llogarinë" -f mag
(GoogleQuery 'CreateAccount' $query) | fl
}
if ($moscow -eq "y"){
write-host "Duke shtuar në grupin moscowoffice"
$query = @{
groupkey = 'moscowoffice@rocketguys.com'
email = $email
}
(GoogleQuery 'AddMember' $query) | fl
}
Funksionet për azhurnimin dhe krijimin e llogarive kanë një sintaksë të ngjashme, jo të gjitha fushat shtesë janë të obligueshme, në seksionin me numra telefoni duhet të specifikohet një array, që mund të përmbajë nga një regjistrim me numrin dhe llojin e tij.
Për të mos marrë një gabim gjatë shtimit të përdoruesit në grup, paraprakisht mund të kontrollojmë nëse ai është tashmë në këtë grup, duke marrë një listë të anëtarëve të grupit ose përbërjen e vetë përdoruesit.
Kërkesa për përbërjen e grupeve të një përdoruesi të caktuar nuk do të jetë rekurzive dhe do të tregojë vetëm anëtarësinë e menjëhershme. Duke përfshirë një përdorues në grupin prind, në të cilin tashmë gjendet një grup i nënshkruar, në të cilin përdoruesi është pjesë, do të jetë e suksesshme.
Përfundimi
Duhet të dërgojmë përdoruesit fjalinë sekret të llogarisë së re. Ne e bëjmë këtë përmes SMS-së, ndërsa informacionin e përgjithshëm me udhëzime dhe emrin e përdoruesit e dërgojmë në postën personale që e ofroi departamenti i rekrutimit së bashku me numrin e telefonit. Si një opsion alternativ, mund të kursejmë disa para dhe ta dërgojmë fjalinë sekrete në një bisedë sekrete në Telegram, që gjithashtu mund të merret si faktori i dytë (përjashtim do të jenë makbukut).
Faleminderit që e lexuat deri në fund. Do të jem i lumtur të pranoj propozime për përmirësimin e stilit të shkrimit të artikujve dhe ju uroj të kapni sa më pak gabime gjatë shkruarjes së skripteve =)
Lista e lidhjeve që mund të jenë tematikisht të dobishme ose thjesht të japin përgjigje për pyetjet e hasura:
Burimi: habr.com
