بررسی برخی تغییرات در Angular 8
- کامپایل سریعتری را فراهم میکند (انتشار در انگیولار 9)
- بررسی type در قالبها، خیلی بیشتر بهبود یافته است؛ بهگونهای که میتوان خطاهای بیشتری را در زمان build گرفت که باعث میشود کاربران در زمان runtime به آن خطاها برخورد نکنند (انتشار در انگیولار 9).
- bundleهای با سایز کوچکتری در مقایسه با سایز bundleهای کامپایل شدهی جاری
- کدهای تولید شده توسط Angular compiler، بسیار آسانتر، برای خواندن و درک انسان است.
- آخرین و مهمترین ویژگی مورد علاقه من این است که میتوان قالبها (templates) را debug کرد. من یقین دارم که این ویژگی توسط تعداد زیادی از توسعه دهندگان مورد توجه قرار خواهد گرفت .
ng serve --aot
"projects": { "app-name": { "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", }, "test": { "builder": "@angular-devkit/build-angular:karma", }, "lint": { "builder": "@angular-devkit/build-angular:tslint", }, "e2e": { "builder": "@angular-devkit/build-angular:protractor", } } } }
دلایل شانه خالی کردن از آزمایش واحد!
1- نوشتن آزمایشات زمان زیادی را به خود اختصاص خواهند داد.
مهمترین دلیلی که برنامهنویسها به سبب آن از نوشتن آزمایشات واحد امتناع میکنند، همین موضوع است. اکثر افراد به آزمایش بهعنوان مرحله آخر توسعه فکر میکنند. اگر این چنین است، بله! نوشتن آزمایشهای واحد واقعا سخت و زمانگیر خواهند بود. به همین جهت برای جلوگیری از این مساله روش pay-as-you-go مطرح شده است (ماخذ: کتاب Pragmatic Unit Testing در سی شارپ). یعنی با اضافه شدن هر واحد کوچکی به سیستم، آزمایش واحد آنرا نیز تهیه کنید. به این صورت در طول توسعه سیستم با باگهای کمتری نیز برخورد خواهید داشت چون اجزای آنرا در این حین به تفصیل مورد بررسی قرار دادهاید. اثر این روش را در شکل زیر میتوانید ملاحظه نمائید (تصویری از همان کتاب ذکر شده)
نوشتن آزمایشات واحد زمانبر هستند اما توسعه پیوسته آنها با به تاخیر انداختن آزمایشات به انتهای پروژه، همانند تصویر فوق تاثیر بسیار قابل توجهی در بهره وری شما خواهند داشت.
بنابراین اگر عنوان میکنید که وقت ندارید آزمایش واحد بنویسید، به چند سؤال زیر پاسخ دهید:
الف) چه مقدار زمان را صرف دیباگ کردن کدهای خود یا دیگران میکنید؟
ب) چه میزان زمان را صرف بازنویسی کدی کردهاید که تصور میرفت درست کار میکند اما اکنون بسیار مشکل زا ظاهر شده است؟
ج) چه مقدار زمان را صرف این کردهاید که منشاء باگ گزارش شده در برنامه را کشف کنید؟
برای افرادی که آزمایشات واحد را در حین پروسه توسعه در نظر نمیگیرند، این مقادیر بالا است و با ازدیاد تعداد خطوط سورس کدها، این ارقام سیر صعودی خواهند داشت.
تصویری از کتاب xUnit Test Patterns ، که بیانگر کاهش زمان و هزینه کد نویسی در طول زمان با رعایت اصول آزمایشات واحد است
2- اجرای آزمایشات واحد زمان زیادی را تلف میکند.
نباید اینطور باشد. عموما اجرای هزاران آزمایش واحد، باید در کسری از ثانیه صورت گیرد. (برای اطلاعات بیشتر به قسمت حد و مرز یک آزمایش واحد در قسمت قبل مراجعه نمائید)
3- امکان تهیه آزمایشات واحد برای کدهای قدیمی ( legacy code ) من وجود ندارد
برای بسیاری از برنامه نویسها، تهیه آزمایش واحد برای کدهای قدیمی بسیار مشکل است زیرا شکستن آنها به واحدهای کوچکتر قابل آزمایش بسیار خطرناک و پرهزینه است و ممکن است سبب از کار افتادن سیستم آنها گردد. اینجا مشکل از آزمایش واحد نیست. مشکل از ضعف برنامه نویسی آن سیستم است. روش refactoring ، طراحی مجدد و نوشتن آزمایشات واحد، به تدریج سبب طراحی بهتر برنامه از دیدگاههای شیءگرایی شده و نگهداری سیستم را در طولانی مدت سادهتر میسازد. آزمایشات واحد این نوع سیستمها را از حالت فلج بودن خارج میسازد.
4- کار من نیست که کدهای نوشته شده را آزمایش کنم!
باید درنظر داشته باشید که این هم کار شما نیست که انبوهی از کدهای مشکل دار را به واحد بررسی کننده آن تحویل دهید! همچنین اگر تیم آزمایشات و کنترل کیفیت به این نتیجه برسد که عموما از کدهای شما کمتر میتوان باگ گرفت، این امر سبب معروفیت و تضمین شغلی شما خواهد شد.
همچنین این کار شما است که تضمین کنید واحد تهیه شده مقصود مورد نظر را ارائه میدهد و اینکار را با ارائه یک یا چندین آزمایش واحد میتوان اثبات کرد.
5- تنها قسمتی از سیستم به من واگذار شده است و من دقیقا نمیدانم که رفتار کلی آن چیست. بنابراین آن را نمیتوانم آزمایش کنم!
اگر واقعا نمیدانید که این کد قرار است چه کاری را انجام دهید به طور قطع الان زمان مناسبی برای کد نویسی آن نیست!
6- کد من کامپایل میشود!
باید دقت داشت که کامپایلر فقط syntax کدهای شما را بررسی کرده و خطاهای آنرا گوشزد میکند و نه نحوهی عملکرد آنرا.
7- من برای نوشتن آزمایشات حقوق نمیگیرم!
باید اذعان داشت که به شما جهت صرف تمام وقت یک روز خود برای دیباگ کردن یک خطا هم حقوق نمیدهند! شما برای تهیه یک کد قابل قبول و قابل اجرا حقوق میگیرید و آزمایش واحد نیز ابزاری است جهت نیل به این مقصود (همانند یک IDE و یا یک کامپایلر).
8- احساس گناه خواهم کرد اگر تیم فنی کنترل کیفیت و آزمایشات را از کار بی کار کنم!!
نگران نباشید، این اتفاق نخواهد افتاد! بحث ما در اینجا آزمایش کوچکترین اجزا و واحدهای یک سیستم است. موارد دیگری مانند functional testing, acceptance testing, performance & environmental testing, validation & verification, formal analysis توسط تیمهای کنترل کیفیت و آزمایشات هنوز باید بررسی شوند.
9- شرکت من اجازه اجرای آزمایشات واحد را بر روی سیستمهای در حال اجرا نمیدهد.
قرار هم نیست بدهد! چون دیگر نام آن آزمایش واحد نخواهد بود. این آزمایشات باید بر روی سیستم شما و توسط ابزار و امکانات شما صورت گیرد.
پ.ن.
در هشتمین دلیل ذکر شده، از acceptance testing نامبرده شده. تفاوت آن با unit testing به صورت زیر است:
آزمایش واحد:
توسط برنامه نویسها تعریف میشود
سبب اطمینان خاطر برنامه نویسها خواهد شد
واحدهای کوچک سیستم را مورد بررسی قرار میدهد
یک آزمایش سطح پائین ( low level ) به شمار میرود
بسیار سریع اجرا میشود
به صورت خودکار (100 درصد خودکار است) و با برنامه نویسی قابل کنترل است
اما در مقابل آزمایش پذیرش به صورت زیر است:
توسط مصرف کنندگان تعریف میشود
سبب اطمینان خاطر مصرف کنندگان میشود.
کل برنامه مورد آزمایش قرار میگیرد
یک آزمایش سطح بالا ( high level ) به شمار میرود
ممکن است طولانی باشد
عموما به صورت دستی یا توسط یک سری اسکریپت اجرا میشود
مثال : گزارش ماهیانه باید جمع صحیحی از تمام صفحات را در آخرین برگه گزارش به همراه داشته باشد
ادامه دارد...
آسیب پذیری SQL Injection یا به اختصار SQLi
تزریق SQL، یکی از قدیمی ترین، شایعترین و مخربترین آسیب پذیریها، برای برنامهها میباشد و در صورت برقراری شرایط مناسب جهت حمله و با اعمال نفوذ، از طریق تزریق SQL ، مهاجم میتواند با دور زدن فرآیندهای اعتبارسنجی و احراز هویت یک برنامه، به تمامی محتوای پایگاه دادهی آن و گاها کنترل سرور، دسترسی پیدا کند. این حمله برای افزودن، ویرایش و حذف رکوردهای یک پایگاه داده مبتنی بر SQL انجام میشود.
عملکرد SQL Injection
برای اجرای SQLهای مخرب در برنامههایی که از پایگاههای دادهی مبتنی بر SQL مانند (SQL Server ،MySQL ،PostgreSQL ،Oracle و ...) استفاده میکنند، هکر یا مهاجم در اولین گام باید به دنبال ورودیهایی در برنامه باشد که درون یک درخواست SQL قرار گرفته باشند (مانند صفحات لاگین، ثبت نام، جستجو و ...).
کد زیر را در نظر بگیرید:
# Define POST variables uname = request.POST['username'] passwd = request.POST['password'] # SQL query vulnerable to SQLi sql = "SELECT id FROM users WHERE username='" + uname + "' AND password='" + passwd + "'" # Execute the SQL statement database.execute(sql)
اکنون ورودی password را برای نفوذ، تست میکنیم. مهاجم بدون داشتن نام کاربری، قصد دور زدن احراز هویت را دارد. بجای password عبارت زیر را قرار میدهد:
password' OR 1=1
در نهایت در بانک اطلاعاتی دستور زیر اجرا میشود:
SELECT id FROM users WHERE username='username' AND password= 'password' OR 1=1'
میدانیم که 1=1 است. پس بدون در نظر گرفتن اینکه شما برای username و password چه چیزی را وارد نمودید، عبارت درست در نظر گرفته میشود:
شرط اول and شرط دوم = نتیجه or 1=1 چون 1=1 است همیشه شرط کوئری درست خواهد بود
معمولا در بانک اطلاعاتی، اولین کاربری که وارد میکنند Administrator برنامه میباشد. پس به احتمال قوی شما میتوانید با مجوز ادمین به برنامه وارد شوید. البته میتوان با دانستن تنها نام کاربری هم بهراحتی با گذاشتن در قسمت username بدون دانستن password، به برنامه وارد شد؛ زیرا میتوان شرط چک کردن password را کامنت نمود:
-- MySQL, MSSQL, Oracle, PostgreSQL, SQLite ' OR '1'='1' -- ' OR '1'='1' /* -- MySQL ' OR '1'='1' # -- Access (using null characters) ' OR '1'='1' %00 ' OR '1'='1' %16
ابزارهایی برای تست آسیب پذیری SQLi
2) اسکنر اکانتیکس
چگونه از SQL Injection جلوگیری کنیم
1) روی دادههایی که از کاربر دریافت میگردد، اعتبار سنجی سمت کلاینت و سرور انجام شود. اگر فقط به اعتبارسنجی سمت کلاینت اکتفا کنید، هکر بهراحتی با استفاده از پروکسی، دادهها را تغییر میدهد. ورودیها را فیلتر و پاکسازی و با لیست سفید یا سیاه بررسی کنید ( ^ , ^, ^, ^ ).
2) از کوئریهایی که بدون استفاده از پارامتر از کاربر ورودی گرفته و درون یک درخواستSQL قرار میگیرند، اجتناب کنید:
[HttpGet] [Route("nonsensitive")] public string GetNonSensitiveDataById() { using (SqlConnection connection = new SqlConnection(_configuration.GetValue<string>("ConnectionString"))) { connection.Open(); SqlCommand command = new SqlCommand($"SELECT * FROM NonSensitiveDataTable WHERE Id = {Request.Query["id"]}", connection); using (var reader = command.ExecuteReader()) { if (reader.Read()) { string returnString = string.Empty; returnString += $"Name : {reader["Name"]}. "; returnString += $"Description : {reader["Description"]}"; return returnString; } else { return string.Empty; } } } }
با استفاده از پارامتر: (بهتر است نوع دیتا تایپ پارامتر و طول آن ذکر شود)
[HttpGet] [Route("nonsensitivewithparam")] public string GetNonSensitiveDataByNameWithParam() { using (SqlConnection connection = new SqlConnection(_configuration.GetValue<string>("ConnectionString"))) { connection.Open(); SqlCommand command = new SqlCommand($"SELECT * FROM NonSensitiveDataTable WHERE Name = @name", connection); command.Parameters.AddWithValue("@name", Request.Query["name"].ToString()); using (var reader = command.ExecuteReader()) { if (reader.Read()) { string returnString = string.Empty; returnString += $"Name : {reader["Name"]}. "; returnString += $"Description : {reader["Description"]}"; return returnString; } else { return string.Empty; } } } }
3) از Stored Procedureها استفاده کنید و بصورت پارامتری دادههای مورد نیاز را به آنها پاس دهید: (بهتر است نوع دیتا تایپ پارامتر و طول آن ذکر شود)
[HttpGet] [Route("nonsensitivewithsp")] public string GetNonSensitiveDataByNameWithSP() { using (SqlConnection connection = new SqlConnection(_configuration.GetValue<string>("ConnectionString"))) { connection.Open(); SqlCommand command = new SqlCommand("SP_GetNonSensitiveDataByName", connection); command.CommandType = System.Data.CommandType.StoredProcedure; command.Parameters.AddWithValue("@name", Request.Query["name"].ToString()); using (var reader = command.ExecuteReader()) { if (reader.Read()) { string returnString = string.Empty; returnString += $"Name : {reader["Name"]}. "; returnString += $"Description : {reader["Description"]}"; return returnString; } else { return string.Empty; } } } }
4) اگر از داینامیک کوئری استفاده میکنید، دادههای مورد استفادهی در کوئری را بصورت پارامتری ارسال کنید:
فرض کنید چنین جدولی دارید
CREATE TABLE tbl_Product ( Name NVARCHAR(50), Qty INT, Price FLOAT ) GO INSERT INTO tbl_Product (Name, Qty, Price) VALUES (N'Shampoo', 200, 10.0); INSERT INTO tbl_Product (Name, Qty, Price) VALUES (N'Hair Clay', 400, 20.0); INSERT INTO tbl_Product (Name, Qty, Price) VALUES (N'Hair Tonic', 300, 30.0);
یک پروسیجر را دارید که عملیات جستجو را انجام میدهد و از داینامیک کوئری استفاده میکند.
ALTER PROCEDURE sp_GetProduct(@Name NVARCHAR(50)) AS BEGIN DECLARE @sqlcmd NVARCHAR(MAX); SET @sqlcmd = N'SELECT * FROM tbl_Product WHERE Name = ''' + @Name + ''''; EXECUTE(@sqlcmd) END
با اینکه از Stored Procedure استفاده میکنید، باز هم در معرض خطر SQLi میباشید. فرض کنید هکر چنین درخواستی را ارسال میکند:
Shampoo'; DROP TABLE tbl_Product; --
نتیجه، تبدیل به دستور زیر میشود:
SELECT * FROM tbl_Product WHERE Name = 'Shampoo'; DROP TABLE tbl_Product; --'
برای جلوگیری از SQLi در کوئریهای داینامیک SP بشکل زیر عمل میکنیم:
ALTER PROCEDURE sp_GetProduct(@Name NVARCHAR(50)) AS BEGIN DECLARE @sqlcmd NVARCHAR(MAX); DECLARE @params NVARCHAR(MAX); SET @sqlcmd = N'SELECT * FROM tbl_Product WHERE Name = @Name'; SET @params = N'@Name NVARCHAR(50)'; EXECUTE sp_executesql @sqlcmd, @params, @Name; END
5) میتوان از تنظیمات IIS یا وب سرورهای دیگر برای جلوگیری از SQLi استفاده نمود.
6) استفاده از چند کاربرِ دیتابیس در برنامه و بکارگیری سطح دسترسی محدود و مناسب( ^ , ^ ).
7) از ORM استفاده کنید و اگر نیاز به سرعت بیشتری دارید از یک Micro ORM استفاده کنید؛ با در نظر داشتن نکات لازم.