federails 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|