Skip to content

Commit

Permalink
Starting to replace basic auth with jwt
Browse files Browse the repository at this point in the history
  • Loading branch information
jcoyne committed May 7, 2019
1 parent 7b36240 commit 46f8ce7
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 8 deletions.
10 changes: 3 additions & 7 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2019-05-02 14:26:35 -0500 using RuboCop version 0.65.0.
# on 2019-05-07 10:45:41 -0700 using RuboCop version 0.65.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand Down Expand Up @@ -115,10 +115,11 @@ RSpec/ContextWording:
- 'spec/services/registration_service_spec.rb'
- 'spec/services/version_service_spec.rb'

# Offense count: 3
# Offense count: 4
RSpec/DescribeClass:
Exclude:
- 'spec/requests/about_spec.rb'
- 'spec/requests/authorization_spec.rb'
- 'spec/requests/metadata_refresh.rb'
- 'spec/requests/metadata_spec.rb'

Expand Down Expand Up @@ -178,11 +179,6 @@ RSpec/MessageSpies:
- 'spec/services/registration_service_spec.rb'
- 'spec/services/version_service_spec.rb'

# Offense count: 2
# Configuration parameters: AggregateFailuresByDefault.
RSpec/MultipleExpectations:
Max: 11

# Offense count: 22
RSpec/NestedGroups:
Max: 5
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ gem 'honeybadger'
gem 'okcomputer'

gem 'faraday'
gem 'jwt'
gem 'rest-client'
# Pin net-http-persistent to avoid a problem with exhausting file handles when running under load
gem 'marc'
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ GEM
iso-639 (0.2.8)
jaro_winkler (1.5.2)
json (2.2.0)
jwt (2.1.0)
link_header (0.0.8)
listen (3.0.8)
rb-fsevent (~> 0.9, >= 0.9.4)
Expand Down Expand Up @@ -459,6 +460,7 @@ DEPENDENCIES
equivalent-xml
faraday
honeybadger
jwt
listen (~> 3.0.5)
lyber-core (>= 2.0.2)
marc
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@

This Ruby application provides a REST API for DOR Services. [View the REST API documentation](https://consul.stanford.edu/display/chimera/REST+mappings+for+dor-services+gem).


## Authentication

To generate an authentication token run `rake generate_token` on the prod server.
This will use the HMAC secret to sign the token.

## Developer Notes

DOR Services App is a Rails app.
Expand Down
34 changes: 33 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,39 @@ class ApplicationController < ActionController::API
http_basic_authenticate_with name: Settings.DOR.SERVICE_USER,
password: Settings.DOR.SERVICE_PASSWORD

protected
before_action :check_auth_token

# Since Basic auth is already using the Authorization header, we'll use something
# non-standard:
TOKEN_HEADER = 'X-Auth'

private

# In the transition period, we are going to check auth tokens, but we won't
# require them. We will continue to use BasicAuth.
# Later we will ensurer that the tokens are present and remove BasicAuth
def check_auth_token
token = decoded_auth_token
Honeybadger.context(invoked_by: token[:sub]) if token
end

def decoded_auth_token
@decoded_auth_token ||= begin
body = JWT.decode(http_auth_header, Settings.dor.hmac_secret, true, algorithm: 'HS256').first
HashWithIndifferentAccess.new body
rescue StandardError
nil
end
end

def http_auth_header
if request.headers[TOKEN_HEADER].blank?
Honeybadger.notify("no #{TOKEN_HEADER} token was provided by #{request.remote_ip}")
return
end

request.headers[TOKEN_HEADER].split(' ').last
end

def proxy_rest_client_response(response)
render status: response.code, content_type: response.headers[:content_type], body: response.body
Expand Down
2 changes: 2 additions & 0 deletions config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ CLEANUP:
DOR:
SERVICE_USER: 'user'
SERVICE_PASSWORD: 'password'
dor:
hmac_secret: 'my$ecretK3y'

RELEASE:
SYMPHONY_PATH: './'
Expand Down
9 changes: 9 additions & 0 deletions lib/tasks/jwt.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

desc 'Generate a JWT token for authentication'
task generate_token: :environment do
print 'Account name: '
name = $stdin.gets.chomp
payload = { sub: name }
puts "Your token:\n#{JWT.encode(payload, Settings.dor.hmac_secret, 'HS256')}"
end
38 changes: 38 additions & 0 deletions spec/requests/authorization_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Authorization' do
let(:user) { Settings.DOR.SERVICE_USER }
let(:password) { Settings.DOR.SERVICE_PASSWORD }
let(:basic_auth) { ActionController::HttpAuthentication::Basic.encode_credentials(user, password) }
let(:object) { instance_double(Dor::Item, current_version: '5') }

before do
allow(Dor).to receive(:find).and_return(object)
allow(Honeybadger).to receive(:notify)
allow(Honeybadger).to receive(:context)
end

context 'without a bearer token' do
it 'Logs tokens to honeybadger' do
get '/v1/objects/druid:mk420bs7601/versions/current',
headers: { 'Authorization' => basic_auth }
expect(response.body).to eq '5'
expect(Honeybadger).to have_received(:notify).with('no X-Auth token was provided by 127.0.0.1')
end
end

context 'with a bearer token' do
let(:payload) { { sub: 'argo' } }
let(:jwt) { JWT.encode(payload, Settings.dor.hmac_secret, 'HS256') }

it 'Logs tokens to honeybadger' do
get '/v1/objects/druid:mk420bs7601/versions/current',
headers: { 'Authorization' => basic_auth, 'X-Auth' => "Bearer #{jwt}" }
expect(response.body).to eq '5'
expect(Honeybadger).not_to have_received(:notify)
expect(Honeybadger).to have_received(:context).with(invoked_by: 'argo')
end
end
end

0 comments on commit 46f8ce7

Please sign in to comment.