api keys: add IP whitelist and API permission system.

Add the ability to restrict API keys so that they can only be used with
certain IP addresses or certain API endpoints.

Restricting your key is useful to limit damage in case it gets leaked or
stolen. For example, if your key is on a remote server and it gets
hacked, or if you accidentally check-in your key to Github.

Restricting your key's API permissions is useful if a third-party app or
script wants your key, but you don't want to give full access to your
account.

If you're an app or userscript developer, and your app needs an API key
from the user, you should only request a key with the minimum
permissions needed by your app.

If you have a privileged account, and you have scripts running under
your account, you are highly encouraged to restrict your key to limit
damage in case your key gets leaked or stolen.
This commit is contained in:
evazion
2021-02-14 18:05:32 -06:00
parent a6707fbfa2
commit 25fda1ecc2
19 changed files with 286 additions and 32 deletions

View File

@@ -108,6 +108,53 @@ class ApplicationControllerTest < ActionDispatch::IntegrationTest
end
end
context "for an API key with restrictions" do
should "restrict requests to the permitted IP addresses" do
@api_key = create(:api_key, permitted_ip_addresses: ["192.168.0.1", "10.0.0.1/24", "2600::1/64"])
ActionDispatch::Request.any_instance.stubs(:remote_ip).returns("192.168.0.1")
get posts_path, params: { login: @api_key.user.name, api_key: @api_key.key }
assert_response :success
ActionDispatch::Request.any_instance.stubs(:remote_ip).returns("10.0.0.42")
get posts_path, params: { login: @api_key.user.name, api_key: @api_key.key }
assert_response :success
ActionDispatch::Request.any_instance.stubs(:remote_ip).returns("2600::1234:0:0:1")
get posts_path, params: { login: @api_key.user.name, api_key: @api_key.key }
assert_response :success
ActionDispatch::Request.any_instance.stubs(:remote_ip).returns("127.0.0.2")
get posts_path, params: { login: @api_key.user.name, api_key: @api_key.key }
assert_response 403
ActionDispatch::Request.any_instance.stubs(:remote_ip).returns("10.0.1.0")
get posts_path, params: { login: @api_key.user.name, api_key: @api_key.key }
assert_response 403
ActionDispatch::Request.any_instance.stubs(:remote_ip).returns("2600:dead:beef::1")
get posts_path, params: { login: @api_key.user.name, api_key: @api_key.key }
assert_response 403
end
should "restrict requests to the permitted endpoints" do
@post = create(:post)
@api_key = create(:api_key, permissions: ["posts:index", "posts:show"])
get posts_path, params: { login: @api_key.user.name, api_key: @api_key.key }
assert_response :success
get post_path(@post), params: { login: @api_key.user.name, api_key: @api_key.key }
assert_response :success
get tags_path, params: { login: @api_key.user.name, api_key: @api_key.key }
assert_response 403
put post_path(@post), params: { login: @api_key.user.name, api_key: @api_key.key, post: { rating: "s" }}
assert_response 403
end
end
context "with cookie-based authentication" do
should "not allow non-GET requests without a CSRF token" do
# get the csrf token from the login page so we can login