đ„Si tĂ« arrish nĂ« qiell dhe tĂ« bĂ«hesh pilot | ProHoster
Në këtë artikull do të përshkruhet implementimi i ndërveprimit të PowerShell me Google API për të kryer manipulime me përdoruesit e G Suite.
Në organizatën tonë ne përdorim disa shërbime të brendshme dhe cloud. Kryesisht, autorizimi në to përfshin Google ose Active Directory, midis të cilëve nuk mund të mbajmë një replikë, prandaj, kur një punonjës i ri hyn, është e nevojshme të krijohet/aktivizohet një llogari në këto dy sisteme. Për të automatizuar këtë proces, ne vendosëm të shkruajmë një skenar që mbledh informacionin dhe e dërgon në të dy shërbimet.
Autorizimi
Duke përpiluar kërkesat, ne vendosëm të përdorim për autorizim njerëz realë administratori, kjo e thjeshton analizimin e veprimeve gjatë ndryshimeve masive të rastësishme ose të qëllimshme.
Për autentifikim dhe autorizim, Google API përdor protokollin OAuth 2.0. Scenarët e përdorimit dhe përshkrimi më i detajuar mund të shihen këtu: .
Unë zgjodha skenarin që përdoret gjatë autorizimit në aplikacione desktop. Ka gjithashtu një mundësi për të përdorur një llogari shërbimi, e cila nuk kërkon lëvizje të tepërta nga përdoruesi.
Imazhi më poshtë është një përshkrim skematik i skenarit të zgjedhur nga faqja e Google.

- Fillimisht ne e dërgojmë përdoruesin në faqen e autentifikimit në llogarinë Google, duke treguar parametrat GET:
- identifikuesin e aplikacionit
- fushat për të cilat aplikacioni ka nevojë për akses
- adresën në të cilën përdoruesi do të ridrejtohet pas përfundimit të procedurës
- metodën me të cilën do të azhurnojmë tokenin
- kodin e verifikimit
- formatin e transmetimit të kodit të verifikimit
- Pas përfundimit të autorizimit, përdoruesi do të ridrejtohet në faqen e përcaktuar në kërkesën e parë, me një gabim ose kodin e autorizimit, të dërguara si parametra GET
- Aplikacioni (skenari) do të duhet të marrë këta parametra dhe, në rast se merr kodin, të kryejë kërkesën e mëtejshme për të marrë tokenet
- Me një kërkesë të saktë, Google API kthen:
- Tokenin e aksesit, me të cilin mund të bëjmë kërkesa
- Kohën e skadencës së këtij tokeni
- Tokenin e rifreskimit, i nevojshëm për rinovimin e Tokenit të aksesit.
Fillimisht duhet të shkojmë në konsolën Google API: , zgjidhni aplikacionin e nevojshëm dhe në seksionin Credentials krijoni një identifikues OAuth klienti. Atje (ose më vonë, në pronat e identifikuesit të krijuar) duhet të specifikoni adresat, në të cilat lejohet ridrejtimi. Në rastin tonë, kjo do të jetë disa regjistra localhost me porte të ndryshme (shih më poshtë).
Për të lehtësuar leximin e algoritmit të skriptit, mund të nxirrni hapat e parë në një funksion të veçantë, i cili do të kthejë Access dhe refresh token për aplikacionin:
$client_secret = 'Sekreti ynë i klientit'
$client_id = 'ID e klientit tonë'
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 "Niseni shfletuesin..."
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 arriti të nisë dëgjuesin."
exit 1
}
while (($code -eq $null)) {
$context = $listener.GetContext()
Write-Host "Konekcioni u pranua" -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ë skedë 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 caktojmĂ« Client ID dhe Client Secret, tĂ« marra nĂ« pronat e identifikuesit klient OAuth, dhe code verifier â kjo Ă«shtĂ« njĂ« varg me gjatĂ«si nga 43 deri nĂ« 128 karaktere, i cili duhet tĂ« gjenerohet rastĂ«sisht nga karaktere tĂ« pavaruara: [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~".
Më pas ky kod do të përcillet përsëri. Ai eliminohet vulnerabilitetin, ku një sulmues mund të kapë përgjigjen, e cila kthehet me ridrejtim pas autorizimit të përdoruesit.
Dërgimi i code verifier në kërkesën aktuale mund të bëhet në formë të hapur (çka e bën atë të panevojshëm - kjo i përshtatet vetëm sistemeve që nuk mbështesin SHA256), ose duke krijuar një hash sipas algoritmit SHA256, i cili duhet të kodifikohet në BASE64Url (dallon nga Base64 me dy simbole të ndryshme) dhe të hiqet simboli i përfundimit të rreshtit: =.
Më pas, na nevojitet të fillojmë të dëgjojmë http në makinën lokale, për të marrë përgjigjen pas autorizimit, e cila do të kthehet përmes një ridirektimi.
Detyrat administrative realizohen nĂ« njĂ« ŃĐ”ŃĐČĐ”Ń tĂ« veçantĂ«, nuk mund tĂ« pĂ«rjashtojmĂ« mundĂ«sinĂ« qĂ« disa administratorĂ«t tĂ« fillojnĂ« skenarin nĂ« tĂ« njĂ«jtĂ«n kohĂ«, ndaj ai do tĂ« zgjedhĂ« rastĂ«sisht njĂ« port pĂ«r pĂ«rdoruesin aktual, por unĂ« kam caktuar mĂ« parĂ« porte tĂ« pĂ«rcaktuara, pasi ato duhet gjithashtu tĂ« shtohen si tĂ« besuara nĂ« konsolĂ«n e API-t.
access_type=offline do të thotë që aplikacioni mund të freskojë tokenin e skaduar vetë pa ndërveprimin e përdoruesit me shfletuesin,
response_type=code përcakton formatin se si do të kthehet kodi (referencë ndaj mënyrës së vjetër të autorizimit, kur përdoruesi kopjon dhe ngjit kode nga shfletuesi në skenar),
scope tregon fushat dhe llojin e aksesit. Ato duhet të ndahen me hapësira ose (siparo URL Encoding). Lista e fushave të aksesit me llojet mund të shihet këtu: .
Pas marrjes së kodit të autorizimit, aplikacioni do të kthejë në shfletues një mesazh për mbylljen, do të ndalojë dëgjimin e portit dhe do të dërgojë një kërkesë POST për të marrë tokenin. Ne e caktuar në të identifikimin dhe sekretin e vendosur më parë nga konsola e API-t, 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ë Access token, kohën e tij të vlefshmërisë në sekonda dhe një Refresh token, me të cilin mund të përditësojmë Access token.
Aplikacioni duhet të ruajë tokenet në një vend të sigurt me një afat shëndetësor të gjatë, prandaj, derisa ne të tërheqim aksesin e marrë, aplikacioni nuk do të marrë një refresh token. Në fund, kam shtuar një kërkesë për të tërhequr tokenin, nëse aplikacioni përfundoi pa sukses dhe refresh token nuk u kthye, ai do të fillojë procesin nga e para (ne e konsideruam të pasigurt të ruajmë tokenet lokal në terminal, dhe nuk dëshirojmë ta komplikesojmë me kriptografi ose të hapim shpesh shfletuesin).
bëni {
$token_result = Merr-GoogleAuthToken
$token = $token_result.access_token
nëse ($token_result.refresh_token -eq $null) {
Write-Host ("Seanca nuk është shuar. Po revokoj tokenin...")
Invoke-WebRequest -Uri ("https://accounts.google.com/o/oauth2/revoke?token="+$token)
}
} ndërsa ($token_result.refresh_token -eq $null)
$refresh_token = $token_result.refresh_token
$minute = ([int]("{0:mm}" -f ([timespan]::fromseconds($token_result.expires_in))))+((Merr-date).Minute)-2
nëse ($minute -lt 0) {$minute += 60}
ndryshe nëse ($minute -gt 59) {$minute -=60}
$token_expire = @{
orë = ([int]("{0:hh}" -f ([timespan]::fromseconds($token_result.expires_in))))+((Merr-date).Hour)
minut = $minute
}
Siç e keni vënë re, kur revokohet tokeni, përdoret Invoke-WebRequest. Ndryshe nga Invoke-RestMethod, ai nuk kthen të dhënat e marra në një format të përshtatshëm për përdorim dhe tregon statusin e kërkesës.
Më pas skripti do të kërkojë të hyni emrin dhe mbiemrin e përdoruesit, duke gjeneruar login + email.
Kërkesat
KĂ«rkesat e ardhshme do tĂ« jenĂ« â sĂ« pari duhet tĂ« kontrollohet nĂ«se ekziston tashmĂ« njĂ« pĂ«rdorues me kĂ«tĂ« login pĂ«r tĂ« marrĂ« vendimin mbi formimin e njĂ« tĂ« ri ose pĂ«rfshirjen e aktualit.
Vendosa të realizoj të gjitha kërkesat në formatin e një funksioni me përzgjedhje, duke përdorur 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'])
}
}
}
}Ădo kĂ«rkesĂ« duhet tĂ« dĂ«rgojĂ« njĂ« titull Authorization, i cili pĂ«rmban llojin e tokenit dhe vetĂ« tokenin e Qasjes. Aktualisht, lloji i tokenit Ă«shtĂ« gjithmonĂ« Bearer. Duke qenĂ« se na nevojitet tĂ« kontrollojmĂ« qĂ« tokeni tĂ« mos jetĂ« i skaduar dhe ta azhurnojmĂ« pas njĂ« ore nga lĂ«shimi, kam parashikuar njĂ« kĂ«rkesĂ« pĂ«r njĂ« funksion tjetĂ«r, i cili kthen tokenin e Qasjes. Ky kod i njĂ«jtĂ« ndodhet nĂ« fillim tĂ« skriptit kur marrim tokenin e parĂ« tĂ« Qasjes:
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 i skaduar. 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ë loginit:
function Check_Google {
$query = (GoogleQuery 'SearchAccount' $username)
if ($query.users -ne $null) {
$user = $query.users[0]
Write-Host $user.name.fullName' - '$user.PrimaryEmail' - pezulluar: '$user.Suspended
$GAresult = $user
}
if ($GAresult) {
$return = $GAresult
} else {$return = 'gg'}
return $return
}Kërkesa e emailit: $query do të kërkojë API-në për të gjetur një përdorues me atë email, duke përfshirë aliasët. Po ashtu mund të përdoren wildcard: =, :, :{PREFIX}*.
PĂ«r tĂ« marrĂ« tĂ« dhĂ«nat pĂ«rdoret metoda GET, pĂ«r tĂ« futur tĂ« dhĂ«na (krijimi i llogarisĂ« ose shtimi i 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.
Skedari gjithashtu do të kërkojë numrin e telefonit (string pa validim) dhe nëse të përfshihet 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 shpik 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 nisur ndryshimin e llogarisë" -f mag
(GoogleQuery 'UpdateAccount' $query) | fl
write-host "Mos harro të kontrollosh grupet për $Username të aktivizuar në Google."
} else {
Write-Host "Duke nisur krijimin e llogarisë" -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 e përditësimit dhe krijimit të llogarive kanë një sintaksë të ngjashme, jo të gjitha fushat shtesë janë të detyrueshme, në seksionin me numrat e telefonit duhet të specifikohet një array që mund të përmbajë nga një regjistër me numrin dhe tipin e tij.
Për të shmangur një gabim gjatë shtimit të përdoruesit në grup, paraprakisht mund të kontrollojmë nëse ai tashmë është në këtë grup, duke marrë listën e 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ësimin e drejtpërdrejtë. Inkludimi i përdoruesit në grupin prind, në të cilin tashmë është një grup i fëmijës, të cilit përdoruesi i përket, do të jetë i suksesshëm.
Përfundim
Kështu mbetet të dërgojmë përdoruesit fjalëkalimin e llogarisë së re. Ne e bëjmë këtë përmes SMS, ndërsa informacioni i përgjithshëm me udhëzime dhe login e dërgojmë në postën personale, të cilën, së bashku me numrin e telefonit, e ofroi departamenti i rekrutimit. Si një alternativë, mund të kursejmë disa para dhe ta dërgojmë fjalëkalimin në një bisedë sekrete në Telegram, e cila mund të konsiderohet gjithashtu si faktori i dytë (përjashtime do të jenë makbukët).
Faleminderit që e lexuat deri në fund. Do të isha i lumtur të shihja sugjerime për përmirësimin e stilit të shkrimit të artikujve dhe ju uroj që të okoni sa më pak gabime gjatë shkruajtjes së skenarëve =)
Lista e lidhjeve që mund të jenë tematikisht të dobishme ose thjesht të përgjigjen pyetjeve që mund të kenë lindur:
Burimi: habr.com
