federails 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +38 -1
- data/app/controllers/federails/client/activities_controller.rb +9 -3
- data/app/controllers/federails/client/actors_controller.rb +5 -2
- data/app/controllers/federails/client/followings_controller.rb +13 -3
- data/app/controllers/federails/client_controller.rb +9 -0
- data/app/controllers/federails/server/activities_controller.rb +11 -6
- data/app/controllers/federails/server/actors_controller.rb +6 -2
- data/app/controllers/federails/server/followings_controller.rb +3 -2
- data/app/controllers/federails/server/nodeinfo_controller.rb +13 -7
- data/app/controllers/federails/server/web_finger_controller.rb +7 -3
- data/app/controllers/federails/{application_controller.rb → server_controller.rb} +10 -3
- data/app/helpers/federails/server_helper.rb +12 -0
- data/app/models/concerns/federails/entity.rb +67 -15
- data/app/models/concerns/federails/has_uuid.rb +35 -0
- data/app/models/federails/activity.rb +8 -13
- data/app/models/federails/actor.rb +41 -2
- data/app/models/federails/following.rb +9 -0
- data/app/policies/federails/client/activity_policy.rb +3 -0
- data/app/policies/federails/client/actor_policy.rb +1 -1
- data/app/policies/federails/client/following_policy.rb +2 -2
- data/app/policies/federails/federails_policy.rb +4 -0
- data/app/policies/federails/server/activity_policy.rb +3 -0
- data/app/views/federails/client/activities/_activity.html.erb +1 -1
- data/app/views/federails/client/activities/feed.html.erb +8 -1
- data/app/views/federails/client/activities/index.html.erb +1 -1
- data/app/views/federails/client/actors/index.html.erb +14 -0
- data/app/views/federails/client/actors/show.html.erb +101 -89
- data/app/views/federails/client/common/_client_links.html.erb +24 -0
- data/app/views/federails/client/followings/_follow_actions.html.erb +32 -0
- data/app/views/federails/client/followings/_form.html.erb +1 -0
- data/app/views/federails/server/activities/{_activity.json.jbuilder → _activity.activitypub.jbuilder} +6 -1
- data/app/views/federails/server/actors/_actor.activitypub.jbuilder +26 -0
- data/app/views/federails/server/nodeinfo/{show.json.jbuilder → show.nodeinfo.jbuilder} +8 -7
- data/app/views/federails/server/web_finger/{find.json.jbuilder → find.jrd.jbuilder} +5 -1
- data/config/initializers/mime_types.rb +21 -0
- data/config/routes.rb +7 -3
- data/db/migrate/20200712133150_create_federails_actors.rb +3 -5
- data/db/migrate/20241002094500_add_uuids.rb +13 -0
- data/db/migrate/20241002094501_add_keypair_to_actors.rb +8 -0
- data/lib/federails/configuration.rb +11 -28
- data/lib/federails/version.rb +1 -1
- data/lib/federails.rb +13 -5
- data/lib/fediverse/inbox.rb +33 -37
- data/lib/fediverse/notifier.rb +44 -5
- data/lib/fediverse/signature.rb +49 -0
- data/lib/fediverse/webfinger.rb +31 -7
- data/lib/generators/federails/copy_client_views/USAGE +8 -0
- data/lib/generators/federails/copy_client_views/copy_client_views_generator.rb +9 -0
- data/lib/generators/federails/install/install_generator.rb +2 -2
- data/lib/generators/federails/install/templates/federails.yml +2 -0
- metadata +28 -20
- data/app/controllers/federails/server/server_controller.rb +0 -17
- data/app/helpers/federails/application_helper.rb +0 -4
- data/app/views/federails/server/actors/_actor.json.jbuilder +0 -11
- data/db/migrate/20240731145400_change_actor_entity_rel_to_polymorphic.rb +0 -11
- /data/app/views/federails/server/activities/{outbox.json.jbuilder → outbox.activitypub.jbuilder} +0 -0
- /data/app/views/federails/server/activities/{show.json.jbuilder → show.activitypub.jbuilder} +0 -0
- /data/app/views/federails/server/actors/{followers.json.jbuilder → followers.activitypub.jbuilder} +0 -0
- /data/app/views/federails/server/actors/{following.json.jbuilder → following.activitypub.jbuilder} +0 -0
- /data/app/views/federails/server/actors/{show.json.jbuilder → show.activitypub.jbuilder} +0 -0
- /data/app/views/federails/server/followings/{_following.json.jbuilder → _following.activitypub.jbuilder} +0 -0
- /data/app/views/federails/server/followings/{show.json.jbuilder → show.activitypub.jbuilder} +0 -0
- /data/app/views/federails/server/nodeinfo/{index.json.jbuilder → index.nodeinfo.jbuilder} +0 -0
- /data/app/views/federails/server/web_finger/{host_meta.xml.erb → host_meta.xrd.erb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e489a0088b05ef80d9cc112164f054a030f0cb007406cb14789372a5a52c490f
|
4
|
+
data.tar.gz: b6832c622e5c33bf85f520839604a9e6e516821097d70bb6c6acb7df92f0d4b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59e24e51b83545daabfa7dc0c878183d8b0bc14276b559b529fb5ee48183739c49b1a1987eb4a5e8d0d795dcfbe6970d6a3478691f9a13cd12c399903df61695
|
7
|
+
data.tar.gz: 01f6e7bef60bd2d15e142b7b55fecf25dd76ab67571c994c3c8aae1eba23fbcc75d02fb03775131a070b3cc6c18021d33ce91bb8645e937d94c1425a0a7433a3
|
data/README.md
CHANGED
@@ -92,7 +92,7 @@ With `routes_path = 'federation'`, routes will be:
|
|
92
92
|
|
93
93
|
Some routes can be disabled in configuration if you don't want to expose particular features:
|
94
94
|
|
95
|
-
```
|
95
|
+
```rb
|
96
96
|
Federails.configure do |config|
|
97
97
|
# Disable routing for .well-known and nodeinfo
|
98
98
|
config.enable_discovery = false
|
@@ -102,6 +102,17 @@ Federails.configure do |config|
|
|
102
102
|
end
|
103
103
|
```
|
104
104
|
|
105
|
+
#### Remote following
|
106
|
+
|
107
|
+
By default, remote follow requests (where you press a follow button on another server and get redirected home to complete the follow)
|
108
|
+
will use the built-in client paths. If you're not using the client, or want to provide your own user interface, you can set the path like this, assuming that `new_follow_url` is a valid route in your app. A `uri` query parameter template will be automatically appended, you don't need to specify that.
|
109
|
+
|
110
|
+
```rb
|
111
|
+
Federails.configure do |config|
|
112
|
+
config.remote_follow_url_method = :new_follow_url
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
105
116
|
### Migrations
|
106
117
|
|
107
118
|
Copy the migrations:
|
@@ -143,6 +154,32 @@ actor.following
|
|
143
154
|
#...
|
144
155
|
```
|
145
156
|
|
157
|
+
### Using the Federails client
|
158
|
+
|
159
|
+
Federails comes with a client, enabled by default, that provides basic views to display and interact with Federails data,
|
160
|
+
accessible on `/app` by default (changeable with the configuration option `client_routes_path`)
|
161
|
+
|
162
|
+
If it's a good starting point, it might be disabled once you made your own integration by setting `client_routes_path`
|
163
|
+
to a `nil` value.
|
164
|
+
|
165
|
+
If you want to override the client's views, copy them in your application:
|
166
|
+
|
167
|
+
```sh
|
168
|
+
rails generate federails:copy_client_views
|
169
|
+
```
|
170
|
+
|
171
|
+
## Common questions
|
172
|
+
|
173
|
+
- **I override the base controller and the links breaks in my layout**
|
174
|
+
|
175
|
+
Use `main_app.<url_helper>` for links to your application; `federails.<federails_url_helper>` for links to the Federails client.
|
176
|
+
- **I specified a custom layout and the links breaks in it**
|
177
|
+
|
178
|
+
Use `main_app.<url_helper>` for links to your application; `federails.<federails_url_helper>` for links to the Federails client.
|
179
|
+
- **I specified a custom layout and my helpers are not available**
|
180
|
+
|
181
|
+
You will have better results if you specify a `base_controller` from your application as Federails base controller is isolated from the main app and does not have access to its helpers.
|
182
|
+
|
146
183
|
## Contributing
|
147
184
|
|
148
185
|
Contributions are welcome, may it be issues, ideas, code or whatever you want to share. Please note:
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module Federails
|
2
2
|
module Client
|
3
|
-
class ActivitiesController < Federails::
|
3
|
+
class ActivitiesController < Federails::ClientController
|
4
4
|
before_action :authenticate_user!, only: [:feed]
|
5
|
-
|
5
|
+
before_action :authorize_action!
|
6
6
|
|
7
7
|
# GET /app/activities
|
8
8
|
# GET /app/activities.json
|
9
9
|
def index
|
10
10
|
@activities = policy_scope(Federails::Activity, policy_scope_class: Federails::Client::ActivityPolicy::Scope).all
|
11
|
-
@activities = @activities.where
|
11
|
+
@activities = @activities.where actor: Actor.find_param(params[:actor_id]) if params[:actor_id]
|
12
12
|
end
|
13
13
|
|
14
14
|
# GET /app/feed
|
@@ -16,6 +16,12 @@ module Federails
|
|
16
16
|
def feed
|
17
17
|
@activities = Activity.feed_for(current_user.actor)
|
18
18
|
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def authorize_action!
|
23
|
+
authorize(Federails::Activity, policy_class: Federails::Client::ActivityPolicy)
|
24
|
+
end
|
19
25
|
end
|
20
26
|
end
|
21
27
|
end
|
@@ -1,12 +1,15 @@
|
|
1
1
|
module Federails
|
2
2
|
module Client
|
3
|
-
class ActorsController < Federails::
|
3
|
+
class ActorsController < Federails::ClientController
|
4
4
|
before_action :set_actor, only: [:show]
|
5
5
|
|
6
6
|
# GET /app/actors
|
7
7
|
# GET /app/actors.json
|
8
8
|
def index
|
9
|
+
authorize Federails::Actor, policy_class: Federails::Client::ActorPolicy
|
10
|
+
|
9
11
|
@actors = policy_scope(Federails::Actor, policy_scope_class: Federails::Client::ActorPolicy::Scope).all
|
12
|
+
@actors = @actors.local if params[:local_only]
|
10
13
|
end
|
11
14
|
|
12
15
|
# GET /app/actors/1
|
@@ -25,7 +28,7 @@ module Federails
|
|
25
28
|
|
26
29
|
# Use callbacks to share common setup or constraints between actions.
|
27
30
|
def set_actor
|
28
|
-
@actor = Federails::Actor.
|
31
|
+
@actor = Federails::Actor.find_param(params[:id])
|
29
32
|
authorize @actor, policy_class: Federails::Client::ActorPolicy
|
30
33
|
end
|
31
34
|
|
@@ -1,9 +1,18 @@
|
|
1
1
|
module Federails
|
2
2
|
module Client
|
3
|
-
class FollowingsController < Federails::
|
3
|
+
class FollowingsController < Federails::ClientController
|
4
4
|
before_action :authenticate_user!
|
5
|
+
before_action :skip_authorization, only: [:new, :create]
|
5
6
|
before_action :set_following, only: [:accept, :destroy]
|
6
7
|
|
8
|
+
# GET /app/followings/new?uri={uri}
|
9
|
+
def new
|
10
|
+
# Find actor (and fetch if necessary)
|
11
|
+
actor = Actor.find_or_create_by_federation_url(params[:uri])
|
12
|
+
# Redirect to local profile page which will have a follow button on it
|
13
|
+
redirect_to federails.client_actor_url(actor)
|
14
|
+
end
|
15
|
+
|
7
16
|
# PUT /app/followings/:id/accept
|
8
17
|
# PUT /app/followings/:id/accept.json
|
9
18
|
def accept
|
@@ -32,9 +41,10 @@ module Federails
|
|
32
41
|
# POST /app/followings/follow
|
33
42
|
# POST /app/followings/follow.json
|
34
43
|
def follow
|
44
|
+
authorize Federails::Following, policy_class: Federails::Client::FollowingPolicy
|
45
|
+
|
35
46
|
begin
|
36
47
|
@following = Following.new_from_account following_account_params, actor: current_user.actor
|
37
|
-
authorize @following, policy_class: Federails::Client::FollowingPolicy
|
38
48
|
rescue ::ActiveRecord::RecordNotFound
|
39
49
|
# Renders a 422 instead of a 404
|
40
50
|
respond_to do |format|
|
@@ -62,7 +72,7 @@ module Federails
|
|
62
72
|
|
63
73
|
# Use callbacks to share common setup or constraints between actions.
|
64
74
|
def set_following
|
65
|
-
@following = Following.
|
75
|
+
@following = Following.find_param(params[:id])
|
66
76
|
authorize @following, policy_class: Federails::Client::FollowingPolicy
|
67
77
|
end
|
68
78
|
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Federails
|
2
|
+
class ClientController < Federails.configuration.base_client_controller.constantize
|
3
|
+
include Pundit::Authorization
|
4
|
+
|
5
|
+
after_action :verify_authorized
|
6
|
+
|
7
|
+
layout Federails.configuration.app_layout if Federails.configuration.app_layout
|
8
|
+
end
|
9
|
+
end
|
@@ -2,13 +2,15 @@ require 'fediverse/inbox'
|
|
2
2
|
|
3
3
|
module Federails
|
4
4
|
module Server
|
5
|
-
class ActivitiesController < ServerController
|
5
|
+
class ActivitiesController < Federails::ServerController
|
6
6
|
before_action :set_activity, only: [:show]
|
7
7
|
|
8
8
|
# GET /federation/activities
|
9
9
|
# GET /federation/actors/1/outbox.json
|
10
10
|
def outbox
|
11
|
-
|
11
|
+
authorize Federails::Activity, policy_class: Federails::Server::ActivityPolicy
|
12
|
+
|
13
|
+
@actor = Actor.find_param(params[:actor_id])
|
12
14
|
@activities = policy_scope(Federails::Activity, policy_scope_class: Federails::Server::ActivityPolicy::Scope).where(actor: @actor).order(created_at: :desc)
|
13
15
|
@total_activities = @activities.count
|
14
16
|
@activities = @activities.page(params[:page])
|
@@ -19,13 +21,15 @@ module Federails
|
|
19
21
|
|
20
22
|
# POST /federation/actors/1/inbox
|
21
23
|
def create
|
24
|
+
skip_authorization
|
25
|
+
|
22
26
|
payload = payload_from_params
|
23
|
-
return
|
27
|
+
return head :unprocessable_entity unless payload
|
24
28
|
|
25
29
|
if Fediverse::Inbox.dispatch_request(payload)
|
26
|
-
|
30
|
+
head :created
|
27
31
|
else
|
28
|
-
|
32
|
+
head :unprocessable_entity
|
29
33
|
end
|
30
34
|
end
|
31
35
|
|
@@ -33,7 +37,8 @@ module Federails
|
|
33
37
|
|
34
38
|
# Use callbacks to share common setup or constraints between actions.
|
35
39
|
def set_activity
|
36
|
-
@activity =
|
40
|
+
@activity = Actor.find_param(params[:actor_id]).activities.find_param(params[:id])
|
41
|
+
authorize @activity, policy_class: Federails::Server::ActivityPolicy
|
37
42
|
end
|
38
43
|
|
39
44
|
# Only allow a list of trusted parameters through.
|
@@ -1,17 +1,21 @@
|
|
1
1
|
module Federails
|
2
2
|
module Server
|
3
|
-
class ActorsController < ServerController
|
3
|
+
class ActorsController < Federails::ServerController
|
4
4
|
before_action :set_actor, only: [:show, :followers, :following]
|
5
5
|
|
6
6
|
# GET /federation/actors/1
|
7
7
|
# GET /federation/actors/1.json
|
8
8
|
def show; end
|
9
9
|
|
10
|
+
# GET /federation/actors/:id/followers
|
11
|
+
# GET /federation/actors/:id/followers.json
|
10
12
|
def followers
|
11
13
|
@actors = @actor.followers.order(created_at: :desc)
|
12
14
|
followings_queries
|
13
15
|
end
|
14
16
|
|
17
|
+
# GET /federation/actors/:id/followers
|
18
|
+
# GET /federation/actors/:id/followers.json
|
15
19
|
def following
|
16
20
|
@actors = @actor.follows.order(created_at: :desc)
|
17
21
|
followings_queries
|
@@ -21,7 +25,7 @@ module Federails
|
|
21
25
|
|
22
26
|
# Use callbacks to share common setup or constraints between actions.
|
23
27
|
def set_actor
|
24
|
-
@actor = Actor.
|
28
|
+
@actor = Actor.find_param(params[:id])
|
25
29
|
authorize @actor, policy_class: Federails::Server::ActorPolicy
|
26
30
|
end
|
27
31
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Federails
|
2
2
|
module Server
|
3
|
-
class FollowingsController < ServerController
|
3
|
+
class FollowingsController < Federails::ServerController
|
4
4
|
before_action :set_following, only: [:show]
|
5
5
|
|
6
6
|
# GET /federation/actors/1/followings/1.json
|
@@ -10,7 +10,8 @@ module Federails
|
|
10
10
|
|
11
11
|
# Use callbacks to share common setup or constraints between actions.
|
12
12
|
def set_following
|
13
|
-
|
13
|
+
actor = Actor.find_param(params[:actor_id])
|
14
|
+
@following = Following.find_by!(actor: actor, uuid: params[:id])
|
14
15
|
authorize @following, policy_class: Federails::Server::FollowingPolicy
|
15
16
|
end
|
16
17
|
end
|
@@ -1,21 +1,27 @@
|
|
1
1
|
module Federails
|
2
2
|
module Server
|
3
|
-
class NodeinfoController < ServerController
|
3
|
+
class NodeinfoController < Federails::ServerController
|
4
4
|
def index
|
5
|
-
|
5
|
+
skip_authorization
|
6
|
+
|
7
|
+
render formats: [:nodeinfo]
|
6
8
|
end
|
7
9
|
|
8
10
|
def show # rubocop:todo Metrics/AbcSize
|
11
|
+
skip_authorization
|
12
|
+
|
9
13
|
@total = @active_halfyear = @active_month = 0
|
14
|
+
@has_user_counts = false
|
10
15
|
Federails::Configuration.entity_types.each_value do |config|
|
11
|
-
next unless config[:
|
16
|
+
next unless (method = config[:user_count_method]&.to_sym)
|
12
17
|
|
18
|
+
@has_user_counts = true
|
13
19
|
model = config[:class]
|
14
|
-
@total += model.
|
15
|
-
@active_month += model.
|
16
|
-
@active_halfyear += model.
|
20
|
+
@total += model.send(method, nil)
|
21
|
+
@active_month += model.send(method, ((30.days.ago)...Time.current))
|
22
|
+
@active_halfyear += model.send(method, ((180.days.ago)...Time.current))
|
17
23
|
end
|
18
|
-
render formats: [:
|
24
|
+
render formats: [:nodeinfo]
|
19
25
|
end
|
20
26
|
end
|
21
27
|
end
|
@@ -2,8 +2,10 @@ require 'fediverse/webfinger'
|
|
2
2
|
|
3
3
|
module Federails
|
4
4
|
module Server
|
5
|
-
class WebFingerController < ServerController
|
5
|
+
class WebFingerController < Federails::ServerController
|
6
6
|
def find
|
7
|
+
skip_authorization
|
8
|
+
|
7
9
|
resource = params.require(:resource)
|
8
10
|
case resource
|
9
11
|
when %r{^https?://.+}
|
@@ -15,11 +17,13 @@ module Federails
|
|
15
17
|
end
|
16
18
|
raise ActiveRecord::RecordNotFound if @user.nil?
|
17
19
|
|
18
|
-
render formats: [:
|
20
|
+
render formats: [:jrd]
|
19
21
|
end
|
20
22
|
|
21
23
|
def host_meta
|
22
|
-
|
24
|
+
skip_authorization
|
25
|
+
|
26
|
+
render formats: [:xrd]
|
23
27
|
end
|
24
28
|
|
25
29
|
# TODO: complete missing endpoints
|
@@ -1,16 +1,23 @@
|
|
1
1
|
module Federails
|
2
|
-
class
|
2
|
+
class ServerController < ::ActionController::Base # rubocop:disable Rails/ApplicationController
|
3
3
|
include Pundit::Authorization
|
4
4
|
|
5
|
-
|
5
|
+
after_action :verify_authorized
|
6
|
+
|
7
|
+
protect_from_forgery with: :null_session
|
8
|
+
helper Federails::ServerHelper
|
6
9
|
|
7
|
-
|
10
|
+
rescue_from ActiveRecord::RecordNotFound, with: :error_not_found
|
8
11
|
|
9
12
|
private
|
10
13
|
|
11
14
|
def error_fallback(exception, fallback_message, status)
|
12
15
|
message = exception&.message || fallback_message
|
13
16
|
respond_to do |format|
|
17
|
+
format.jrd { head status }
|
18
|
+
format.xrd { head status }
|
19
|
+
format.activitypub { head status }
|
20
|
+
format.nodeinfo { head status }
|
14
21
|
format.json { render json: { error: message }, status: status }
|
15
22
|
format.html { raise exception }
|
16
23
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Federails
|
2
|
+
module ServerHelper
|
3
|
+
def remote_follow_url
|
4
|
+
method_name = Federails.configuration.remote_follow_url_method.to_s
|
5
|
+
if method_name.starts_with? 'federails.'
|
6
|
+
send(method_name.gsub('federails.', ''))
|
7
|
+
else
|
8
|
+
Rails.application.routes.url_helpers.send(method_name)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -2,39 +2,91 @@ module Federails
|
|
2
2
|
module Entity
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
|
-
included do
|
5
|
+
included do # rubocop:todo Metrics/BlockLength
|
6
|
+
include ActiveSupport::Callbacks
|
7
|
+
define_callbacks :followed
|
8
|
+
|
9
|
+
# Define a method that will be called after the entity receives a follow request
|
10
|
+
# @param method [Symbol] The name of the method to call, or a block that will be called directly
|
11
|
+
# @example
|
12
|
+
# after_followed :accept_follow
|
13
|
+
def self.after_followed(method)
|
14
|
+
set_callback :followed, :after, method
|
15
|
+
end
|
16
|
+
|
17
|
+
# Define a method that will be called after an activity has been received
|
18
|
+
# @param activity_type [String] The activity action to handle, e.g. 'Create'. If you specify '*', the handler will be called for any activity type.
|
19
|
+
# @param object_type [String] The object type to handle, e.g. 'Note'. If you specify '*', the handler will be called for any object type.
|
20
|
+
# @param method [Symbol] The name of the class method to call. The method will receive the complete activity payload as a parameter.
|
21
|
+
# @example
|
22
|
+
# after_activity_received 'Create', 'Note', :create_note
|
23
|
+
def self.after_activity_received(activity_type, object_type, method)
|
24
|
+
Fediverse::Inbox.register_handler(activity_type, object_type, self, method)
|
25
|
+
end
|
26
|
+
|
6
27
|
has_one :actor, class_name: 'Federails::Actor', as: :entity, dependent: :destroy
|
7
28
|
|
8
|
-
after_create :create_actor
|
29
|
+
after_create :create_actor, if: lambda {
|
30
|
+
raise("Entity not configured for #{self.class.name}. Did you use \"acts_as_federails_actor\"?") unless Federails::Configuration.entity_types.key? self.class.name
|
31
|
+
|
32
|
+
Federails::Configuration.entity_types[self.class.name][:auto_create_actors]
|
33
|
+
}
|
9
34
|
|
10
35
|
# Configures the mapping between entity and actor
|
11
36
|
# @param username_field [Symbol] The method or attribute name that returns the preferred username for ActivityPub
|
12
37
|
# @param name_field [Symbol] The method or attribute name that returns the preferred name for ActivityPub
|
13
38
|
# @param profile_url_method [Symbol] The route method name that will generate the profile URL for ActivityPub
|
14
39
|
# @param actor_type [String] The ActivityStreams Actor type for this entity; defaults to 'Person'
|
15
|
-
# @param
|
40
|
+
# @param user_count_method [Symbol] A class method to call to count active users. Leave unspecified to leave this
|
41
|
+
# entity out of user counts. Method signature should accept a single parameter which will specify a date range
|
42
|
+
# If parameter is nil, the total user count should be returned. If the parameter is specified, the number of users
|
43
|
+
# active during the time period should be returned.
|
44
|
+
# @param auto_create_actors [Boolean] Whether to automatically create an actor when the entity is created
|
16
45
|
# @example
|
17
46
|
# acts_as_federails_actor username_field: :username, name_field: :display_name, profile_url_method: :url_for, actor_type: 'Person'
|
47
|
+
# rubocop:disable Metrics/ParameterLists
|
18
48
|
def self.acts_as_federails_actor(
|
19
|
-
|
20
|
-
|
21
|
-
profile_url_method:
|
49
|
+
name_field:,
|
50
|
+
username_field:,
|
51
|
+
profile_url_method: nil,
|
22
52
|
actor_type: 'Person',
|
23
|
-
|
53
|
+
user_count_method: nil,
|
54
|
+
auto_create_actors: true
|
24
55
|
)
|
25
56
|
Federails::Configuration.register_entity(
|
26
57
|
self,
|
27
|
-
username_field:
|
28
|
-
name_field:
|
29
|
-
profile_url_method:
|
30
|
-
actor_type:
|
31
|
-
|
58
|
+
username_field: username_field,
|
59
|
+
name_field: name_field,
|
60
|
+
profile_url_method: profile_url_method,
|
61
|
+
actor_type: actor_type,
|
62
|
+
user_count_method: user_count_method,
|
63
|
+
auto_create_actors: auto_create_actors
|
32
64
|
)
|
33
65
|
end
|
66
|
+
# rubocop:enable Metrics/ParameterLists
|
34
67
|
|
35
|
-
#
|
36
|
-
#
|
37
|
-
|
68
|
+
# Add custom data to actor responses.
|
69
|
+
# Override in your own model to add extra data, which will be merged into the actor response
|
70
|
+
# generated by Federails. You can include extra `@context` for activitypub extensions and it will
|
71
|
+
# be merged with the main response context.
|
72
|
+
# @example
|
73
|
+
# def to_activitypub_object
|
74
|
+
# {
|
75
|
+
# "@context": {
|
76
|
+
# toot: "http://joinmastodon.org/ns#",
|
77
|
+
# attributionDomains: {
|
78
|
+
# "@id": "toot:attributionDomains",
|
79
|
+
# "@type": "@id"
|
80
|
+
# }
|
81
|
+
# },
|
82
|
+
# attributionDomains: [
|
83
|
+
# "example.com"
|
84
|
+
# ]
|
85
|
+
# }
|
86
|
+
# end
|
87
|
+
def to_activitypub_object
|
88
|
+
{}
|
89
|
+
end
|
38
90
|
|
39
91
|
private
|
40
92
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Federails
|
2
|
+
module HasUuid
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
before_validation :generate_uuid
|
7
|
+
validates :uuid, presence: true, uniqueness: true
|
8
|
+
|
9
|
+
def self.find_param(param)
|
10
|
+
find_by!(uuid: param)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_param
|
15
|
+
uuid
|
16
|
+
end
|
17
|
+
|
18
|
+
# Override UUID accessor to provide lazy initialization of UUIDs for old data
|
19
|
+
def uuid
|
20
|
+
if self[:uuid].blank?
|
21
|
+
generate_uuid
|
22
|
+
save!
|
23
|
+
end
|
24
|
+
self[:uuid]
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def generate_uuid
|
30
|
+
return if self[:uuid].present?
|
31
|
+
|
32
|
+
(self.uuid = SecureRandom.uuid) while self[:uuid].blank? || self.class.exists?(uuid: self[:uuid])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Federails
|
2
2
|
class Activity < ApplicationRecord
|
3
|
+
include Federails::HasUuid
|
4
|
+
|
3
5
|
belongs_to :entity, polymorphic: true
|
4
6
|
belongs_to :actor
|
5
7
|
|
@@ -13,22 +15,15 @@ module Federails
|
|
13
15
|
|
14
16
|
after_create_commit :post_to_inboxes
|
15
17
|
|
16
|
-
def recipients
|
18
|
+
def recipients
|
17
19
|
return [] unless actor.local?
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
actors.push(*actor.followers) if entity_type == 'Note'
|
25
|
-
when 'Accept'
|
26
|
-
actors.push(entity.actor) if entity_type == 'Federails::Following'
|
27
|
-
when 'Undo'
|
28
|
-
actors.push(entity.target_actor) if entity_type == 'Federails::Following'
|
21
|
+
case entity_type
|
22
|
+
when 'Federails::Following'
|
23
|
+
[(action == 'Accept' ? entity.actor : entity.target_actor)]
|
24
|
+
else
|
25
|
+
actor.followers
|
29
26
|
end
|
30
|
-
|
31
|
-
actors
|
32
27
|
end
|
33
28
|
|
34
29
|
private
|
@@ -3,6 +3,8 @@ require 'fediverse/webfinger'
|
|
3
3
|
|
4
4
|
module Federails
|
5
5
|
class Actor < ApplicationRecord # rubocop:disable Metrics/ClassLength
|
6
|
+
include Federails::HasUuid
|
7
|
+
|
6
8
|
validates :federated_url, presence: { unless: :entity }, uniqueness: { unless: :entity }
|
7
9
|
validates :username, presence: { unless: :entity }
|
8
10
|
validates :server, presence: { unless: :entity }
|
@@ -89,7 +91,9 @@ module Federails
|
|
89
91
|
end
|
90
92
|
|
91
93
|
def entity_configuration
|
92
|
-
Federails::Configuration.entity_types
|
94
|
+
raise("Entity not configured for #{entity_type}. Did you use \"acts_as_federails_actor\"?") unless Federails::Configuration.entity_types.key? entity_type
|
95
|
+
|
96
|
+
Federails::Configuration.entity_types[entity_type]
|
93
97
|
end
|
94
98
|
|
95
99
|
class << self
|
@@ -112,7 +116,7 @@ module Federails
|
|
112
116
|
|
113
117
|
def find_by_federation_url(federated_url)
|
114
118
|
local_route = Utils::Host.local_route federated_url
|
115
|
-
return
|
119
|
+
return find_param(local_route[:id]) if local_route && local_route[:controller] == 'federails/server/actors' && local_route[:action] == 'show'
|
116
120
|
|
117
121
|
actor = find_by federated_url: federated_url
|
118
122
|
return actor if actor
|
@@ -148,5 +152,40 @@ module Federails
|
|
148
152
|
end
|
149
153
|
end
|
150
154
|
end
|
155
|
+
|
156
|
+
def public_key
|
157
|
+
ensure_key_pair_exists!
|
158
|
+
self[:public_key]
|
159
|
+
end
|
160
|
+
|
161
|
+
def private_key
|
162
|
+
ensure_key_pair_exists!
|
163
|
+
self[:private_key]
|
164
|
+
end
|
165
|
+
|
166
|
+
def key_id
|
167
|
+
"#{federated_url}#main-key"
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def ensure_key_pair_exists!
|
173
|
+
return if self[:private_key].present? || !local?
|
174
|
+
|
175
|
+
update!(generate_key_pair)
|
176
|
+
end
|
177
|
+
|
178
|
+
def generate_key_pair
|
179
|
+
rsa_key = OpenSSL::PKey::RSA.new 2048
|
180
|
+
cipher = OpenSSL::Cipher.new('AES-128-CBC')
|
181
|
+
{
|
182
|
+
private_key: if Rails.application.credentials.secret_key_base
|
183
|
+
rsa_key.to_pem(cipher, Rails.application.credentials.secret_key_base)
|
184
|
+
else
|
185
|
+
rsa_key.to_pem
|
186
|
+
end,
|
187
|
+
public_key: rsa_key.public_key.to_pem,
|
188
|
+
}
|
189
|
+
end
|
151
190
|
end
|
152
191
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Federails
|
2
2
|
class Following < ApplicationRecord
|
3
|
+
include Federails::HasUuid
|
4
|
+
|
3
5
|
enum status: { pending: 0, accepted: 1 }
|
4
6
|
|
5
7
|
validates :target_actor_id, uniqueness: { scope: [:actor_id, :target_actor_id] }
|
@@ -9,6 +11,7 @@ module Federails
|
|
9
11
|
# FIXME: Handle this with something like undelete
|
10
12
|
has_many :activities, as: :entity, dependent: :destroy
|
11
13
|
|
14
|
+
after_create :after_follow
|
12
15
|
after_create :create_activity
|
13
16
|
after_destroy :destroy_activity
|
14
17
|
|
@@ -32,6 +35,12 @@ module Federails
|
|
32
35
|
|
33
36
|
private
|
34
37
|
|
38
|
+
def after_follow
|
39
|
+
target_actor&.entity&.run_callbacks :followed, :after do
|
40
|
+
self
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
35
44
|
def create_activity
|
36
45
|
Activity.create! actor: actor, action: 'Create', entity: self
|
37
46
|
end
|