webfinger-2.1.3/0000755000004100000410000000000014574706602013530 5ustar www-datawww-datawebfinger-2.1.3/webfinger.gemspec0000644000004100000410000000202014574706602017037 0ustar www-datawww-dataGem::Specification.new do |gem| gem.name = 'webfinger' gem.version = File.read('VERSION').delete("\n\r") gem.authors = ['nov matake'] gem.email = ['nov@matake.jp'] gem.description = %q{Ruby WebFinger client library} gem.summary = %q{Ruby WebFinger client library, following IETF WebFinger WG spec updates.} gem.homepage = 'https://github.com/nov/webfinger' gem.license = 'MIT' gem.files = `git ls-files`.split($/) gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.require_paths = ['lib'] gem.add_runtime_dependency 'faraday', '~> 2.0' gem.add_runtime_dependency 'faraday-follow_redirects' gem.add_runtime_dependency 'activesupport' gem.add_development_dependency 'rake' gem.add_development_dependency 'rspec' gem.add_development_dependency 'rspec-its' gem.add_development_dependency 'simplecov' gem.add_development_dependency 'webmock', '>= 1.6.2' end webfinger-2.1.3/.gitignore0000644000004100000410000000023314574706602015516 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage* doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp webfinger-2.1.3/README.rdoc0000644000004100000410000000410014574706602015331 0ustar www-datawww-data= WebFinger An Ruby WebFinger client library. Following the latest WebFinger spec discussed at IETF WebFinger WG. http://tools.ietf.org/html/draft-ietf-appsawg-webfinger If you found something different from the latest spec, open an issue please. == Installation Add this line to your application's Gemfile: gem 'webfinger' And then execute: $ bundle Or install it yourself as: $ gem install webfinger == Usage === Basic You can discover resource metadata. WebFinger.discover! 'acct:nov@connect-op.heroku.com' WebFinger.discover! 'connect-op.heroku.com' WebFinger.discover! 'http://connect-op.heroku.com' You can also specify link relations via "rel" option. WebFinger.discover! 'acct:nov@connect-op.heroku.com', rel: 'http://openid.net/specs/connect/1.0/issuer' WebFinger.discover! 'acct:nov@connect-op.heroku.com', rel: ['http://openid.net/specs/connect/1.0/issuer', 'vcard'] === Caching Caching is important in HTTP-based discovery. If you set your own cache object to WebFinger.cache, this gem caches the discovery result until it expires. (the expiry is calculated based on the "expires" value in JRD response) # Set Cache WebFinger.cache = Rails.cache WebFinger.discover! 'acct:nov@connect-op.heroku.com' # do HTTP request WebFinger.discover! 'acct:nov@connect-op.heroku.com' # use cache, no HTTP request === Debugging Once you turn-on debugging, you can see all HTTP request/response in your log. # Turn on debugging WebFinger.debug! # Set logger (OPTIONAL, ::Logger.new(STDOUT) is used as default) WebFinger.logger = Rails.logger You can also specify URL builder to force non-HTTPS access. (NOTE: allow non-HTTPS access only for debugging, not on your production.) WebFinger.url_builder = URI::HTTP # default URI::HTTPS == Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request == Copyright Copyright (c) 2012 nov matake. See LICENSE for details. webfinger-2.1.3/.github/0000755000004100000410000000000014574706602015070 5ustar www-datawww-datawebfinger-2.1.3/.github/workflows/0000755000004100000410000000000014574706602017125 5ustar www-datawww-datawebfinger-2.1.3/.github/workflows/spec.yml0000644000004100000410000000110314574706602020575 0ustar www-datawww-dataname: Spec on: push: branches: - main pull_request: permissions: contents: read jobs: spec: strategy: matrix: os: ['ubuntu-20.04', 'ubuntu-22.04'] ruby-version: ['3.1', '3.2', '3.3'] include: - os: 'ubuntu-20.04' ruby-version: '3.0' runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true - name: Run Specs run: bundle exec rake spec webfinger-2.1.3/.github/FUNDING.yml0000644000004100000410000000007314574706602016705 0ustar www-datawww-data# These are supported funding model platforms github: nov webfinger-2.1.3/lib/0000755000004100000410000000000014574706602014276 5ustar www-datawww-datawebfinger-2.1.3/lib/webfinger.rb0000644000004100000410000000242214574706602016573 0ustar www-datawww-datarequire 'json' require 'faraday' require 'faraday/follow_redirects' require 'active_support' require 'active_support/core_ext' module WebFinger VERSION = File.read( File.join(File.dirname(__FILE__), '../VERSION') ).delete("\n\r") module_function def discover!(resource, options = {}) Request.new(resource, options).discover! end def logger @logger end def logger=(logger) @logger = logger end self.logger = ::Logger.new(STDOUT) logger.progname = 'WebFinger' def debugging? @debugging end def debugging=(boolean) @debugging = boolean end def debug! self.debugging = true end self.debugging = false def url_builder @url_builder ||= URI::HTTPS end def url_builder=(builder) @url_builder = builder end def http_client Faraday.new(headers: {user_agent: "WebFinger #{VERSION}"}) do |faraday| faraday.response :raise_error faraday.response :json faraday.response :follow_redirects faraday.response :logger, WebFinger.logger if debugging? faraday.adapter Faraday.default_adapter http_config.try(:call, faraday) end end def http_config(&block) @http_config ||= block end end require 'webfinger/exception' require 'webfinger/request' require 'webfinger/response' webfinger-2.1.3/lib/webfinger/0000755000004100000410000000000014574706602016246 5ustar www-datawww-datawebfinger-2.1.3/lib/webfinger/request.rb0000644000004100000410000000355414574706602020272 0ustar www-datawww-datamodule WebFinger class Request attr_accessor :resource, :relations def initialize(resource, options = {}) self.resource = resource self.relations = Array(options[:rel] || options[:relations]) @options = options end def discover! handle_response do WebFinger.http_client.get(endpoint.to_s) end end private def endpoint path = '/.well-known/webfinger' host, port = host_and_port WebFinger.url_builder.build [nil, host, port, path, query_string, nil] end def host_and_port uri = URI.parse(resource) rescue nil if uri.try(:host).present? [uri.host, [80, 443].include?(uri.port) ? nil : uri.port] else scheme_or_host, host_or_port, port_or_nil = resource.split('@').last.split('/').first.split(':') case host_or_port when nil, /\d+/ [scheme_or_host, host_or_port.try(:to_i)] else [host_or_port, port_or_nil.try(:to_i)] end end end def query_string query_params.join('&') end def query_params query_params = [{resource: resource}] relations.each do |relation| query_params << {rel: relation} end query_params.collect(&:to_query) end def handle_response json = yield.body.with_indifferent_access Response.new json rescue Faraday::Error => e case e.response_status when nil raise Exception.new e when 400 raise BadRequest.new('Bad Request', e.response_body) when 401 raise Unauthorized.new('Unauthorized', e.response_body) when 403 raise Forbidden.new('Forbidden', e.response_body) when 404 raise NotFound.new('Not Found', e.response_body) else raise HttpError.new(e.response_status, e.response_body, e.response_body) end end end end webfinger-2.1.3/lib/webfinger/response.rb0000644000004100000410000000063214574706602020432 0ustar www-datawww-data# NOTE: # Make a JSON Resource Descriptor (JRD) gem as separate one and use it as superclass? module WebFinger class Response < ActiveSupport::HashWithIndifferentAccess [:subject, :aliases, :properties, :links].each do |method| define_method method do self[method] end end def link_for(rel) links.detect do |link| link[:rel] == rel end end end endwebfinger-2.1.3/lib/webfinger/exception.rb0000644000004100000410000000143114574706602020570 0ustar www-datawww-datamodule WebFinger class Exception < StandardError; end class HttpError < Exception attr_accessor :status, :response def initialize(status, message = nil, response = nil) super message @status = status @response = response end end class BadRequest < HttpError def initialize(message = nil, response = nil) super 400, message, response end end class Unauthorized < HttpError def initialize(message = nil, response = nil) super 401, message, response end end class Forbidden < HttpError def initialize(message = nil, response = nil) super 403, message, response end end class NotFound < HttpError def initialize(message = nil, response = nil) super 404, message, response end end end webfinger-2.1.3/LICENSE.txt0000644000004100000410000000205214574706602015352 0ustar www-datawww-dataCopyright (c) 2012 nov matake MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.webfinger-2.1.3/spec/0000755000004100000410000000000014574706602014462 5ustar www-datawww-datawebfinger-2.1.3/spec/mock_json/0000755000004100000410000000000014574706602016444 5ustar www-datawww-datawebfinger-2.1.3/spec/mock_json/all.json0000644000004100000410000000125314574706602020110 0ustar www-datawww-data{ "expires": "2012-11-16T19:41:35Z", "subject": "acct:nov@example.com", "aliases": ["http://nov.example.com/"], "properties": { "http://example.com/rel/role/": "employee" }, "links": [{ "rel": "http://webfinger.net/rel/avatar", "type": "image/jpeg", "href": "http://nov.example.com/profile_picture.png" }, { "rel": "http://webfinger.net/rel/profile-page", "href": "http://nov.example.com/" }, { "rel": "blog", "type": "text/html", "href": "http://blogs.nov.example.com/", "titles": { "en-us": "Nov's Blog", "ja": "Novのブログ" } }, { "rel": "vcard", "href": "http://nov.example.com/me.vcf" }] } webfinger-2.1.3/spec/mock_json/email_config.json0000644000004100000410000000111614574706602021752 0ustar www-datawww-data{ "subject": "mailto:nov@example.com", "links": [{ "rel": "http://example.net/rel/smtp-server", "properties": { "http://example.net/email/host": "smtp.example.com", "http://example.net/email/port": "587", "http://example.net/email/login-required": "yes", "http://example.net/email/transport": "starttls" } }, { "rel": "http://example.net/rel/imap-server", "properties": { "http://example.net/email/host": "imap.example.com", "http://example.net/email/port": "993", "http://example.net/email/transport": "ssl" } }] } webfinger-2.1.3/spec/mock_json/open_id.json0000644000004100000410000000046414574706602020760 0ustar www-datawww-data{ "expires": "2012-11-16T19:41:35Z", "subject": "acct:nov@example.com", "aliases": ["http://nov.example.com/"], "properties": { "http://example.com/rel/role/": "employee" }, "links": [{ "rel": "http://openid.net/specs/connect/1.0/issuer", "href": "https://openid.example.com/" }] } webfinger-2.1.3/spec/mock_json/device_info.json0000644000004100000410000000021714574706602021611 0ustar www-datawww-data{ "subject": "device:p1.example.com", "links": [{ "rel": "http://example.com/rel/tipsi", "href": "http://192.168.1.5/npap/" }] } webfinger-2.1.3/spec/spec_helper.rb0000644000004100000410000000040614574706602017300 0ustar www-datawww-datarequire 'simplecov' SimpleCov.start do add_filter 'spec' end require 'rspec' require 'rspec/its' require 'webfinger' RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] end end require 'helpers/webmock_helper'webfinger-2.1.3/spec/helpers/0000755000004100000410000000000014574706602016124 5ustar www-datawww-datawebfinger-2.1.3/spec/helpers/webmock_helper.rb0000644000004100000410000000163214574706602021441 0ustar www-datawww-datarequire 'webmock/rspec' module WebMockHelper def mock_json(endpoint, response_file, options = {}) endpoint = endpoint.to_s stub_request(:get, endpoint).with( request_for(options) ).to_return( response_for(response_file, options) ) yield a_request(:get, endpoint).with( request_for(options) ).should have_been_made.once end private def request_for(options = {}) request = {} if options[:query] request[:query] = options[:query] end request end def response_for(response_file, options = {}) response = {} response[:headers] = { 'Content-Type': 'application/json' } response[:body] = File.new(File.join(File.dirname(__FILE__), '../mock_json', "#{response_file}.json")) if options[:status] response[:status] = options[:status] end response end end include WebMockHelper WebMock.disable_net_connect! webfinger-2.1.3/spec/webfinger_spec.rb0000644000004100000410000001464014574706602017776 0ustar www-datawww-datarequire 'spec_helper' describe WebFinger do let(:resource) { 'acct:nov@example.com' } describe '#discover!' do { 'example.com' => 'https://example.com', 'example.com/~nov/' => 'https://example.com', 'nov@example.com' => 'https://example.com', 'nov.matake@example.com' => 'https://example.com', 'acct:nov@example.com' => 'https://example.com', 'mailto:nov@example.com' => 'https://example.com', 'device:example.com' => 'https://example.com', 'unknown:nov@example.com' => 'https://example.com', 'http://example.com/nov' => 'https://example.com', 'https://example.com/nov' => 'https://example.com', 'example.com:8080' => 'https://example.com:8080', 'example.com:8080/~nov/' => 'https://example.com:8080', 'nov@example.com:8080' => 'https://example.com:8080', 'nov.matake@example.com:8080' => 'https://example.com:8080', 'acct:nov@example.com:8080' => 'https://example.com:8080', 'mailto:nov@example.com:8080' => 'https://example.com:8080', 'device:example.com:8080' => 'https://example.com:8080', 'unknown:nov@example.com:8080' => 'https://example.com:8080', 'http://example.com:8080' => 'https://example.com:8080', 'https://example.com:8080' => 'https://example.com:8080', 'discover.example.com' => 'https://discover.example.com', 'discover.example.com/~nov/' => 'https://discover.example.com', 'nov@discover.example.com' => 'https://discover.example.com', 'nov.matake@discover.example.com' => 'https://discover.example.com', 'acct:nov@discover.example.com' => 'https://discover.example.com', 'mailto:nov@discover.example.com' => 'https://discover.example.com', 'device:discover.example.com' => 'https://discover.example.com', 'unknown:nov@discover.example.com' => 'https://discover.example.com', 'http://discover.example.com/nov' => 'https://discover.example.com', 'https://discover.example.com/nov' => 'https://discover.example.com', 'discover.example.com:8080' => 'https://discover.example.com:8080', 'discover.example.com:8080/~nov/' => 'https://discover.example.com:8080', 'nov@discover.example.com:8080' => 'https://discover.example.com:8080', 'nov.matake@discover.example.com:8080' => 'https://discover.example.com:8080', 'acct:nov@discover.example.com:8080' => 'https://discover.example.com:8080', 'mailto:nov@discover.example.com:8080' => 'https://discover.example.com:8080', 'device:discover.example.com:8080' => 'https://discover.example.com:8080', 'unknown:nov@discover.example.com:8080' => 'https://discover.example.com:8080', 'http://discover.example.com:8080/nov' => 'https://discover.example.com:8080', 'https://discover.example.com:8080/nov' => 'https://discover.example.com:8080' }.each do |resource, base_url| endpoint = File.join base_url, '/.well-known/webfinger' context "when resource=#{resource}" do it "should access to #{endpoint}" do mock_json endpoint, 'all', query: {resource: resource} do response = WebFinger.discover! resource response.should be_instance_of WebFinger::Response end end end end context 'with rel option' do shared_examples_for :discovery_with_rel do let(:query_string) do query_params = [{resource: resource}.to_query] Array(rel).each do |_rel_| query_params << {rel: _rel_}.to_query end query_params.join('&') end it 'should request with rel' do query_string.scan('rel').count.should == Array(rel).count mock_json 'https://example.com/.well-known/webfinger', 'all', query: query_string do response = WebFinger.discover! resource, rel: rel response.should be_instance_of WebFinger::Response end end end context 'when single rel' do let(:rel) { 'http://openid.net/specs/connect/1.0/issuer' } it_behaves_like :discovery_with_rel end context 'when multiple rel' do let(:rel) { ['http://openid.net/specs/connect/1.0/issuer', 'vcard'] } it_behaves_like :discovery_with_rel end end context 'when error' do { 400 => WebFinger::BadRequest, 401 => WebFinger::Unauthorized, 403 => WebFinger::Forbidden, 404 => WebFinger::NotFound, 500 => WebFinger::HttpError }.each do |status, exception_class| context "when status=#{status}" do it "should raise #{exception_class}" do expect do mock_json 'https://example.com/.well-known/webfinger', 'all', query: {resource: resource}, status: [status, 'HTTPError'] do response = WebFinger.discover! resource end end.to raise_error exception_class end end end end end describe '#logger' do subject { WebFinger.logger } context 'as default' do it { should be_instance_of Logger } end context 'when specified' do let(:logger) { 'Rails.logger or something' } before { WebFinger.logger = logger } it { should == logger } end end describe '#debugging?' do subject { WebFinger.debugging? } context 'as default' do it { should == false } end context 'when debugging' do before { WebFinger.debug! } it { should == true } context 'when debugging mode canceled' do before { WebFinger.debugging = false } it { should == false } end end end describe '#url_builder' do subject { WebFinger.url_builder } context 'as default' do it { should == URI::HTTPS } end context 'when specified' do let(:url_builder) { 'URI::HTTP or something' } before { WebFinger.url_builder = url_builder } it { should == url_builder } end end describe '#http_client' do subject { WebFinger.http_client } describe '#logger' do subject { WebFinger.http_client.builder.handlers.collect(&:klass) } context 'as default' do it { should_not include Faraday::Response::Logger } end context 'when debugging' do before { WebFinger.debug! } it { should include Faraday::Response::Logger } end end end end webfinger-2.1.3/spec/webfinger/0000755000004100000410000000000014574706602016432 5ustar www-datawww-datawebfinger-2.1.3/spec/webfinger/response_spec.rb0000644000004100000410000000205714574706602021633 0ustar www-datawww-datarequire 'spec_helper' describe WebFinger::Response do let(:_subject_) { 'acct:nov@matake.jp' } let(:aliases) { ['mailto:nov@matake.jp'] } let(:properties) do {'http://webfinger.net/rel/name' => 'Nov Matake'} end let(:links) do [{ rel: 'http://openid.net/specs/connect/1.0/issuer', href: 'https://openid.example.com/' }.with_indifferent_access] end let(:attributes) do { subject: _subject_, aliases: aliases, properties: properties, links: links }.with_indifferent_access end subject do WebFinger::Response.new attributes end its(:subject) { should == _subject_ } its(:aliases) { should == aliases } its(:properties) { should == properties } its(:links) { should == links } describe '#link_for' do context 'when unknown' do it do subject.link_for('unknown').should be_nil end end context 'otherwise' do it do subject.link_for('http://openid.net/specs/connect/1.0/issuer').should == links.first end end end endwebfinger-2.1.3/Rakefile0000644000004100000410000000062214574706602015175 0ustar www-datawww-datarequire 'bundler' Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) namespace :coverage do desc "Open coverage report" task :report do require 'simplecov' `open "#{File.join SimpleCov.coverage_path, 'index.html'}"` end end task :spec do Rake::Task[:'coverage:report'].invoke unless ENV['TRAVIS_RUBY_VERSION'] end task :default => :specwebfinger-2.1.3/Gemfile0000644000004100000410000000004614574706602015023 0ustar www-datawww-datasource 'https://rubygems.org' gemspecwebfinger-2.1.3/VERSION0000644000004100000410000000000514574706602014573 0ustar www-datawww-data2.1.3webfinger-2.1.3/CHANGELOG.md0000644000004100000410000000120314574706602015335 0ustar www-datawww-data## [Unreleased] ## [2.1.1] - 2022-10-09 ### Changed - convert Faraday::Error to WebFinger::Exception https://github.com/nov/webfinger/commit/a0d8da20f6ce819eea8c2d2c81bcc90d3f41df6f ## [2.1.1] - 2022-10-08 ### Fixed - make response json indifferent accessable https://github.com/nov/webfinger/commit/806bc2e2220024f419f44857e5629ff7f9f78fbf ## [2.1.0] - 2022-10-08 ### Changed - Moved to faraday v2 https://github.com/nov/webfinger/commit/e72fa7b7c7018c809ba9ce8aae2ee64d6c1d1d02 ## [2.0.0] - 2022-10-08 ### Added - start recording CHANGELOG ### Changed - Switch from httpclient to faraday https://github.com/nov/webfinger/pull/9