From b35108e9d0c8a38de235bc49c55a5447ea4c16a1 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Wed, 4 Jan 2023 17:08:05 +0100 Subject: [PATCH] Implement NodeInfo Closes #902 --- Gemfile | 1 + Gemfile.lock | 3 + .../well_known/node_info_controller.rb | 58 ++++++++++ config/justask.yml.example | 2 +- config/routes.rb | 7 +- .../well_known/node_info_controller_spec.rb | 102 ++++++++++++++++++ 6 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 app/controllers/well_known/node_info_controller.rb create mode 100644 spec/controllers/well_known/node_info_controller_spec.rb diff --git a/Gemfile b/Gemfile index a6e08c35..c6bfe77c 100644 --- a/Gemfile +++ b/Gemfile @@ -88,6 +88,7 @@ group :development, :test do gem "factory_bot_rails", require: false gem "faker" gem "haml_lint", require: false + gem "json-schema" gem "letter_opener" # Use this just in local test environments gem "rails-controller-testing" gem "rake" diff --git a/Gemfile.lock b/Gemfile.lock index 947c9c22..f1b1ec67 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -225,6 +225,8 @@ GEM mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) json (2.6.3) + json-schema (3.0.0) + addressable (>= 2.8) jwt (2.6.0) kaminari (1.2.2) activesupport (>= 4.1.0) @@ -567,6 +569,7 @@ DEPENDENCIES hcaptcha (~> 7.0) httparty i18n-js (= 4.0) + json-schema jwt (~> 2.6) letter_opener lograge diff --git a/app/controllers/well_known/node_info_controller.rb b/app/controllers/well_known/node_info_controller.rb new file mode 100644 index 00000000..6633b137 --- /dev/null +++ b/app/controllers/well_known/node_info_controller.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +class WellKnown::NodeInfoController < ApplicationController + def discovery + expires_in 3.days, public: true + + render json: { + links: [ + rel: "http://nodeinfo.diaspora.software/ns/schema/2.1", + href: node_info_url + ] + } + end + + def nodeinfo + expires_in 30.minutes, public: true + + render json: { + version: "2.1", + software: software_info, + protocols: %i[], + services: { + inbound: inbound_services, + outbound: outbound_services + }, + usage: usage_stats, + # We don't implement this so we can always return true for now + openRegistrations: true, + metadata: {} + } + end + + private + + def software_info + { + name: "Retrospring", + version: Retrospring::Version.to_s, + repository: "https://github.com/Retrospring/retrospring" + } + end + + def usage_stats + { + users: { + total: User.count + } + } + end + + def inbound_services = [] + + def outbound_services + { + "twitter" => APP_CONFIG.dig("sharing", "twitter", "enabled") + }.select { |_service, available| available }.keys + end +end diff --git a/config/justask.yml.example b/config/justask.yml.example index 1d627249..65e109c6 100644 --- a/config/justask.yml.example +++ b/config/justask.yml.example @@ -56,7 +56,7 @@ features: # OAuth tokens sharing: twitter: - enabled: true + enabled: false # Get the tokens from https://apps.twitter.com consumer_key: '' consumer_secret: '' diff --git a/config/routes.rb b/config/routes.rb index 0929f60c..90d380ca 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -172,7 +172,12 @@ Rails.application.routes.draw do get "/feedback/bugs(/*any)", to: "feedback#bugs", as: "feedback_bugs" get "/feedback/feature_requests(/*any)", to: "feedback#features", as: "feedback_features" - get "/.well-known/change-password", to: redirect("/settings/account") + namespace :well_known, path: "/.well-known" do + get "/change-password", to: redirect("/settings/account") + get "/nodeinfo", to: "node_info#discovery" + end + + get "/nodeinfo/2.1", to: "well_known/node_info#nodeinfo", as: :node_info puts "processing time of routes.rb: #{"#{(Time.zone.now - start).round(3).to_s.ljust(5, '0')}s".light_green}" end diff --git a/spec/controllers/well_known/node_info_controller_spec.rb b/spec/controllers/well_known/node_info_controller_spec.rb new file mode 100644 index 00000000..d3338235 --- /dev/null +++ b/spec/controllers/well_known/node_info_controller_spec.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe WellKnown::NodeInfoController do + describe "#discovery" do + subject { get :discovery } + + it "returns the expected response" do + subject + parsed = JSON.parse(response.body) + expect(parsed).to eq({ + "links" => [ + { + "rel" => "http://nodeinfo.diaspora.software/ns/schema/2.1", + "href" => "http://test.host/nodeinfo/2.1" + } + ] + }) + end + end + + describe "#nodeinfo" do + subject { get :nodeinfo } + + it "is valid as specified by the schema" do + get(:discovery) + schema = response.body + subject + parsed = JSON.parse(response.body) + messages = JSON::Validator.fully_validate(schema, parsed) + + expect(messages).to be_empty + end + + context "version is 2023.0120.1" do + before do + allow(Retrospring::Version).to receive(:to_s).and_return("2023.0102.1") + end + + it "returns the correct version" do + subject + parsed = JSON.parse(response.body) + expect(parsed["software"]).to eq({ + "name" => "Retrospring", + "version" => "2023.0102.1", + "repository" => "https://github.com/Retrospring/retrospring" + }) + end + end + + context "Twitter integration enabled" do + before do + stub_const("APP_CONFIG", { + "sharing" => { + "twitter" => { + "enabled" => true + } + } + }) + end + + it "includes Twitter in outbound services" do + subject + parsed = JSON.parse(response.body) + expect(parsed.dig("services", "outbound")).to include("twitter") + end + end + + context "Twitter integration disabled" do + before do + stub_const("APP_CONFIG", { + "sharing" => { + "twitter" => { + "enabled" => false + } + } + }) + end + + it "includes Twitter in outbound services" do + subject + parsed = JSON.parse(response.body) + parsed.dig("services", "outbound").should_not include("twitter") + end + end + + context "site has users" do + let(:num_users) { rand(10..50) } + + before do + FactoryBot.create_list(:user, num_users) + end + + it "includes the correct user count" do + subject + parsed = JSON.parse(response.body) + expect(parsed.dig("usage", "users", "total")).to eq(num_users) + end + end + end +end