Skip to content

Commit

Permalink
Implement IIIF Authentication 1.0 browse-based client application aut…
Browse files Browse the repository at this point in the history
…hentication
  • Loading branch information
cbeer committed Jun 6, 2017
1 parent 5a2ed20 commit 98794a7
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 39 deletions.
4 changes: 2 additions & 2 deletions app/controllers/iiif_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,12 @@ def image_info
unless anonymous_ability.can? :download, current_image
info['service'] = {
'@id' => iiif_auth_api_url,
'profile' => 'http://iiif.io/api/auth/0/login',
'profile' => 'http://iiif.io/api/auth/1/login',
'label' => 'Stanford-affiliated? Login to view',
'service' => [
{
'@id' => iiif_token_api_url,
'profile' => 'http://iiif.io/api/auth/0/token'
'profile' => 'http://iiif.io/api/auth/1/token'
}
]
}
Expand Down
63 changes: 48 additions & 15 deletions app/controllers/iiif_token_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,71 @@ def create

write_bearer_token_cookie(token) if token

@message = if token
{
accessToken: token,
tokenType: 'Bearer',
expiresIn: 3600
}
else
{ error: 'missingCredentials', description: '' }
end

if browser_based_client_auth?
create_for_browser_based_client_application_auth
else
create_for_json_access_token_auth(token)
end
end

private

# Handle IIIF Authentication 1.0 browser-based client application requests
# See {http://iiif.io/api/auth/1.0/#interaction-for-browser-based-client-applications}
def create_for_browser_based_client_application_auth
browser_params.require(:origin)

# The browser-based interaction requires using iframes
response.headers['X-Frame-Options'] = "ALLOW-FROM #{browser_params[:origin]}"

@message[:messageId] = browser_params[:messageId]

@origin = browser_params[:origin]

render 'create', layout: false
end

# Handle IIIF Authentication 1.0 JSON Access Token requests
# See {http://iiif.io/api/auth/1.0/#the-json-access-token-response}
def create_for_json_access_token_auth(token)
respond_to do |format|
format.html { redirect_to callback: callback_value, format: 'js' }
format.js do
response = if token
{
accessToken: token,
tokenType: 'Bearer',
expiresIn: 3600
}
else
{ error: 'missingCredentials', description: '' }
end

status = if callback_value || token
:ok
else
:unauthorized
end

render json: response.to_json, callback: callback_value, status: status
render json: @message.to_json, callback: callback_value, status: status
end
end
end

private

def allowed_params
def json_params
params.permit(:callback)
end

def browser_params
params.permit(:messageId, :origin)
end

def browser_based_client_auth?
browser_params[:messageId].present?
end

def callback_value
allowed_params[:callback]
json_params[:callback]
end

def mint_bearer_token
Expand Down
10 changes: 10 additions & 0 deletions app/views/iiif_token/create.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<body>
<script>
window.parent.postMessage(
<%= @message.to_json.html_safe %>,
<%= @origin.to_json.html_safe %>
);
</script>
</body>
</html>
15 changes: 15 additions & 0 deletions spec/controllers/iiif_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,19 +117,34 @@ def maybe_downloadable?
context 'when the image is downloadable' do
before do
allow(controller).to receive(:can?).with(:download, stub_metadata_object).and_return(true)
allow(controller.send(:anonymous_ability)).to receive(:can?)
.with(:download, stub_metadata_object).and_return(true)
end

it 'the tile height/width is 1024' do
expect(image_info[:tile_height]).to eq 1024
expect(image_info[:tile_width]).to eq 1024
end

it 'omits the authentication service' do
expect(image_info['service']).not_to be_present
end
end

context 'when the image is not downloadable' do
it 'the tile height/width is 256' do
expect(image_info[:tile_height]).to eq 256
expect(image_info[:tile_width]).to eq 256
end

it 'advertises an authentication service' do
expect(image_info['service']).to be_present
expect(image_info['service']['profile']).to eq 'http://iiif.io/api/auth/1/login'
expect(image_info['service']['@id']).to eq iiif_auth_api_url

expect(image_info['service']['service'].first['profile']).to eq 'http://iiif.io/api/auth/1/token'
expect(image_info['service']['service'].first['@id']).to eq iiif_token_api_url
end
end
end
end
Expand Down
86 changes: 64 additions & 22 deletions spec/controllers/iiif_token_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -1,46 +1,88 @@
require 'rails_helper'

describe IiifTokenController do
describe '#create' do
subject do
get :create, format: :js
end
render_views

describe '#create' do
let(:user) { User.new(anonymous_locatable_user: true) }

before do
allow(controller).to receive(:current_user).and_return(user)
end

context 'HTML format' do
context 'browser-based interaction' do
let(:user) { User.new id: 'xyz' }

subject do
get :create, params: { format: :html }
get :create, params: { origin: 'http://example.edu/', messageId: '1' }
end

it 'redirects to JSON' do
expect(subject).to redirect_to format: :js
it 'sets the X-Frame-Options header' do
expect(subject.headers['X-Frame-Options']).to eq 'ALLOW-FROM http://example.edu/'
end
end

context 'with a user' do
let(:user) { User.new id: 'xyz' }
it 'assigns the message and origin parameters' do
expect(subject.response_code).to eq 200
expect(assigns(:origin)).to eq 'http://example.edu/'
expect(assigns(:message)).to include messageId: '1', accessToken: be_present
end

context 'other formats' do
subject do
get :create, params: { origin: 'http://example.edu/', messageId: '1', format: :js }
end

it 'renders HTML anyway' do
expect(subject.body).to match(/<html/)
end
end

it 'returns the token response' do
expect(subject.status).to eq 200
context 'missing the origin header' do
subject do
get :create, params: { messageId: '1' }
end

data = JSON.parse(subject.body)
expect(data['accessToken']).not_to be_blank
expect(data['tokenType']).to eq 'Bearer'
expect(data['expiresIn']).to be > 0
it 'returns a 400 error' do
expect { subject.response }.to raise_error ActionController::ParameterMissing
end
end
end

context 'with an anonymous user' do
it 'returns the error response' do
expect(subject.status).to eq 401
context 'JSON API interaction' do
subject do
get :create, params: { format: :js }
end

context 'HTML format' do
subject do
get :create, params: { format: :html }
end

it 'redirects to JSON' do
expect(subject).to redirect_to format: :js
end
end

context 'with a user' do
let(:user) { User.new id: 'xyz' }

it 'returns the token response' do
expect(subject.status).to eq 200

data = JSON.parse(subject.body)
expect(data['accessToken']).not_to be_blank
expect(data['tokenType']).to eq 'Bearer'
expect(data['expiresIn']).to be > 0
end
end

context 'with an anonymous user' do
it 'returns the error response' do
expect(subject.status).to eq 401

data = JSON.parse(subject.body)
expect(data['error']).to eq 'missingCredentials'
data = JSON.parse(subject.body)
expect(data['error']).to eq 'missingCredentials'
end
end
end
end
Expand Down

0 comments on commit 98794a7

Please sign in to comment.