DPSinfraDocument
12 min

1. Mục đích

Quy định thống nhất một số nguyên tắc về lập trình cho nhân viên lập trình nhằm tránh được các lỗi cơ bản về bảo mật và truy xuất dữ liệu trong lập trình các ứng dụng.

2. Các nguyên tắc bắt buộc

2.1 Tương tác với database

2.1.1 Quy định chung

Đối với tất cả ứng dụng không dùng entity framework khi tương tác với cơ sở dữ liệu đều phải thực hiện thông qua thư viện kết nối với database của DPS là DpsConnection.

Tất cả các câu lệnh sqlcommand có tham số truyền vào bắt buộc phải truyền theo dạng parameters, tuyệt đối không sử dụng cách nối chuổi để tránh lỗi SQL Injection.

Ví dụ: "select * from tablename where id=" + request["id"] -> "select * from tablename where id=@id"

2.1.2 Làm việc với thư viện DpsConnection

Đối với database có quá nhiều kết nối đến sẽ dẩn đến vấn đề không thể mở thêm được kết nối nếu không đóng các kết nối cũ. Nhằm tránh việc quá tải cho database dẩn đến ứng dụng không thể truy cập dữ liệu do không thể kết nối đến database lập trình viên cần tuân thủ các quy tắc sau:

  • Sử dụng câu lệnh using để mở kết nối đến database: việc này có thể tránh được việc quên đóng kết nối sau khi kết thúc truy xuất cơ sở dữ liệu.

Ví dụ:

using(dpsconnection conn=new dpsconnection())
{

      //code

}
  • Không được mở 1 kết nối khác đến cùng 1 database khi đã có 1 kết nối đang tồn tại:

Ví dụ:

Function1()
{

  Using(dpsconnection conn=new dpsconnection())
  {
      //Code

      Using(dpsconnection conn1=new dpsconnection())
      {

          //Code

      }

      Function2();   -> Function2(conn);
  }

}

Function2()
{      
       
  Using(Dpsconnection conn=new Dpsconnection())
  {

      //Code

  }

}

Function2(Dpsconnection conn) 
{ 

    //code

}
  • Đảm bảo việc commit khi sử dụng Transaction: Transaction chỉ được dùng trong trường hợp cần đảm bảo tính toàn vẹn khi cập nhật nhiều dữ liệu. Thường sử dụng khi cập nhật dữ liệu vào nhiều table khác nhau và cần đảm bảo dữ liệu được update vào tất cả các table thì mới hoàn thành, ngược lại khi xảy ra bất kì lỗi gì thì toàn bộ các thao tác trước đó sẽ bị hủy. Để bắt đầu transaction dùng function Begintransaction trong dpsconnection và khi sử dụng phải đảm bảo endtransaction hoặc rollbacktransaction dù có xảy ra bất kỳ exception nào. Vì vậy phải đảm bảo việc bẩy lỗi khi khi đã bắt đầu transaction. Ví dụ:
Using(Dpsconnection conn=new Dpsconnection())
{

    Conn.Begintransaction();

    Try{

    //Code thao tác dữ liệu

    } Catch (Exception ex)
    {

        ///Hủy tất cả cập nhật nếu có phát sinh exception

        Conn.Rollbacktransaction();

    }

    ///Phải có câu lệnh này dữ liệu mới được cập nhật vào database (nếu chưa rollback) và đóng transaction lại.

    Conn.Endtransaction();

}
  • Lưu ý: Tuyệt đối không tạo thêm 1 kết nối nào khác ở giữa begintransaction và endtransaction.

2.2 Kiểm tra trước khi thao tác trên dữ liệu

2.2.1 Các định nghĩa

  • Pham vi thao tác dữ liệu: là quy định về các dữ liệu mà một người dùng được phép thao tác (xem, sửa, xóa), được quy định cụ thể trong phần nghiệp vụ của một phần mềm. Ví dụ: Đối với phần mềm nhân sự nhân viên nhân sự ở chi nhánh A chỉ được thêm, sửa các hồ sơ ở chi nhánh đó, không được phép chỉnh sửa hay xem các hồ sơ ở chi nhánh khác trong cùng công ty.

2.2.2 Kiểm tra quyền

Tất cả API get, update dữ liệu đều cần phải kiểm tra phân quyền (nếu chức năng có phân quyền) trước khi thực hiện chức năng. Đối với các api dùng đoạn code sau để kiểm tra quyền.

[CusAuthorize(Roles = "<role>")]

2.2.3 Kiểm tra token

Đối với các hàm chức năng dùng nội bộ trong phần mềm cần phải kiểm tra token đăng nhập của người dùng hợp lệ trước khi thực hiện.

Đối với các API tương tác với hệ thống của bên thứ 3 cần phải authencation key của bên thứ 3 trước khi thực hiện bất kỳ thao tác nào khác.

2.2.4 Kiểm tra phạm vi thao tác dữ liệu

Phạm vi thao tác dữ liệu được quy định trong nghiệp vụ của phần mềm. Tuy nhiên đối với các phần mềm Saas phục vụ cho nhiều khách hàng trên cùng 1 hệ thống thì bắt buộc phải có 1 logic là nhân viên của khách hàng A chỉ được thao tác các dữ liệu thuộc khách hàng A và không thể đụng được các dữ liệu bất kỳ dữ liệu nào của 1 khách hàng khác.

  • Đối với chức năng get: chỉ lấy dữ liệu trong phạm vi người dùng được thao tác.
  • Đối với chức năng sửa, xóa: phải kiểm tra dữ liệu có thuộc phạm vi người dùng được thao tác hay không.

Ví dụ về cách khai thác lỗi không kiểm tra phạm vi:

Khi bấm nút "Tạo tài khoản cho nhân viên", admin có thể thiết lập tên đăng nhập và mật khẩu cho người dùng, sau khi gửi request thì người dùng này có thể đăng nhập vào hệ thống. Khi gửi request thì hệ thống sẽ gọi API để update dữ liệu

Bằng cách gọi lại request này và thay trường "Id_nv", một người có thể cài đặt tên đăng nhập và mật khẩu cho một id nhân viên bất kì và sau đó có thể đăng nhập vào tài khoản.

2.3 Gọi API đối với backend/frontend

Việc call 1 resful api có thể trả về lỗi do server tạm thời lỗi hoặc vì 1 lý do gì đó mà trả về lỗi. Nếu báo lỗi ngay lập tức có thể ảnh hưởng nhiều đến người sử dụng. Vì vậy 1 api khi gọi lỗi cần phải được gọi lại 1 số lần với độ trễ có thể tăng lên.

Chỉ gọi lại API trong các trường hợp sau:

  • Phương thức của api là get. Không gọi lại phương thức post vì có khả năng bị trùng lặp dữ liệu.
  • Chỉ call lại nếu các http status code trả về là: 408, 500, 502, 503, 504, 522, 524
  • Số lần call lại: 3 lần. Nếu quá 3 lần vẩn trả về lỗi thì báo lỗi lên giao diện.
  • Độ trễ (backoff) tăng dần sau mổi lần thất bại: 300, 600, 1200ms.

3. Quy định về xử lý thời gian

Để có thể hỗ trợ việc lưu trữ và hiển thị thời gian theo nhiều múi giờ khác nhau tùy theo timezone của người dùng thì cần thống nhất việc lưu trữ thời gian theo 1 múi giờ chuẩn, cụ thể sẽ lưu theo timezone UTC. Quy định về lưu trữ và xử lý thời gian như sau:

3.1 Đối với ứng dụng ở client (front-end/back-end/mobile app)

  • Khi gửi thông tin về API, các thông tin về thời gian cần chuyển sang UTC trước khi gửi lên server.
  • Khi nhận thông tin từ API trả về thì thời gian đó theo giờ UTC, client cần chuyển giờ đó sang localtime (thời gian theo timezone của client) trước khi hiển thị lên giao diện cho người dùng.

3.2 Đối với API

Khi lưu trữ dữ liệu thời gian xuống database thì lưu theo giờ UTC:

  • Dữ liệu truyền từ client: Đã truyền theo giờ UTC
  • Dữ liệu giờ lấy từ giờ hệ thống: Kiểm tra time zone của hệ thống nếu không phải UTC thì cần chuyển sang UTC trước khi lưu trữ.

Khi trả thông tin thời gian về cho client thì trả theo thời gian UTC

3.3 Đối với xử lý thời gian trên database

Vì thời gian lưu database là giờ UTC nên khi xử lý thời gian trong các câu lệnh query như GetDate() cần phải chuyển về UTC để cùng 1 timezone.