Access Control in Ruby
Access control in Ruby feels, to me, a little different to access control in other languages. In this post I'm going to talk about it, demonstrate ways around it and ultimately give my opinion on why I think access control in general is a little bit pointless.
Note: This is going to be fairly light-hearted and not-practical. If you truly need something to be private in your code there are best practices involving databases and encryption. This post just aims to demonstrate the possibilities in Ruby and have some fun with them.
Prerequisite Knowledge: Knowing what access control is will help, but I'll
provide an explanation at the beginning of the post anyway. Basic Ruby is
required. Some of the metaprogramming stuff might be confusing but bear with it.
If you don't know what instance_eval
does, I recommend looking it up before
reading this post.
# What is access control?
Access control is the jargon used to describe whether a member variable in an object is public, private, protected or any other variation defined by the language in question. A brief overview of the generally accepted definitions of the popular three:
- Public: Accessible to all, inside and outside of the object.
- Private: Accessible only inside the object. Cannot be accessed by anything outside of methods in the object, not even inheriting objects.
- Protected: Accessible inside the object and inside any inheriting objects.
However, Ruby and its dynamic nature treats access control a little differently. I don't want to restate anyone, and there's a fantastic blog post that has already covered this beautifully so I'll just link to it: Alan Skorkin on Access Control in Ruby. Please read it before continuing.
# Circumventing Access Control
One of the arguments I hear, and was taught at University, is that access control stops people screwing with and invalidating the internal state of your object. So by declaring all of your attributes as private and defining getters and setters to them, you are controlling the data that goes into your object.
# Instance variables
Take the following example:
class Foo
def initialize
@secret = 42
end
end
It's naive to believe that this is private in any way. It shows up when you inspect the object:
f = Foo.new
#=> #<Foo:0x000000018ea7f0 @secret=42>
You can also straight up access it through instance_variable_get
:
f = Foo.new
f.instance_variable_get :@secret
#=> 42
So instance variables are most certainly not private and, as far as I can tell, there's no way to make them so.
# Private methods
So let's up our game. Let's try something a bit better and define ourselves an explicitly private method:
class Foo
private
def secret
42
end
end
This time we won't see anything revealing when we initialize an object of Foo:
f = Foo.new
#=> #<Foo:0x0000000152c3e8>
And we definitely cannot call the method:
f = Foo.new
f.secret
#=> NoMethodError: private method `secret' called for #<Foo:0x0000000152c3e8>
# from (irb):8
# from /home/sam/.rvm/rubies/ruby-1.9.2-p320/bin/irb:16:in `<main>'
So on the surface it seems pretty safe. Unfortunately, using some popular Ruby metaprogramming techniques it's very easy to access this private variable from anywhere in a program. Two examples:
#
Accessing private methods with send
f = Foo.new
f.send :secret
#=> 42
The send
method is available to every object in Ruby and simply acts as if
it's calling a method with the same name as the symbol passed in. It doesn't
stop for anybody, not even the private
declaration.
#
Accessing private methods with instance_eval
f = Foo.new
f.instance_eval { secret }
#=> 42
instance_eval
allows you to execute code as if it was written inside the
instance of the object it was called on. It, too, is available on all objects
and allows us to freely access the secret
method without so much as a whimper.
#
Defending against send
and instance_eval
Okay. So how about we try and stop people from using these techniques to get at our private data? Look at this class:
class Foo
def send *args
nil
end
def instance_eval *args, &block
nil
end
private
def secret
42
end
end
Here we're overriding the two methods we saw before that allowed people to get at our private method in the hope that will totally block access.
Still, this won't protect our data:
Foo.class_eval { remove_method :send }
f = Foo.new
f.send :secret
The remove_method
method is available in all Ruby classes and will just
discard of a method definition so that people can once again get at our data.
What about if we override class_eval
as well? class_eval
is what was letting
people get into the Foo
class and remove the send
method override. Here's
the new class with even better security:
class Foo
def send *args
nil
end
def instance_eval *args, &block
nil
end
def self.class_eval *args, &block
nil
end
private
def secret
42
end
end
Again, this just won't work. There's nothing stopping people reopening the class and just removing the method that way:
class Foo
remove_method :send
end
f = Foo.new
f.send :secret
#=> 42
And I honestly can't see a way around that one, which ends this particular branch of madness.
# Another technique worth mentioning...
If you do somehow figure out how to get around all of the above things that have been mentioned, let's not forget the following possibility:
class Foo
puts method(self.methods.first).source_location
end
It won't tell you exactly where the secret value is and it won't work if there are no methods defined in the class, but if the conditions are right then you'll get output that looks like this:
["foo.rb", 10]
Which is the relative file name and the line of the first method in that file. Ruby is an interpreted language. By necessity, the user will have your source code. It's pointless trying to make anything private any way.
# So what's the point of access control?
The idea of access control in Ruby is to serve as a guide. If a developer marks something as private or protected, they are trying to make it clear to you that you really need to know what you're doing if you want to play around with it. Apart from that, there is very little point of marking things as private or protected.
Even in strongly typed languages such as Java that enforce access control pretty strongly (though it is still circumventable) I think it's pointless, if not harmful. Occasionally the developer is held back by the fact something is marked as private (a useful helper method for example) a situation I have found myself in more than once using third party libraries in the past. Sure, it makes the auto-complete list look cleaner when there are no private methods in it but sometimes it's incredibly helpful to have access to them. I know the implications, I'm not stupid.
You'll rarely find anything marked as private in my code. If I ever wanted to mark something as private, I would do it in a conventional way, using leading underscores, rather than trying to hide things on the top shelf so to speak.
# Conclusion
I hoped you enjoyed this trip down the rabbit hole. If you can think of any other creative ways of getting access to code people don't want you to access please let me know. Stretching languages is always fun and it's a practice well worth engaging in :)
# Disclaimer
Before getting angry and telling me it's stupid to try and hide sensitive
information in private variables, I assure you that I am well aware. The idea of
this post is to demonstrate that it's very easy to get around the private
declaration and hopefully demonstrate some cool metaprogramming techniques to
people who may not be familiar. It's not intended to be a tutorial on how make
"truly private" methods in Ruby, I doubt such a thing is even possible.