axelhodler

Immutability

Take the following example in Java

private List<String> allowedUsers = asList("Alice", "Bob", "Charlie");

public List<String> getAllowedUsers() {
    return allowedUsers;
}

public boolean canUserAccessResource(String username) {
    return this.getAllowedUsers().contains(username);
}

A more verbose version can be found in the excellent course on Programming Languages on Coursera

What’s the issue with the code above? Try to think about if for a few seconds.

Our allowedUsers can be modified to allow access to users which were not specified as allowed by the original author.

Tests display the the issue at hand

@Test
public void mallory_cant_directly_access_the_resource() {
    assertThat(subject.canUserAccessResource("Mallory")).isFalse();
}

@Test
public void mallory_can_be_added_to_the_allowed_users() {
    subject.getAllowedUsers().set(0, "Mallory");

    assertThat(subject.canUserAccessResource("Mallory")).isTrue();
}

Ok. Nevermind. Instead of learning how we can alleviate the above why don’t we use more modern languages which might bring us some immutability. Like Kotlin.

The Java implementation looks as follows in Kotlin

val allowedUsers = listOf("Alice", "Bob", "Charlie")

fun canUserAccessResource(username: String): Boolean {
    return this.allowedUsers.contains(username)
}

The docs on listOf state

Returns a new read-only list of given elements.

So, is the security issue fixed?

We verify it with kotlinc

>>> val authorizedUsers = listOf("Alice", "Bob", "Charlie")
>>> authorizedUsers
[Alice, Bob, Charlie]
val mp = list as MutableList<Int>
mp[2] = 5 // changes both list and mp

Attempts along the lines of

authorizedUsers[2] = "Mallory"

or

authorizedUsers = listOf("Mallory")

will fail.

There is neither a set method to provide array access or the possibility to reassign val

Nevertheless, we can cast it to a MutableList and do the following

>>> val unauthorizedUsers = authorizedUsers as MutableList<String>
>>> unauthorizedUsers[1] = "Mallory"
>>> unauthorizedUsers
[Alice, Mallory, Charlie]
>>> authorizedUsers
[Alice, Mallory, Charlie]

Oh snap. The actual object can be changed by another reference.

In defense of Kotlin. It’s stressed quite often how the wording read-only is used instead of immutability.

Although the wording MutableList might suggest using List would be immutable.

On with the journey.

Let’s crack open a Haskell REPL (stack)

λ> let authorizedUsers = ["Alice", "Bob", "Charlie"]
λ> authorizedUsers
["Alice","Bob","Charlie"]
λ> authorizedUsers ++ ["Mallory"]
["Alice","Bob","Charlie","Mallory"]
λ> authorizedUsers
["Alice","Bob","Charlie"]

Data is immutable in Haskell. We do not mutate the existing list but build a new list from applying the function. Haskell lends us a hand at avoiding the issue.

Why does all the above matter? It’s about predictability. Would a senior programmer recognize the bug in the Java example? Quite likely. Would a junior or a senior with sleep deprivation on a deadline? Would you want to keep in mind how every other part of the codebase is able to modify the allowed users? I guess the answer is no.

Of course we can fix the issue with Java by simply copying the list in the getter

public List<String> getAllowedUsers() {
    return new ArrayList<>(allowedUsers);
}

Although that is something we would have to remember all the time.

Would someone starting out with Kotlin realize the difference between read-only and immutability before stumbling upon it in the docs or when encountering a bug?

Seems how the simplest approach to getting the issues above out of the way is by using immutability by default.

Code examples can be found on GitHub