diff --git a/README.md b/README.md index 4f7513d..63d6664 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,37 @@ You may optionally set a private RSA key to encrypt the `public_id` to uniquely CRONUT_PRIVATE_KEY: -----BEGIN RSA PRIVATE KEY----- -----END RSA PRIVATE KEY----- +Sending an encrypted payload from ruby is fairly straightforward, you can simply +encrypt the payload with : +`OpenSSL::PKey::RSA.new(public_key_string_coming_from_the_env_a_file_or_anywhere_else).public_encrypt(str)` +and attach this string to a POST request. + +Here is an example with faraday: + +```ruby +host = "https://your.cronut.host.com" +public_key = ENV["CRONUT_PUBLIC_KEY"] # Or from a file, the string should look like "-----BEGIN PUBLIC KEY-----\n\n-----END PUBLIC KEY-----" +conn = Faraday.new(host) do |c| + c.request :url_encoded + c.use Faraday::Adapter::NetHttp + c.headers = { + "X-CRONUT-API-TOKEN" => "" + } +end + +conn.post "/ping/", {:public_id => OpenSSL::PKey::RSA.new(public_key).public_encrypt(str)} +``` + +Another (simpler) approach is to use the [ping-me-maybe gem](https://github.com/harrystech/ping-me-maybe) + +#### Base64 option + +Ruby uses a non standard string representation of the underlying encrypted bytes +and this can be quite tricky to reproduce when trying to ping cronut from +another environment, such as the JVM. +In this can you can base 64 encode the encrypted bytes and use the `/v2/ping` +endpoint instead. It expects the payload to be base 64 encoded by default + Usage ----- On Cronut dashboard, you can schedule two types of jobs: interval jobs and cron jobs. diff --git a/app/controllers/jobs_controller.rb b/app/controllers/jobs_controller.rb index b645c75..7424ee4 100644 --- a/app/controllers/jobs_controller.rb +++ b/app/controllers/jobs_controller.rb @@ -105,6 +105,9 @@ def ping begin str = params[:public_id] if Encryptor.enabled? + if use_base64? + str = Base64.decode64(str) + end str = Encryptor.decrypt(str) end array = str.split("-") @@ -113,7 +116,8 @@ def ping raise "Timestamp does not match" end @job = Job.find_by_public_id!(array[1]) - rescue Exception => e + rescue StandardError => e + puts e.message raise ActiveRecord::RecordNotFound.new('Not Found') end @@ -132,6 +136,10 @@ def verify_api_token private + def use_base64? + params[:use_base64].to_s == "true" + end + def job_params params.require(:job).permit(:name, :notifications, {notification_ids: []}, :frequency, :cron_expression, :buffer_time) end diff --git a/config/routes.rb b/config/routes.rb index 8a7496d..f1bb73c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,7 +2,8 @@ resources :notifications resources :jobs - post 'ping/' => "jobs#ping" + post 'ping' => "jobs#ping" + post 'v2/ping' => "jobs#ping", defaults: {use_base64: true} root to: 'jobs#index' # The priority is based upon order of creation: diff --git a/spec/controllers/jobs_controller_spec.rb b/spec/controllers/jobs_controller_spec.rb index aee032a..e5fd132 100644 --- a/spec/controllers/jobs_controller_spec.rb +++ b/spec/controllers/jobs_controller_spec.rb @@ -235,7 +235,7 @@ end end - describe "GET ping" do + describe "POST ping" do before(:each) do invalid_basic_auth_login @job = IntervalJob.create!({:name => "Test IntervalJob", :frequency => 600}) @@ -299,6 +299,14 @@ response.status.should eq 200 @job.last_successful_time.should_not be_nil end + + it "pings with valid token and a base64 encoded payload" do + request.headers[JobsController::API_TOKEN_HEADER] = @token.token + post :ping, {:public_id => Base64.strict_encode64(Encryptor.encrypt(@str)), use_base64: "true"}, valid_session + @job.reload + response.status.should eq 200 + @job.last_successful_time.should_not be_nil + end end end diff --git a/spec/requests/jobs_spec.rb b/spec/requests/jobs_spec.rb index 4d81701..dc7e41c 100644 --- a/spec/requests/jobs_spec.rb +++ b/spec/requests/jobs_spec.rb @@ -40,7 +40,8 @@ ActiveRecord::Base.connection.reset_pk_sequence!('notifications') end - describe "POST ping" do + context "ping" do + before(:each) do #invalid_basic_auth_login @job = IntervalJob.create!({:name => "Test IntervalJob", :frequency => 600}) @@ -57,42 +58,61 @@ @token.destroy end - it "ignores pings with unencrypted public id" do - headers[JobsController::API_TOKEN_HEADER] = @token.token - expect { - post "/ping", {:public_id => @str}, headers - }.to raise_error(ActiveRecord::RecordNotFound) - @job.reload - @job.last_successful_time.should be_nil - end + describe "POST ping" do + it "ignores pings with unencrypted public id" do + headers[JobsController::API_TOKEN_HEADER] = @token.token + expect { + post "/ping", {:public_id => @str}, headers + }.to raise_error(ActiveRecord::RecordNotFound) + @job.reload + @job.last_successful_time.should be_nil + end - it "ignores pings with encrypted wrong id" do - wrong_str = "#{Time.now.to_i.to_s}-abc" - headers[JobsController::API_TOKEN_HEADER] = @token.token - expect { - post "/ping", {:public_id => Encryptor.encrypt(wrong_str)}, headers - }.to raise_error(ActiveRecord::RecordNotFound) - @job.reload - @job.last_successful_time.should be_nil - end + it "ignores pings with encrypted wrong id" do + wrong_str = "#{Time.now.to_i.to_s}-abc" + headers[JobsController::API_TOKEN_HEADER] = @token.token + expect { + post "/ping", {:public_id => Encryptor.encrypt(wrong_str)}, headers + }.to raise_error(ActiveRecord::RecordNotFound) + @job.reload + @job.last_successful_time.should be_nil + end - it "ignores pings with encrypted wrong date" do - wrong_str = "#{(Time.now - 31.seconds).to_i.to_s}-#{@job.public_id}" - headers[JobsController::API_TOKEN_HEADER] = @token.token - expect { - post "/ping", {:public_id => Encryptor.encrypt(wrong_str)}, headers - }.to raise_error(ActiveRecord::RecordNotFound) - @job.reload - @job.last_successful_time.should be_nil + it "ignores pings with encrypted wrong date" do + wrong_str = "#{(Time.now - 31.seconds).to_i.to_s}-#{@job.public_id}" + headers[JobsController::API_TOKEN_HEADER] = @token.token + expect { + post "/ping", {:public_id => Encryptor.encrypt(wrong_str)}, headers + }.to raise_error(ActiveRecord::RecordNotFound) + @job.reload + @job.last_successful_time.should be_nil + end + + it "pings with valid token" do + headers[JobsController::API_TOKEN_HEADER] = @token.token + post "/ping", {:public_id => Encryptor.encrypt(@str)}, headers + @job.reload + response.status.should eq 200 + @job.last_successful_time.should_not be_nil + end + + it "pings with valid token and a base 64 payload" do + headers[JobsController::API_TOKEN_HEADER] = @token.token + post "/ping", {:public_id => Base64.strict_encode64(Encryptor.encrypt(@str)), use_base64: true}, headers + @job.reload + response.status.should eq 200 + @job.last_successful_time.should_not be_nil + end end - it "pings with valid token" do - headers[JobsController::API_TOKEN_HEADER] = @token.token - post "/ping", {:public_id => Encryptor.encrypt(@str)}, headers - @job.reload - response.status.should eq 200 - @job.last_successful_time.should_not be_nil + describe "POST v2/ping" do + it "pings with valid token and a base64 encoded payload" do + headers[JobsController::API_TOKEN_HEADER] = @token.token + post '/v2/ping', {:public_id => Base64.strict_encode64(Encryptor.encrypt(@str))}, headers + @job.reload + response.status.should eq 200 + @job.last_successful_time.should_not be_nil + end end end - end