Inappropriate coding practices - Go

Inappropriate coding practices - Go

Need

Enforcement of proper encapsulation in coding practices

Context

  • Usage of Go 1.16 for building high-performance and efficient applications
  • Usage of Gin for building web applications in Go

Description

Non compliant code

        package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

type User struct {
	PublicID string
	Username string
	Password string
}

func main() {
	r := gin.Default()

	r.GET("/user/:id", func(c *gin.Context) {
		id := c.Param("id")
		user := User{id, "test", "test123"}
		c.JSON(200, user)
	})

	r.Run()
}
        
        

The above code defines a User struct with all fields as public. In Go, a variable is exported (public) if it begins with a capital letter. In this case, PublicID, Username, and Password are all public fields.

This is a security vulnerability because it violates the principle of encapsulation. Encapsulation is one of the fundamental concepts in object-oriented programming (OOP). It describes the bundling of data with the methods that operate on that data.

In this case, by making the Password field public, we are potentially exposing sensitive data. Anyone with an instance of the User struct can read or modify the password.

Moreover, this violates the principle of least privilege, which states that code should only have the permissions it needs to perform its task and no more. By making the Password field public, we are giving more permissions than necessary.

This inappropriate coding practice can lead to data leakage or unauthorized data modification if the struct is used inappropriately. Therefore, it is a good practice to always encapsulate your data and provide public getter and setter methods to access and modify the data.

Steps

  • Step 1: Use private access modifiers for the fields of the User struct.
  • Step 2: Provide getter and setter methods to access and modify the fields of the User struct.
  • Step 3: Update the code to use the getter methods instead of directly accessing the fields.
  • Step 4: Update the code to use the setter methods instead of directly modifying the fields.

Compliant code

        package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

type User struct {
	privateID string
	username  string
	password  string
}

func (u *User) GetID() string {
	return u.privateID
}

func (u *User) GetUsername() string {
	return u.username
}

func (u *User) GetPassword() string {
	return u.password
}

func (u *User) SetID(id string) {
	u.privateID = id
}

func (u *User) SetUsername(username string) {
	u.username = username
}

func (u *User) SetPassword(password string) {
	u.password = password
}

func main() {
	r := gin.Default()

	r.GET("/user/:id", func(c *gin.Context) {
		id := c.Param("id")
		user := User{}
		user.SetID(id)
		user.SetUsername("test")
		user.SetPassword("test123")
		c.JSON(200, gin.H{
			"id":       user.GetID(),
			"username": user.GetUsername(),
			"password": user.GetPassword(),
		})
	})

	r.Run()
}
        
        

The original code had a vulnerability because it did not respect the encapsulation principle. The fields of the User struct were public, which means they could be accessed and modified directly from outside the struct. This could lead to unintended side effects and make the code harder to maintain.

The fixed code respects the encapsulation principle by making the fields of the User struct private and providing getter and setter methods to access and modify these fields. This way, the fields can only be accessed and modified through these methods, which gives us more control over what can be done with them.

In the main function, instead of creating a User with public fields, we create an empty User and use the setter methods to set the fields. When we send the user as a JSON response, we use the getter methods to get the fields.

This fix improves the security and maintainability of the code by ensuring that the fields of the User struct can only be accessed and modified in controlled ways.

References