Files
akkounts/spec/requests/webfinger_spec.rb
T
raucao 8fc7ffdd1d
CI / Test (pull_request) Successful in 41s
Add PKCE support to remoteStorage OAuth
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
2026-11-02 16:40:25 +04:00

191 lines
7.5 KiB
Ruby

require 'rails_helper'
RSpec.describe "WebFinger", type: :request do
describe "User does not exist" do
it "returns a 404 status" do
get "/.well-known/webfinger?resource=acct%3Ajane.doe%40kosmos.org"
expect(response).to have_http_status(:not_found)
end
end
context "User exists" do
let(:user) { create :user, cn: 'tony', ou: 'kosmos.org' }
before do
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
services_enabled: ["mastodon", "remotestorage"]
})
end
describe "Avatar" do
context "not available" do
it "does not include an avatar link" do
get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org"
expect(response).to have_http_status(:ok)
res = JSON.parse(response.body)
link = res["links"].find{|l| l["rel"] == "http://webfinger.net/rel/avatar"}
expect(link).to be_nil
end
end
context "available" do
let(:fixture_path) { Rails.root.join("spec/fixtures/files/taipei.jpg") }
let(:img_data) { File.read(fixture_path) }
let(:img_hash) { Digest::SHA256.hexdigest(img_data) }
before do
ActiveStorage::Blob.create_and_upload!(
io: File.open(fixture_path),
filename: "#{img_hash}.jpg",
content_type: "image/jpeg"
).tap do |blob|
user.avatar.attach(blob)
end
end
it "includes a public avatar link" do
get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org"
expect(response).to have_http_status(:ok)
res = JSON.parse(response.body)
link = res["links"].find { |l| l["rel"] == "http://webfinger.net/rel/avatar" }
expect(link).to be_present
expect(link["type"]).to eq("image/jpeg")
expect(link["href"]).to match(%r{users/tony/avatars/#{img_hash}.jpg})
end
end
end
describe "Mastodon entries" do
context "Mastodon available" do
it "includes the Mastodon aliases and links for the user" do
get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org"
expect(response).to have_http_status(:ok)
res = JSON.parse(response.body)
expect(res["aliases"]).to include("http://example.social/@tony")
expect(res["aliases"]).to include("http://example.social/users/tony")
profile_link = res["links"].find{|l| l["rel"] == "http://webfinger.net/rel/profile-page"}
self_link = res["links"].find{|l| l["rel"] == "self"}
ostatus_link = res["links"].find{|l| l["rel"] == "http://ostatus.org/schema/1.0/subscribe"}
expect(profile_link["type"]).to eql("text/html")
expect(profile_link["href"]).to eql("http://example.social/@tony")
expect(self_link["type"]).to eql("application/activity+json")
expect(self_link["href"]).to eql("http://example.social/users/tony")
expect(ostatus_link["template"]).to eql("http://example.social/authorize_interaction?uri={uri}")
end
end
context "Mastodon not enabled for user" do
before do
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
services_enabled: ["ejabberd"]
})
end
it "does not include Mastodon aliases or links" do
get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org"
expect(response).to have_http_status(:ok)
res = JSON.parse(response.body)
expect(res["aliases"]).not_to include("http://example.social/@tony")
expect(res["aliases"]).not_to include("http://example.social/users/tony")
expect(res["links"].find{|l| l["rel"] == "http://webfinger.net/rel/profile-page"}).to be(nil)
expect(res["links"].find{|l| l["rel"] == "self"}).to be(nil)
expect(res["links"].find{|l| l["rel"] == "http://ostatus.org/schema/1.0/subscribe"}).to be(nil)
end
end
context "Mastodon not available" do
before do
Setting.mastodon_enabled = false
end
it "does not include Mastodon aliases or links" do
get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org"
expect(response).to have_http_status(:ok)
res = JSON.parse(response.body)
expect(res["aliases"]).not_to include("http://example.social/@tony")
expect(res["aliases"]).not_to include("http://example.social/users/tony")
expect(res["links"].find{|l| l["rel"] == "http://webfinger.net/rel/profile-page"}).to be(nil)
expect(res["links"].find{|l| l["rel"] == "self"}).to be(nil)
expect(res["links"].find{|l| l["rel"] == "http://ostatus.org/schema/1.0/subscribe"}).to be(nil)
end
end
end
describe "remoteStorage entries" do
context "remoteStorage available" do
it "includes the remoteStorage link for the user" do
get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org"
expect(response).to have_http_status(:ok)
res = JSON.parse(response.body)
rs_link = res["links"].find {|l| l["rel"] == "http://tools.ietf.org/id/draft-dejong-remotestorage"}
expect(rs_link["href"]).to eql("#{Setting.rs_storage_url}/tony")
oauth_url = rs_link["properties"]["http://tools.ietf.org/html/rfc6749#section-4.2"]
expect(oauth_url).to eql("http://localhost/rs/oauth/tony")
authorization_url = rs_link["properties"]["http://tools.ietf.org/html/rfc6749#section-3.1"]
expect(authorization_url).to eql("http://localhost/rs/oauth/tony")
token_url = rs_link["properties"]["http://tools.ietf.org/html/rfc6749#section-3.2"]
expect(token_url).to eql("http://localhost/rs/oauth/token")
expect(rs_link["properties"]["http://tools.ietf.org/html/rfc7636"]).to eql("S256")
expect(rs_link["properties"]["http://remotestorage.io/spec/version"]).to eql("draft-dejong-remotestorage-27")
end
it "returns CORS headers" do
get "/.well-known/nostr.json?name=bobdylan"
expect(response.headers['Access-Control-Allow-Origin']).to eq("*")
expect(response.headers['Access-Control-Allow-Methods']).to eq('GET')
end
end
context "remoteStorage not enabled for user" do
before do
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
services_enabled: ["ejabberd"]
})
end
it "does not include the remoteStorage link" do
get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org"
expect(response).to have_http_status(:ok)
res = JSON.parse(response.body)
rs_link = res["links"].find {|l| l["rel"] == "http://tools.ietf.org/id/draft-dejong-remotestorage"}
expect(rs_link).to be_nil
end
end
context "remoteStorage not available" do
before do
Setting.remotestorage_enabled = false
end
it "does not include the remoteStorage link" do
get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org"
expect(response).to have_http_status(:ok)
res = JSON.parse(response.body)
rs_link = res["links"].find {|l| l["rel"] == "http://tools.ietf.org/id/draft-dejong-remotestorage"}
expect(rs_link).to be_nil
end
end
end
end
end