Skip to content
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: 2 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
source 'https://rubygems.org'

gem 'rails'
source "https://rubygems.org"

group :development, :test do
gem "rails", "~> 6"
gem "rspec-rails", "~> 3"
gem "pry", "~> 0"
end
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,12 @@ two environment variables to use this protocol:
format. This environment variable will be the contents of a `.pub` file,
newlines and all.

`CHATOPS_AUTH_BASE_URL` is the base URL of your server as the chatops client
`CHATOPS_AUTH_BASE_URL` is the base URLs of your servers as the chatops client
sees it. This is specified as an environment variable since rails will trust
client headers about a forwarded hostname. For example, if your chatops client
has added the url `https://example.com/_chatops`, you'd set this to
`https://example.com`.
`https://example.com`. You can specify more than one base url divided by comma,
e.g. `https://example.com,https://example2.com`

You can also optionally set `CHATOPS_AUTH_ALT_PUBLIC_KEY` to a second public key
which will be accepted. This is helpful when rolling keys.
Expand Down
1 change: 1 addition & 0 deletions chatops-controller.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Gem::Specification.new do |s|
s.files = Dir["{app,config,db,lib}/**/*", "README.md"]
s.test_files = Dir["spec/**/*"]

s.add_dependency "rails"
s.add_dependency "actionpack", ">= 6.0"
s.add_dependency "activesupport", ">= 6.0"

Expand Down
4 changes: 2 additions & 2 deletions lib/chatops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def self.alt_public_key
ENV["CHATOPS_AUTH_ALT_PUBLIC_KEY"]
end

def self.auth_base_url
ENV[auth_base_url_env_var_name]
def self.auth_base_urls
ENV.fetch(auth_base_url_env_var_name, "").split(",").map(&:strip)
end

def self.auth_base_url_env_var_name
Expand Down
25 changes: 14 additions & 11 deletions lib/chatops/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,26 +118,29 @@ def ensure_user_given
end

def ensure_chatops_authenticated
body = request.raw_post || ""
signature_string = [@chatops_url, @chatops_nonce, @chatops_timestamp, body].join("\n")
# We return this just to aid client debugging.
response.headers["Chatops-Signature-String"] = Base64.strict_encode64(signature_string)
raise ConfigurationError.new("You need to add a client's public key in .pem format via #{Chatops.public_key_env_var_name}") unless Chatops.public_key.present?
if signature_valid?(Chatops.public_key, @chatops_signature, signature_string) ||
signature_valid?(Chatops.alt_public_key, @chatops_signature, signature_string)

body = request.raw_post || ""

@chatops_urls.each do |url|
signature_string = [url, @chatops_nonce, @chatops_timestamp, body].join("\n")
# We return this just to aid client debugging.
response.headers["Chatops-Signature-String"] = Base64.strict_encode64(signature_string)
if signature_valid?(Chatops.public_key, @chatops_signature, signature_string) ||
signature_valid?(Chatops.alt_public_key, @chatops_signature, signature_string)
return true
end
end

return jsonrpc_error(-32800, 403, "Not authorized")
end

def ensure_valid_chatops_url
unless Chatops.auth_base_url.present?
unless Chatops.auth_base_urls.present?
raise ConfigurationError.new("You need to set the server's base URL to authenticate chatops RPC via #{Chatops.auth_base_url_env_var_name}")
end
if Chatops.auth_base_url[-1] == "/"
raise ConfigurationError.new("Don't include a trailing slash in #{Chatops.auth_base_url_env_var_name}; the rails path will be appended and it must match exactly.")
end
@chatops_url = Chatops.auth_base_url + request.path

@chatops_urls = Chatops.auth_base_urls.map { |url| url.chomp("/") + request.path }
end

def ensure_valid_chatops_nonce
Expand Down
6 changes: 3 additions & 3 deletions spec/lib/chatops/controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def ensure_app_given

@private_key = OpenSSL::PKey::RSA.new(2048)
ENV["CHATOPS_AUTH_PUBLIC_KEY"] = @private_key.public_key.to_pem
ENV["CHATOPS_AUTH_BASE_URL"] = "http://test.host"
ENV["CHATOPS_AUTH_BASE_URL"] = "http://old.host,http://test.host/"
end

def rails_flexible_post(path, outer_params, jsonrpc_params = nil)
Expand Down Expand Up @@ -315,15 +315,15 @@ def rails_flexible_post(path, outer_params, jsonrpc_params = nil)
:room_id => "#someroom",
:unknown_key => "few" # This should get ignored
}, {
"app" => "foo"
"app" => "foo"
}
expect(json_response).to eq({
"jsonrpc" => "2.0",
"id" => nil,
"result" => "{\"params\":{\"action\":\"proxy_parameters\",\"chatop\":\"proxy_parameters\",\"controller\":\"anonymous\",\"mention_slug\":\"mention_slug_here\",\"message_id\":\"message_id_here\",\"room_id\":\"#someroom\",\"user\":\"foo\"},\"jsonrpc_params\":{\"app\":\"foo\"}}"
})
expect(response.status).to eq 200
end
end


it "uses typical controller fun like before_action" do
Expand Down