Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
interfect
funkwhale
Commits
0b2fe843
Verified
Commit
0b2fe843
authored
Apr 10, 2018
by
Eliot Berriot
Browse files
Removed too complex FollowRequest model, we now use an aproved field on Follow
parent
c97db31c
Changes
14
Hide whitespace changes
Inline
Side-by-side
api/funkwhale_api/federation/activity.py
View file @
0b2fe843
...
...
@@ -6,8 +6,10 @@ import uuid
from
django.conf
import
settings
from
funkwhale_api.common
import
session
from
funkwhale_api.common
import
utils
as
funkwhale_utils
from
.
import
models
from
.
import
serializers
from
.
import
signing
logger
=
logging
.
getLogger
(
__name__
)
...
...
@@ -85,66 +87,9 @@ def deliver(activity, on_behalf_of, to=[]):
logger
.
debug
(
'Remote answered with %s'
,
response
.
status_code
)
def
get_follow
(
follow_id
,
follower
,
followed
):
return
{
'@context'
:
[
'https://www.w3.org/ns/activitystreams'
,
'https://w3id.org/security/v1'
,
{}
],
'actor'
:
follower
.
url
,
'id'
:
follower
.
url
+
'#follows/{}'
.
format
(
follow_id
),
'object'
:
followed
.
url
,
'type'
:
'Follow'
}
def
get_undo
(
id
,
actor
,
object
):
return
{
'@context'
:
[
'https://www.w3.org/ns/activitystreams'
,
'https://w3id.org/security/v1'
,
{}
],
'type'
:
'Undo'
,
'id'
:
id
+
'/undo'
,
'actor'
:
actor
.
url
,
'object'
:
object
,
}
def
get_accept_follow
(
accept_id
,
accept_actor
,
follow
,
follow_actor
):
return
{
"@context"
:
[
"https://www.w3.org/ns/activitystreams"
,
"https://w3id.org/security/v1"
,
{}
],
"id"
:
accept_actor
.
url
+
'#accepts/follows/{}'
.
format
(
accept_id
),
"type"
:
"Accept"
,
"actor"
:
accept_actor
.
url
,
"object"
:
{
"id"
:
follow
[
'id'
],
"type"
:
"Follow"
,
"actor"
:
follow_actor
.
url
,
"object"
:
accept_actor
.
url
},
}
def
accept_follow
(
target
,
follow
,
actor
):
accept_uuid
=
uuid
.
uuid4
()
accept
=
get_accept_follow
(
accept_id
=
accept_uuid
,
accept_actor
=
target
,
follow
=
follow
,
follow_actor
=
actor
)
def
accept_follow
(
follow
):
serializer
=
serializers
.
AcceptFollowSerializer
(
follow
)
deliver
(
accept
,
to
=
[
actor
.
url
],
on_behalf_of
=
target
)
return
models
.
Follow
.
objects
.
get_or_create
(
actor
=
actor
,
target
=
target
,
)
serializer
.
data
,
to
=
[
follow
.
actor
.
url
],
on_behalf_of
=
follow
.
target
)
api/funkwhale_api/federation/actors.py
View file @
0b2fe843
...
...
@@ -153,24 +153,32 @@ class SystemActor(object):
def
handle_follow
(
self
,
ac
,
sender
):
system_actor
=
self
.
get_actor_instance
()
if
self
.
manually_approves_followers
:
fr
,
created
=
models
.
FollowRequest
.
objects
.
get_or_create
(
actor
=
sender
,
target
=
system_actor
,
approved
=
None
,
)
return
fr
serializer
=
serializers
.
FollowSerializer
(
data
=
ac
,
context
=
{
'follow_actor'
:
sender
})
if
not
serializer
.
is_valid
():
return
logger
.
info
(
'Invalid follow payload'
)
approved
=
True
if
not
self
.
manually_approves_followers
else
None
follow
=
serializer
.
save
(
approved
=
approved
)
if
follow
.
approved
:
return
activity
.
accept_follow
(
follow
)
def
handle_accept
(
self
,
ac
,
sender
):
system_actor
=
self
.
get_actor_instance
()
serializer
=
serializers
.
AcceptFollowSerializer
(
data
=
ac
,
context
=
{
'follow_target'
:
sender
,
'follow_actor'
:
system_actor
})
if
not
serializer
.
is_valid
(
raise_exception
=
True
):
return
logger
.
info
(
'Received invalid payload'
)
return
activity
.
accept_follow
(
system_actor
,
ac
,
sender
)
serializer
.
save
()
def
handle_undo_follow
(
self
,
ac
,
sender
):
actor
=
self
.
get_actor_instance
()
models
.
Follow
.
objects
.
filter
(
actor
=
sender
,
target
=
actor
,
).
delete
()
system_actor
=
self
.
get_actor_instance
()
serializer
=
serializers
.
UndoFollowSerializer
(
data
=
ac
,
context
=
{
'actor'
:
sender
,
'target'
:
system_actor
})
if
not
serializer
.
is_valid
():
return
logger
.
info
(
'Received invalid payload'
)
serializer
.
save
()
def
handle_undo
(
self
,
ac
,
sender
):
if
ac
[
'object'
][
'type'
]
!=
'Follow'
:
...
...
@@ -206,20 +214,6 @@ class LibraryActor(SystemActor):
def
manually_approves_followers
(
self
):
return
settings
.
FEDERATION_MUSIC_NEEDS_APPROVAL
def
handle_follow
(
self
,
ac
,
sender
):
system_actor
=
self
.
get_actor_instance
()
if
self
.
manually_approves_followers
:
fr
,
created
=
models
.
FollowRequest
.
objects
.
get_or_create
(
actor
=
sender
,
target
=
system_actor
,
approved
=
None
,
)
return
fr
return
activity
.
accept_follow
(
system_actor
,
ac
,
sender
)
@
transaction
.
atomic
def
handle_create
(
self
,
ac
,
sender
):
try
:
...
...
@@ -360,15 +354,15 @@ class TestActor(SystemActor):
super
().
handle_follow
(
ac
,
sender
)
# also, we follow back
test_actor
=
self
.
get_actor_instance
()
follow_
uuid
=
uuid
.
uuid4
()
follow
=
activity
.
get_follow
(
follow_id
=
follow_uuid
,
follower
=
test_actor
,
followed
=
sender
)
follow_
back
=
models
.
Follow
.
objects
.
get_or_create
(
actor
=
test_actor
,
target
=
sender
,
approved
=
None
,
)[
0
]
activity
.
deliver
(
follow
,
to
=
[
ac
[
'actor'
]
],
on_behalf_of
=
test_
actor
)
serializers
.
FollowSerializer
(
follow_back
).
data
,
to
=
[
follow_back
.
target
.
url
],
on_behalf_of
=
follow_back
.
actor
)
def
handle_undo_follow
(
self
,
ac
,
sender
):
super
().
handle_undo_follow
(
ac
,
sender
)
...
...
@@ -381,11 +375,7 @@ class TestActor(SystemActor):
)
except
models
.
Follow
.
DoesNotExist
:
return
undo
=
activity
.
get_undo
(
id
=
follow
.
get_federation_url
(),
actor
=
actor
,
object
=
serializers
.
FollowSerializer
(
follow
).
data
,
)
undo
=
serializers
.
UndoFollowSerializer
(
follow
).
data
follow
.
delete
()
activity
.
deliver
(
undo
,
...
...
api/funkwhale_api/federation/admin.py
View file @
0b2fe843
...
...
@@ -23,24 +23,13 @@ class FollowAdmin(admin.ModelAdmin):
list_display
=
[
'actor'
,
'target'
,
'approved'
,
'creation_date'
]
search_fields
=
[
'actor__url'
,
'target__url'
]
list_select_related
=
True
@
admin
.
register
(
models
.
FollowRequest
)
class
FollowRequestAdmin
(
admin
.
ModelAdmin
):
list_display
=
[
'actor'
,
'target'
,
'creation_date'
,
'approved'
]
search_fields
=
[
'actor__url'
,
'target__url'
]
list_filter
=
[
'approved'
]
search_fields
=
[
'actor__url'
,
'target__url'
]
list_select_related
=
True
...
...
api/funkwhale_api/federation/factories.py
View file @
0b2fe843
...
...
@@ -113,15 +113,6 @@ class FollowFactory(factory.DjangoModelFactory):
)
@
registry
.
register
class
FollowRequestFactory
(
factory
.
DjangoModelFactory
):
target
=
factory
.
SubFactory
(
ActorFactory
)
actor
=
factory
.
SubFactory
(
ActorFactory
)
class
Meta
:
model
=
models
.
FollowRequest
@
registry
.
register
class
LibraryFactory
(
factory
.
DjangoModelFactory
):
actor
=
factory
.
SubFactory
(
ActorFactory
)
...
...
api/funkwhale_api/federation/library.py
View file @
0b2fe843
...
...
@@ -38,25 +38,21 @@ def scan_from_account_name(account_name):
actor__domain
=
domain
,
actor__preferred_username
=
username
).
select_related
(
'actor'
).
first
()
follow_request
=
None
if
library
:
data
[
'local'
][
'following'
]
=
True
data
[
'local'
][
'awaiting_approval'
]
=
True
else
:
follow_request
=
models
.
FollowRequest
.
objects
.
filter
(
data
[
'local'
]
=
{
'following'
:
False
,
'awaiting_approval'
:
False
,
}
try
:
follow
=
models
.
Follow
.
objects
.
get
(
target__preferred_username
=
username
,
target__domain
=
username
,
actor
=
system_library
,
).
first
()
data
[
'local'
]
=
{
'following'
:
False
,
'awaiting_approval'
:
False
,
}
if
follow_request
:
data
[
'awaiting_approval'
]
=
follow_request
.
approved
is
None
)
data
[
'local'
][
'awaiting_approval'
]
=
not
bool
(
follow
.
approved
)
data
[
'local'
][
'following'
]
=
True
except
models
.
Follow
.
DoesNotExist
:
pass
follow_request
=
models
.
Follow
try
:
data
[
'webfinger'
]
=
webfinger
.
get_resource
(
'acct:{}'
.
format
(
account_name
))
...
...
api/funkwhale_api/federation/migrations/0004_auto_20180410_1624.py
0 → 100644
View file @
0b2fe843
# Generated by Django 2.0.3 on 2018-04-10 16:24
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'federation'
,
'0003_auto_20180407_1010'
),
]
operations
=
[
migrations
.
RemoveField
(
model_name
=
'followrequest'
,
name
=
'actor'
,
),
migrations
.
RemoveField
(
model_name
=
'followrequest'
,
name
=
'target'
,
),
migrations
.
AddField
(
model_name
=
'follow'
,
name
=
'approved'
,
field
=
models
.
NullBooleanField
(
default
=
None
),
),
migrations
.
DeleteModel
(
name
=
'FollowRequest'
,
),
]
api/funkwhale_api/federation/models.py
View file @
0b2fe843
...
...
@@ -109,6 +109,7 @@ class Follow(models.Model):
creation_date
=
models
.
DateTimeField
(
default
=
timezone
.
now
)
modification_date
=
models
.
DateTimeField
(
auto_now
=
True
)
approved
=
models
.
NullBooleanField
(
default
=
None
)
class
Meta
:
unique_together
=
[
'actor'
,
'target'
]
...
...
@@ -117,49 +118,6 @@ class Follow(models.Model):
return
'{}#follows/{}'
.
format
(
self
.
actor
.
url
,
self
.
uuid
)
class
FollowRequest
(
models
.
Model
):
uuid
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
unique
=
True
)
actor
=
models
.
ForeignKey
(
Actor
,
related_name
=
'emmited_follow_requests'
,
on_delete
=
models
.
CASCADE
,
)
target
=
models
.
ForeignKey
(
Actor
,
related_name
=
'received_follow_requests'
,
on_delete
=
models
.
CASCADE
,
)
creation_date
=
models
.
DateTimeField
(
default
=
timezone
.
now
)
modification_date
=
models
.
DateTimeField
(
auto_now
=
True
)
approved
=
models
.
NullBooleanField
(
default
=
None
)
def
approve
(
self
):
from
.
import
activity
from
.
import
serializers
self
.
approved
=
True
self
.
save
(
update_fields
=
[
'approved'
])
Follow
.
objects
.
get_or_create
(
target
=
self
.
target
,
actor
=
self
.
actor
)
if
self
.
target
.
is_local
:
follow
=
{
'@context'
:
serializers
.
AP_CONTEXT
,
'actor'
:
self
.
actor
.
url
,
'id'
:
self
.
actor
.
url
+
'#follows/{}'
.
format
(
uuid
.
uuid4
()),
'object'
:
self
.
target
.
url
,
'type'
:
'Follow'
}
activity
.
accept_follow
(
self
.
target
,
follow
,
self
.
actor
)
def
refuse
(
self
):
self
.
approved
=
False
self
.
save
(
update_fields
=
[
'approved'
])
class
Library
(
models
.
Model
):
creation_date
=
models
.
DateTimeField
(
default
=
timezone
.
now
)
modification_date
=
models
.
DateTimeField
(
...
...
api/funkwhale_api/federation/serializers.py
View file @
0b2fe843
...
...
@@ -121,28 +121,132 @@ class LibraryActorSerializer(ActorSerializer):
return
validated_data
class
FollowSerializer
(
serializers
.
ModelSerializer
):
# left maps to activitypub fields, right to our internal models
id
=
serializers
.
URLField
(
source
=
'get_federation_url'
)
object
=
serializers
.
URLField
(
source
=
'target.url'
)
actor
=
serializers
.
URLField
(
source
=
'actor.url'
)
type
=
serializers
.
CharField
(
source
=
'ap_type'
)
class
FollowSerializer
(
serializers
.
Serializer
):
id
=
serializers
.
URLField
()
object
=
serializers
.
URLField
()
actor
=
serializers
.
URLField
()
type
=
serializers
.
ChoiceField
(
choices
=
[
'Follow'
])
class
Meta
:
model
=
models
.
Actor
fields
=
[
'id'
,
'object'
,
'actor'
,
'type'
]
def
validate_object
(
self
,
v
):
expected
=
self
.
context
.
get
(
'follow_target'
)
if
expected
and
expected
.
url
!=
v
:
raise
serializers
.
ValidationError
(
'Invalid target'
)
try
:
return
models
.
Actor
.
objects
.
get
(
url
=
v
)
except
models
.
Actor
.
DoesNotExist
:
raise
serializers
.
ValidationError
(
'Target not found'
)
def
validate_actor
(
self
,
v
):
expected
=
self
.
context
.
get
(
'follow_actor'
)
if
expected
and
expected
.
url
!=
v
:
raise
serializers
.
ValidationError
(
'Invalid actor'
)
try
:
return
models
.
Actor
.
objects
.
get
(
url
=
v
)
except
models
.
Actor
.
DoesNotExist
:
raise
serializers
.
ValidationError
(
'Actor not found'
)
def
save
(
self
,
**
kwargs
):
return
models
.
Follow
.
objects
.
get_or_create
(
actor
=
self
.
validated_data
[
'actor'
],
target
=
self
.
validated_data
[
'object'
],
**
kwargs
,
)[
0
]
def
to_representation
(
self
,
instance
):
ret
=
super
().
to_representation
(
instance
)
ret
[
'@context'
]
=
AP_CONTEXT
return
{
'@context'
:
AP_CONTEXT
,
'actor'
:
instance
.
actor
.
url
,
'id'
:
instance
.
get_federation_url
(),
'object'
:
instance
.
target
.
url
,
'type'
:
'Follow'
}
return
ret
class
AcceptFollowSerializer
(
serializers
.
Serializer
):
id
=
serializers
.
URLField
()
actor
=
serializers
.
URLField
()
object
=
FollowSerializer
()
type
=
serializers
.
ChoiceField
(
choices
=
[
'Accept'
])
def
validate_actor
(
self
,
v
):
expected
=
self
.
context
.
get
(
'follow_target'
)
if
expected
and
expected
.
url
!=
v
:
raise
serializers
.
ValidationError
(
'Invalid actor'
)
try
:
return
models
.
Actor
.
objects
.
get
(
url
=
v
)
except
models
.
Actor
.
DoesNotExist
:
raise
serializers
.
ValidationError
(
'Actor not found'
)
def
validate
(
self
,
validated_data
):
# we ensure the accept actor actually match the follow target
if
validated_data
[
'actor'
]
!=
validated_data
[
'object'
][
'object'
]:
raise
serializers
.
ValidationError
(
'Actor mismatch'
)
try
:
validated_data
[
'follow'
]
=
models
.
Follow
.
objects
.
filter
(
target
=
validated_data
[
'actor'
],
actor
=
validated_data
[
'object'
][
'actor'
]
).
exclude
(
approved
=
True
).
get
()
except
models
.
Follow
.
DoesNotExist
:
raise
serializers
.
ValidationError
(
'No follow to accept'
)
return
validated_data
def
to_representation
(
self
,
instance
):
return
{
"@context"
:
AP_CONTEXT
,
"id"
:
instance
.
get_federation_url
()
+
'/accept'
,
"type"
:
"Accept"
,
"actor"
:
instance
.
target
.
url
,
"object"
:
FollowSerializer
(
instance
).
data
}
def
save
(
self
):
self
.
validated_data
[
'follow'
].
approved
=
True
self
.
validated_data
[
'follow'
].
save
()
return
self
.
validated_data
[
'follow'
]
class
UndoFollowSerializer
(
serializers
.
Serializer
):
id
=
serializers
.
URLField
()
actor
=
serializers
.
URLField
()
object
=
FollowSerializer
()
type
=
serializers
.
ChoiceField
(
choices
=
[
'Undo'
])
def
validate_actor
(
self
,
v
):
expected
=
self
.
context
.
get
(
'follow_target'
)
if
expected
and
expected
.
url
!=
v
:
raise
serializers
.
ValidationError
(
'Invalid actor'
)
try
:
return
models
.
Actor
.
objects
.
get
(
url
=
v
)
except
models
.
Actor
.
DoesNotExist
:
raise
serializers
.
ValidationError
(
'Actor not found'
)
def
validate
(
self
,
validated_data
):
# we ensure the accept actor actually match the follow actor
if
validated_data
[
'actor'
]
!=
validated_data
[
'object'
][
'actor'
]:
raise
serializers
.
ValidationError
(
'Actor mismatch'
)
try
:
validated_data
[
'follow'
]
=
models
.
Follow
.
objects
.
filter
(
actor
=
validated_data
[
'actor'
],
target
=
validated_data
[
'object'
][
'object'
]
).
get
()
except
models
.
Follow
.
DoesNotExist
:
raise
serializers
.
ValidationError
(
'No follow to remove'
)
return
validated_data
def
to_representation
(
self
,
instance
):
return
{
"@context"
:
AP_CONTEXT
,
"id"
:
instance
.
get_federation_url
()
+
'/undo'
,
"type"
:
"Undo"
,
"actor"
:
instance
.
actor
.
url
,
"object"
:
FollowSerializer
(
instance
).
data
}
def
save
(
self
):
self
.
validated_data
[
'follow'
].
delete
()
class
ActorWebfingerSerializer
(
serializers
.
Serializer
):
subject
=
serializers
.
CharField
()
aliases
=
serializers
.
ListField
(
child
=
serializers
.
URLField
())
...
...
api/funkwhale_api/federation/views.py
View file @
0b2fe843
from
django
import
forms
from
django.conf
import
settings
from
django.core
import
paginator
from
django.db
import
transaction
from
django.http
import
HttpResponse
from
django.urls
import
reverse
...
...
@@ -9,9 +10,12 @@ from rest_framework import response
from
rest_framework
import
views
from
rest_framework
import
viewsets
from
rest_framework.decorators
import
list_route
,
detail_route
from
rest_framework.serializers
import
ValidationError
from
funkwhale_api.common
import
utils
as
funkwhale_utils
from
funkwhale_api.music.models
import
TrackFile
from
.
import
activity
from
.
import
actors
from
.
import
authentication
from
.
import
library
...
...
@@ -172,3 +176,29 @@ class LibraryViewSet(viewsets.GenericViewSet):
data
=
library
.
scan_from_account_name
(
account
)
return
response
.
Response
(
data
)
@
transaction
.
atomic
def
create
(
self
,
request
,
*
args
,
**
kwargs
):
try
:
actor_url
=
request
.
data
[
'actor_url'
]
except
KeyError
:
raise
ValidationError
(
'Missing actor_url'
)
try
:
actor
=
actors
.
get_actor
(
actor_url
)
library_data
=
library
.
get_library_data
(
actor
.
url
)
except
Exception
as
e
:
raise
ValidationError
(
'Error while fetching actor and library'
)
library_actor
=
actors
.
SYSTEM_ACTORS
[
'library'
].
get_actor_instance
()
follow
,
created
=
models
.
Follow
.
objects
.
get_or_create
(
actor
=
library_actor
,
target
=
actor
,
)
serializer
=
serializers
.
FollowSerializer
(
follow
)
activity
.
deliver
(
serializer
.
data
,
on_behalf_of
=
library_actor
,
to
=
[
actor
.
url
]
)
return
response
.
Response
({},
status
=
201
)
api/tests/federation/test_activity.py
View file @
0b2fe843
import
uuid
from
funkwhale_api.federation
import
activity
from
funkwhale_api.federation
import
serializers
def
test_deliver
(
nodb_factories
,
r_mock
,
mocker
):
...
...
@@ -38,37 +39,9 @@ def test_deliver(nodb_factories, r_mock, mocker):
def
test_accept_follow
(
mocker
,
factories
):
deliver
=
mocker
.
patch
(
'funkwhale_api.federation.activity.deliver'
)
actor
=
factories
[
'federation.Actor'
]()
target
=
factories
[
'federation.Actor'
](
local
=
True
)
follow
=
{
'actor'
:
actor
.
url
,
'type'
:
'Follow'
,
'id'
:
'http://test.federation/user#follows/267'
,
'object'
:
target
.
url
,
}
uid
=
uuid
.
uuid4
()
mocker
.
patch
(
'uuid.uuid4'
,
return_value
=
uid
)
expected_accept
=
{
"@context"
:
[
"https://www.w3.org/ns/activitystreams"
,
"https://w3id.org/security/v1"
,
{}
],
"id"
:
target
.
url
+
'#accepts/follows/{}'
.
format
(
uid
),
"type"
:
"Accept"
,
"actor"
:
target
.
url
,
"object"
:
{
"id"
:
follow
[
'id'
],
"type"
:
"Follow"
,
"actor"
:
actor