Implements the optional OAuth 2.0 Authorization Code grant with PKCE (RFC 7636) for remoteStorage, as described in the remoteStorage spec (section 10.1). - Advertise the authorization and token endpoints plus the supported code_challenge method (S256) in WebFinger, and bump the advertised spec version to draft-dejong-remotestorage-27 - Add RsAuthorizationCode: a short-lived (5 min), single-use authorization code stored only as a SHA256 digest - Add RsOauthManager services for issuing and exchanging codes, including S256 verification - Add a cross-origin POST /rs/oauth/token endpoint that exchanges a code (and code_verifier) for an access token, with CORS and no CSRF token - Consent dialog now carries response_type / code_challenge through the form and redirects with the code in the query string for the code flow - Remove the existing-token reuse shortcut so every authorization results in a distinct authorization (and token) Part of #254
58 lines
1.7 KiB
Ruby
58 lines
1.7 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe "remoteStorage OAuth token endpoint", type: :request do
|
|
let(:user) { create :user }
|
|
|
|
# Example values from RFC 7636, Appendix B
|
|
let(:code_verifier) { "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" }
|
|
let(:code_challenge) { "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM" }
|
|
|
|
before do
|
|
allow_any_instance_of(AppCatalog::WebApp).to receive(:update_metadata).and_return(true)
|
|
allow_any_instance_of(RemoteStorageAuthorization).to receive(:remove_token_expiry_job).and_return(nil)
|
|
end
|
|
|
|
after do
|
|
user.remote_storage_authorizations.destroy_all
|
|
RsAuthorizationCode.destroy_all
|
|
end
|
|
|
|
def issue_code
|
|
RsOauthManager::IssueCode.call(
|
|
user: user,
|
|
client_id: "example.com",
|
|
redirect_uri: "https://example.com",
|
|
permissions: %w(documents:rw),
|
|
code_challenge: code_challenge,
|
|
code_challenge_method: "S256"
|
|
)
|
|
end
|
|
|
|
it "exchanges a code for an access token without a session" do
|
|
code = issue_code
|
|
|
|
post "/rs/oauth/token", params: {
|
|
grant_type: "authorization_code",
|
|
code: code.code,
|
|
client_id: "https://example.com",
|
|
redirect_uri: "https://example.com",
|
|
code_verifier: code_verifier
|
|
}
|
|
|
|
expect(response).to have_http_status(:ok)
|
|
expect(response.media_type).to eq("application/json")
|
|
|
|
json = JSON.parse(response.body)
|
|
expect(json["access_token"]).to be_present
|
|
expect(json["token_type"]).to eq("bearer")
|
|
expect(response.headers["Access-Control-Allow-Origin"]).to eq("*")
|
|
end
|
|
|
|
it "answers CORS preflight requests" do
|
|
options "/rs/oauth/token"
|
|
|
|
expect(response).to have_http_status(:ok)
|
|
expect(response.headers["Access-Control-Allow-Methods"]).to include("POST")
|
|
end
|
|
end
|