Skip to content

Commit

Permalink
Deny access to methods other than GET for readonly access tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
lfdebrux committed Dec 19, 2023
1 parent 9d40b02 commit 5445024
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 2 deletions.
6 changes: 4 additions & 2 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def authenticate_using_access_tokens
if request.headers["X-Api-Token"].present?
token = request.headers["X-Api-Token"]
@access_token = AccessToken.active.find_by_token_digest(Digest::SHA256.hexdigest(token))
if @access_token.present?
if @access_token.present? && AccessTokenPolicy.new(@access_token, request).request?
@access_token.update!(last_accessed_at: Time.zone.now)
true
else
Expand All @@ -50,7 +50,9 @@ def authenticate_using_access_tokens
else
authenticate_with_http_token do |token|
@access_token = AccessToken.active.find_by_token_digest(Digest::SHA256.hexdigest(token))
@access_token.update!(last_accessed_at: Time.zone.now) if @access_token.present?
return unless @access_token.present? && AccessTokenPolicy.new(@access_token, request).request?

@access_token.update!(last_accessed_at: Time.zone.now)
end
end
end
Expand Down
12 changes: 12 additions & 0 deletions app/policies/access_token_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class AccessTokenPolicy
attr_reader :access_token, :request

def initialize(access_token, request)
@access_token = access_token
@request = request
end

def request?
access_token.all_permissions? || request.get?
end
end
47 changes: 47 additions & 0 deletions spec/integration/access_tokens_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,52 @@

expect(response).to have_http_status(:unauthorized)
end

context "when a user has a readonly token" do
let(:access) do
AccessToken.new(owner: :test, permissions: :readonly)
end

let(:headers) do
{ Authorization: "Token #{token}" }
end

it "allows access to the API for GET requests" do
get(forms_path, headers:)

expect(response).to have_http_status(:ok)
end

it "denies access to the API for POST requests" do
post(forms_path, params: { form: { name: "test form" } }, headers:)

expect(response).to have_http_status(:unauthorized)
expect(Form.last).to be nil
end

it "denies access to the API for PUT requests" do
form = create :form, id: 1, name: "test form"

put(form_path(1), params: { form: { name: "edited test form" } }, headers:)

expect(response).to have_http_status(:unauthorized)
expect(form.name).to eq "test form"
end

it "denies access to the API for PATCH requests" do
form = create :form, id: 1, name: "test form"

patch(form_path(1), params: { form: { name: "edited test form" } }, headers:)

expect(response).to have_http_status(:unauthorized)
expect(form.name).to eq "test form"
end

it "does not allow creating other access tokens" do
post(access_tokens_path, params: { owner: "test" }, headers:)

expect(response).to have_http_status(:unauthorized)
end
end
end
end
53 changes: 53 additions & 0 deletions spec/policies/access_token_policy_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
require "rails_helper"

RSpec.describe AccessTokenPolicy do
subject(:policy) { described_class.new(access, request) }

let(:access) do
build :access_token
end

let(:request) do
ActionDispatch::Request.empty
end

describe "#request?" do
before do
request.headers["REQUEST_METHOD"] = request_method
end

context "when request method is GET" do
let(:request_method) { "GET" }

it "grants access if access token has all permissions" do
access.permissions = :all

expect(policy.request?).to be true
end

it "grants access if access token has readonly permissions" do
access.permissions = :readonly

expect(policy.request?).to be true
end
end

(ActionDispatch::Request::HTTP_METHODS - %w[GET]).each do |request_method_|
context "when request method is #{request_method_}" do
let(:request_method) { request_method_ }

it "grants access if access token has all permissions" do
access.permissions = :all

expect(policy.request?).to be true
end

it "denies access if access token has readonly permissions" do
access.permissions = :readonly

expect(policy.request?).to be false
end
end
end
end
end
19 changes: 19 additions & 0 deletions spec/request/application_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,25 @@
expect(json_body[:status]).to eq("unauthorised")
end
end

context "when token with readonly permissions is used for update method" do
let(:access_token) { AccessToken.new(owner: "test-owner", permissions: :readonly) }

before do
access_token
token
access_token.save!
post forms_path, params: { form: { name: "Test form" } }, headers: req_headers
end

it "returns 401" do
expect(response.status).to eq(401)
end

it "returns an error message" do
expect(json_body[:status]).to eq("unauthorised")
end
end
end
end
end

0 comments on commit 5445024

Please sign in to comment.