API کے ذریعے PowerShell سے گوگل صارفین بنانا

ہیلو!

یہ مضمون بیان کرے گا کہ G Suite صارفین کو جوڑ توڑ کرنے کے لیے Google API کے ساتھ PowerShell تعامل کو کیسے نافذ کیا جائے۔

ہم اپنی تنظیم میں متعدد داخلی اور کلاؤڈ سروسز استعمال کرتے ہیں۔ زیادہ تر حصے کے لیے، توثیق صرف گوگل یا ایکٹو ڈائریکٹری تک محدود ہے، جس کے درمیان ہم نقل برقرار نہیں رکھ سکتے۔ لہذا، جب کوئی نیا ملازم شامل ہوتا ہے، تو ہمیں دونوں سسٹمز میں ایک اکاؤنٹ بنانے اور فعال کرنے کی ضرورت ہوتی ہے۔ اس عمل کو خودکار کرنے کے لیے، ہم نے ایک اسکرپٹ لکھنے کا فیصلہ کیا ہے جو معلومات اکٹھا کرے اور اسے دونوں سروسز کو بھیجے۔

اجازت

تقاضوں کا مسودہ تیار کرتے وقت، ہم نے اجازت کے لیے حقیقی انسانی منتظمین کو استعمال کرنے کا فیصلہ کیا۔ یہ حادثاتی یا جان بوجھ کر بڑی تبدیلیوں کے دوران اعمال کے تجزیہ کو آسان بناتا ہے۔

Google APIs توثیق اور اجازت کے لیے OAuth 2.0 پروٹوکول استعمال کرتے ہیں۔ استعمال کے منظرنامے اور مزید تفصیلی وضاحت یہاں مل سکتی ہے: Google APIs تک رسائی کے لیے OAuth 2.0 استعمال کرنا.

میں نے ڈیسک ٹاپ ایپلی کیشنز میں اجازت کے لیے استعمال ہونے والے منظر نامے کا انتخاب کیا۔ سروس اکاؤنٹ استعمال کرنے کا آپشن بھی ہے، جس کے لیے صارف سے کسی اضافی اقدامات کی ضرورت نہیں ہے۔

نیچے دی گئی تصویر گوگل پیج سے منتخب منظر نامے کی منصوبہ بندی کی وضاحت ہے۔

API کے ذریعے PowerShell سے گوگل صارفین بنانا

  1. سب سے پہلے، ہم صارف کو Google اکاؤنٹ کے تصدیقی صفحہ پر بھیجتے ہیں، GET پیرامیٹرز کی وضاحت کرتے ہوئے:
    • درخواست کی شناخت
    • جن علاقوں تک ایپلیکیشن کو رسائی کی ضرورت ہے۔
    • وہ پتہ جس پر صارف کو طریقہ کار مکمل کرنے کے بعد ری ڈائریکٹ کیا جائے گا۔
    • جس طرح سے ہم ٹوکن کو اپ ڈیٹ کریں گے۔
    • تصدیقی کوڈ
    • تصدیقی کوڈ ٹرانسمیشن فارمیٹ

  2. اجازت مکمل ہونے کے بعد، صارف کو پہلی درخواست میں بیان کردہ صفحہ پر بھیج دیا جائے گا، جس میں GET پیرامیٹرز میں غلطی یا اجازت نامے کوڈ پاس کیا گیا ہے۔
  3. درخواست (اسکرپٹ) کو یہ پیرامیٹرز حاصل کرنے کی ضرورت ہوگی اور، اگر کوڈ موصول ہوتا ہے، تو ٹوکن حاصل کرنے کے لیے اگلی درخواست کو انجام دیں۔
  4. اگر درخواست درست ہے تو، Google API واپس کرتا ہے:
    • ایک رسائی ٹوکن جس کے ساتھ ہم درخواستیں کر سکتے ہیں۔
    • اس ٹوکن کی میعاد ختم ہونے کی تاریخ
    • رسائی ٹوکن کو ریفریش کرنے کے لیے ریفریش ٹوکن درکار ہے۔

سب سے پہلے، آپ کو گوگل API کنسول پر جانے کی ضرورت ہے: اسناد - گوگل API کنسول، مطلوبہ ایپلیکیشن منتخب کریں، اور اسناد سیکشن میں ایک OAuth کلائنٹ شناخت کنندہ بنائیں۔ وہاں (یا بعد میں، تخلیق کردہ شناخت کنندہ کی خصوصیات میں)، آپ کو وہ پتے بتانے کی ضرورت ہے جن پر ری ڈائریکشن کی اجازت ہے۔ ہمارے معاملے میں، یہ مختلف بندرگاہوں کے ساتھ متعدد لوکل ہوسٹ اندراجات ہوں گے (نیچے دیکھیں)۔

اسکرپٹ کے الگورتھم کو پڑھنے میں آسان بنانے کے لیے، آپ ایک علیحدہ فنکشن میں پہلے مراحل کو نکال سکتے ہیں جو ایپلیکیشن کے لیے رسائی اور ریفریش ٹوکنز کو واپس کرے گا:

$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

ہم OAuth کلائنٹ ID خصوصیات سے حاصل کردہ کلائنٹ ID اور کلائنٹ سیکریٹ سیٹ کرتے ہیں، اور کوڈ کی تصدیق کنندہ 43 اور 128 حروف کے درمیان ایک تار ہے جسے تصادفی طور پر غیر محفوظ شدہ حروف سے تیار کیا جانا چاہئے: [A-Z] / [a-z] / [0-9] / "-" / ""۔ / "_" / "~"۔

اس کوڈ کو پھر دوبارہ منتقل کیا جائے گا۔ یہ اس خطرے کو ختم کرتا ہے جس کے تحت حملہ آور صارف کے لاگ ان ہونے کے بعد ری ڈائریکٹ کے ذریعے واپس آنے والے ردعمل کو روک سکتا ہے۔
آپ موجودہ درخواست میں کوڈ کی تصدیق کنندہ کو واضح متن میں بھیج سکتے ہیں (جس سے یہ بے معنی ہو جاتا ہے - یہ صرف ان سسٹمز کے لیے موزوں ہے جو SHA256 کو سپورٹ نہیں کرتے ہیں)، یا SHA256 الگورتھم کا استعمال کرتے ہوئے ایک ہیش بنا کر، جسے BASE64Url میں انکوڈ کیا جانا چاہیے (بیس64 سے دو ٹیبل علامتوں سے مختلف ہے) اور لائن کو ہٹا دیں: = کریکٹر کو ہٹا دیں۔

اگلا، اجازت کے بعد جواب حاصل کرنے کے لیے ہمیں مقامی مشین پر HTTP کو سننا شروع کرنا ہوگا، جو ری ڈائریکٹ کے ذریعے واپس کیا جائے گا۔

انتظامی کام ایک سرشار سرور پر انجام پاتے ہیں، اور ہم ایک ہی وقت میں متعدد منتظمین کے اسکرپٹ کو چلانے کے امکان کو رد نہیں کر سکتے۔ لہذا، یہ موجودہ صارف کے لیے تصادفی طور پر ایک بندرگاہ کا انتخاب کرے گا۔ تاہم، میں نے پہلے سے طے شدہ بندرگاہوں کی وضاحت کی ہے کیونکہ انہیں API کنسول میں قابل اعتماد بندرگاہوں کے طور پر بھی شامل کرنے کی ضرورت ہے۔

رسائی_ٹائپ = آف لائن اس کا مطلب ہے کہ ایپلیکیشن براؤزر کے ساتھ صارف کے تعامل کے بغیر اپنے طور پر ایک میعاد ختم ہونے والے ٹوکن کی تجدید کر سکتی ہے،
ریسپانس_ٹائپ = کوڈ اس فارمیٹ کی وضاحت کرتا ہے کہ کوڈ کو کیسے واپس کیا جائے گا (پرانے اجازت کے طریقہ کار کا حوالہ، جب صارف براؤزر سے کوڈ کو اسکرپٹ میں کاپی پیسٹ کرتا ہے)
گنجائش دائرہ کار اور رسائی کی اقسام کی وضاحت کرتا ہے۔ انہیں خالی جگہوں یا %20 (یو آر ایل انکوڈنگ کے مطابق) سے الگ کیا جانا چاہیے۔ رسائی کے دائرہ کار اور اقسام کی فہرست یہاں مل سکتی ہے: Google APIs کے لیے OAuth 2.0 اسکوپس.

اجازت کا کوڈ حاصل کرنے کے بعد، ایپلیکیشن براؤزر کو قریبی پیغام واپس کرتی ہے، پورٹ پر سننا بند کر دیتی ہے، اور ٹوکن حاصل کرنے کے لیے POST کی درخواست بھیجتی ہے۔ ہم API کنسول سے پہلے سے متعین آئی ڈی اور راز کی وضاحت کرتے ہیں، وہ پتہ جس پر صارف کو ری ڈائریکٹ کیا جائے گا، اور پروٹوکول کی تفصیلات کے مطابق grant_type۔

جواب میں، ہمیں ایک رسائی ٹوکن، سیکنڈوں میں اس کی میعاد کی مدت، اور ایک ریفریش ٹوکن ملے گا، جس کے ساتھ ہم رسائی ٹوکن کو اپ ڈیٹ کر سکتے ہیں۔

ایپلیکیشن کو ٹوکنز کو محفوظ مقام پر محفوظ کرنا چاہیے جس میں طویل شیلف لائف ہے، لہذا جب تک ہم دی گئی رسائی کو منسوخ نہیں کرتے، ایپلیکیشن کو ریفریش ٹوکن موصول نہیں ہوگا۔ آخر میں، میں نے ٹوکن کو منسوخ کرنے کی درخواست شامل کی۔ اگر ایپلیکیشن ناکام ہو جاتی ہے اور ریفریش ٹوکن واپس نہیں آتا ہے، تو یہ عمل دوبارہ شروع کر دے گا (ہم نے ٹرمینل پر ٹوکنز کو مقامی طور پر ذخیرہ کرنا غیر محفوظ سمجھا، اور ہم خفیہ نگاری کے ساتھ چیزوں کو پیچیدہ نہیں کرنا چاہتے تھے یا براؤزر کو اکثر کھولنا نہیں چاہتے تھے)۔

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'])
      }
    }
  }
}

ہر درخواست کو ایک اختیاری ہیڈر بھیجنا چاہیے جس میں ٹوکن کی قسم اور خود رسائی ٹوکن ہو۔ فی الحال، ٹوکن کی قسم ہمیشہ بیئرر ہوتی ہے۔ چونکہ ہمیں اس بات کی تصدیق کرنے کی ضرورت ہے کہ ٹوکن کی میعاد ختم نہیں ہوئی ہے اور اسے جاری کیے جانے کے ایک گھنٹہ بعد اس کی تجدید کی جائے گی، اس لیے میں نے درخواست کو ایک دوسرے فنکشن کے لیے بیان کیا جو رسائی ٹوکن کو واپس کرتا ہے۔ پہلا رسائی ٹوکن وصول کرتے وقت کوڈ کا یہی ٹکڑا اسکرپٹ کے شروع میں ہوتا ہے:

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
}

ای میل:$ استفسار کی درخواست API سے اس مخصوص ای میل ایڈریس والے صارف کو تلاش کرنے کے لیے کہے گی، بشمول عرفی نام۔ آپ وائلڈ کارڈ بھی استعمال کر سکتے ہیں: =، :، :{PREFIX}*.

ڈیٹا کو بازیافت کرنے کے لیے، GET درخواست کا طریقہ استعمال کریں، ڈیٹا داخل کرنے کے لیے (اکاؤنٹ بنائیں یا کسی ممبر کو گروپ میں شامل کریں) - POST کریں، موجودہ ڈیٹا کو اپ ڈیٹ کرنے کے لیے - PUT، ریکارڈ کو حذف کرنے کے لیے (مثال کے طور پر، کسی گروپ سے ممبر) - حذف کریں۔

اسکرپٹ ایک فون نمبر (ایک غلط سٹرنگ) اور علاقائی تقسیم گروپ میں شامل ہونے کے بارے میں بھی پوچھے گا۔ یہ منتخب کردہ ایکٹو ڈائریکٹری 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 = 'moscowoffice@rocketguys.com'
    email = $email
  }
  (GoogleQuery 'AddMember' $query) | fl
}

اپ ڈیٹ اور اکاؤنٹ بنانے کے فنکشنز میں ایک جیسی ترکیب ہوتی ہے۔ تمام اضافی فیلڈز کی ضرورت نہیں ہے۔ فون نمبر کے سیکشن میں، آپ کو ایک صف کی وضاحت کرنی چاہیے جس میں نمبر اور اس کی قسم کے ساتھ کم از کم ایک اندراج شامل ہو۔

کسی صارف کو کسی گروپ میں شامل کرتے وقت غلطی سے بچنے کے لیے، ہم پہلے ہی گروپ ممبران کی فہرست حاصل کر کے یا خود صارف سے کمپوزیشن حاصل کر کے چیک کر سکتے ہیں کہ آیا صارف پہلے سے ہی اس گروپ کا ممبر ہے۔

کسی مخصوص صارف کی گروپ کی رکنیت کے بارے میں استفسار کرنا غیر تکراری ہوگا اور یہ صرف فوری رکنیت کو ظاہر کرے گا۔ کسی صارف کو پیرنٹ گروپ میں شامل کرنا جس میں پہلے سے ہی چائلڈ گروپ ہے جس کا صارف ممبر ہے کامیاب ہو جائے گا۔

حاصل يہ ہوا

بس صارف کو نئے اکاؤنٹ کا پاس ورڈ بھیجنا باقی ہے۔ ہم یہ ایس ایم ایس کے ذریعے کرتے ہیں، اور عام معلومات بشمول ہدایات اور لاگ ان، محکمہ HR کی طرف سے فراہم کردہ ذاتی ای میل ایڈریس پر، فون نمبر کے ساتھ بھیجتے ہیں۔ متبادل طور پر، آپ پیسے بچا سکتے ہیں اور خفیہ ٹیلی گرام چیٹ کے ذریعے پاس ورڈ بھیج سکتے ہیں، جسے دوسرا عنصر بھی سمجھا جا سکتا ہے (میک بکس ایک استثناء ہیں)۔

آخر تک پڑھنے کا شکریہ۔ میں اپنے لکھنے کے انداز کو بہتر بنانے کے لیے تجاویز سننا پسند کروں گا، اور مجھے امید ہے کہ آپ سکرپٹ لکھتے وقت کم غلطیاں کریں گے =)

لنکس کی ایک فہرست جو موضوعی طور پر مفید ہو سکتی ہے یا کسی بھی سوال کے جواب دے سکتی ہے جو پیدا ہو سکتے ہیں:

ماخذ: www.habr.com

DDoS تحفظ، VPS VDS سرورز والی سائٹوں کے لیے قابل اعتماد ہوسٹنگ خریدیں۔ DDoS تحفظ، VPS VDS سرورز کے ساتھ قابل اعتماد ویب سائٹ ہوسٹنگ خریدیں۔ ProHoster