Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support client token in params #155

Merged
merged 3 commits into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion app/controllers/api_controller.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
class ApiController < ActionController::Base
CLIENT_TOKEN_HEADER = "X-MLI-CLIENT-TOKEN"
CLIENT_TOKEN_PARAM = :mli_client_token

protect_from_forgery with: :null_session

before_action :validate_client_token

private

def client_token_valid?
client_token = request.headers["X-CLIENT-TOKEN"]
client_token = request.headers[CLIENT_TOKEN_HEADER] || params[CLIENT_TOKEN_PARAM]
Monolithium.config.client_token == client_token
end

Expand Down
48 changes: 33 additions & 15 deletions app/models/hook_request.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
class HookRequest
SECRET_HEADER_KEYS = [
ApiController::CLIENT_TOKEN_HEADER
]

SECRET_HEADER_PARAMS = [
ApiController::CLIENT_TOKEN_PARAM
]

def self.to_attrs(request, params)
new(request, params).to_attrs
end
Expand All @@ -12,31 +20,41 @@ def initialize(request, params)

def to_attrs
{
body: request_body,
headers: loud_headers,
params: unsafe_params
body: computed_body,
headers: computed_headers,
params: computed_params
}
end

private

def request_body
@request_body ||= request.body.read
def computed_body
@request_body ||= request.body&.read
end

def headers_hash
@headers_hash ||= request.headers.to_h
end
def computed_headers
headers_hash = request.headers.to_h

def loud_header_keys
headers_hash.keys.select { |key| key == key.upcase && !key.starts_with?("ROUTES") }
end
header_keys = headers_hash.keys.select do |key|
key == key.upcase && !key.starts_with?("ROUTES")
end

def loud_headers
headers_hash.slice(*loud_header_keys)
sliced_headers = headers_hash.slice(*header_keys)

SECRET_HEADER_KEYS.each do |secret_key|
sliced_headers[secret_key] = "REDACTED" if sliced_headers.key? secret_key
end

sliced_headers
end

def unsafe_params
params.to_unsafe_hash
def computed_params
unsafe_params = params.to_unsafe_hash

SECRET_HEADER_PARAMS.each do |secret_param|
unsafe_params[secret_param] = "REDACTED" if unsafe_params.key? secret_param
end

unsafe_params
end
end
77 changes: 77 additions & 0 deletions spec/models/hook_request_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
require "rails_helper"

describe HookRequest do
describe ".to_attrs" do
let(:env) { {} }
let(:request) { ActionDispatch::Request.new(env) }

let(:parameters) { {} }
let(:params) { ActionController::Parameters.new(parameters) }

before do
params.permit(:safe)
end

context "with a typical request" do
let(:env) do
rack_input = StringIO.new("typical request body")
{"rack.input" => rack_input, "UPPER" => "typical request header"}
end

let(:parameters) { {safe: "typical request param"} }

it "returns the body, headers and params from the request" do
attrs = HookRequest.to_attrs(request, params)

expect(attrs[:body]).to eq "typical request body"
expect(attrs[:headers]["UPPER"]).to eq "typical request header"
expect(attrs[:params][:safe]).to eq "typical request param"
end
end

context "with a lowercase header" do
let(:env) { {"lower" => "ignore me!"} }

it "ignores that header" do
attrs = HookRequest.to_attrs(request, params)
expect(attrs[:headers].keys).to_not include "lower"
end
end

context "with a header that starts with 'ROUTES'" do
let(:env) { {"ROUTES_KEY" => "ignore me!"} }

it "ignores that header" do
attrs = HookRequest.to_attrs(request, params)
expect(attrs[:headers].keys).to_not include "ROUTES_KEY"
end
end

context "with a client token header" do
let(:env) { {ApiController::CLIENT_TOKEN_HEADER => "shhh"} }

it "redacts that header value" do
attrs = HookRequest.to_attrs(request, params)
expect(attrs[:headers][ApiController::CLIENT_TOKEN_HEADER]).to eq "REDACTED"
end
end

context "with a param that has not been permitted" do
let(:parameters) { {unsafe: "don't ignore me!"} }

it "does not igore that param" do
attrs = HookRequest.to_attrs(request, params)
expect(attrs[:params][:unsafe]).to eq "don't ignore me!"
end
end

context "with a client token param" do
let(:parameters) { {ApiController::CLIENT_TOKEN_PARAM => "shhh"} }

it "redacts that header value" do
attrs = HookRequest.to_attrs(request, params)
expect(attrs[:params][ApiController::CLIENT_TOKEN_PARAM]).to eq "REDACTED"
end
end
end
end
34 changes: 30 additions & 4 deletions spec/requests/api/v1/post_bin_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,46 @@
end
end

context "with an invalid client token" do
context "with an invalid client token header" do
it "returns an empty 404" do
headers = {"X-Client-Token" => "invalid"}
headers = {ApiController::CLIENT_TOKEN_HEADER => "invalid"}
post "/api/v1/post_bin", headers: headers
expect(response.status).to eq 404
end
end

context "with a valid client token" do
context "with a valid client token header" do
it "returns an empty 201 and creates a PostBin record" do
headers = {"X-Client-Token" => Monolithium.config.client_token}
headers = {ApiController::CLIENT_TOKEN_HEADER => Monolithium.config.client_token}
post "/api/v1/post_bin", headers: headers
expect(response.status).to eq 201
expect(PostBinRequest.count).to eq 1
end
end

context "with an invalid client token param" do
it "returns an empty 404" do
params = {ApiController::CLIENT_TOKEN_PARAM => "invalid"}
post "/api/v1/post_bin", params: params
expect(response.status).to eq 404
end
end

context "with a valid client token param" do
it "returns an empty 201 and creates a PostBin record" do
params = {ApiController::CLIENT_TOKEN_PARAM => Monolithium.config.client_token}
post "/api/v1/post_bin", params: params
expect(response.status).to eq 201
expect(PostBinRequest.count).to eq 1
end
end

context "with an invalid client token header and a valid client token param" do
it "returns an empty 404" do
params = {ApiController::CLIENT_TOKEN_PARAM => Monolithium.config.client_token}
headers = {ApiController::CLIENT_TOKEN_HEADER => "invalid"}
post "/api/v1/post_bin", params: params, headers: headers
expect(response.status).to eq 404
end
end
end