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
097707de
Verified
Commit
097707de
authored
Apr 08, 2018
by
Eliot Berriot
Browse files
Added remote library scanning logic end endpoint
parent
836e8139
Changes
8
Hide whitespace changes
Inline
Side-by-side
api/config/api_urls.py
View file @
097707de
...
...
@@ -32,6 +32,10 @@ v1_patterns += [
include
(
(
'funkwhale_api.instance.urls'
,
'instance'
),
namespace
=
'instance'
)),
url
(
r
'^federation/'
,
include
(
(
'funkwhale_api.federation.api_urls'
,
'federation'
),
namespace
=
'federation'
)),
url
(
r
'^providers/'
,
include
(
(
'funkwhale_api.providers.urls'
,
'providers'
),
...
...
api/funkwhale_api/federation/api_urls.py
0 → 100644
View file @
097707de
from
rest_framework
import
routers
from
.
import
views
router
=
routers
.
SimpleRouter
()
router
.
register
(
r
'libraries'
,
views
.
LibraryViewSet
,
'libraries'
)
urlpatterns
=
router
.
urls
api/funkwhale_api/federation/library.py
0 → 100644
View file @
097707de
import
requests
from
funkwhale_api.common
import
session
from
.
import
actors
from
.
import
serializers
from
.
import
signing
from
.
import
webfinger
def
scan_from_account_name
(
account_name
):
"""
Given an account name such as library@test.library, will:
1. Perform the webfinger lookup
2. Perform the actor lookup
3. Perform the library's collection lookup
and return corresponding data in a dictionary.
"""
data
=
{}
try
:
data
[
'webfinger'
]
=
webfinger
.
get_resource
(
'acct:{}'
.
format
(
account_name
))
except
requests
.
ConnectionError
:
return
{
'webfinger'
:
{
'errors'
:
[
'This webfinger resource is not reachable'
]
}
}
except
requests
.
HTTPError
as
e
:
return
{
'webfinger'
:
{
'errors'
:
[
'Error {} during webfinger request'
.
format
(
e
.
response
.
status_code
)]
}
}
try
:
data
[
'actor'
]
=
actors
.
get_actor_data
(
data
[
'webfinger'
][
'actor_url'
])
except
requests
.
ConnectionError
:
data
[
'actor'
]
=
{
'errors'
:
[
'This actor is not reachable'
]
}
return
data
except
requests
.
HTTPError
as
e
:
data
[
'actor'
]
=
{
'errors'
:
[
'Error {} during actor request'
.
format
(
e
.
response
.
status_code
)]
}
return
data
serializer
=
serializers
.
LibraryActorSerializer
(
data
=
data
[
'actor'
])
serializer
.
is_valid
(
raise_exception
=
True
)
data
[
'library'
]
=
get_library_data
(
serializer
.
validated_data
[
'library_url'
])
return
data
def
get_library_data
(
library_url
):
actor
=
actors
.
SYSTEM_ACTORS
[
'library'
].
get_actor_instance
()
auth
=
signing
.
get_auth
(
actor
.
private_key
,
actor
.
private_key_id
)
try
:
response
=
session
.
get_session
().
get
(
library_url
,
auth
=
auth
,
timeout
=
5
,
headers
=
{
'Content-Type'
:
'application/activity+json'
}
)
except
requests
.
ConnectionError
:
return
{
'errors'
:
[
'This library is not reachable'
]
}
scode
=
response
.
status_code
if
scode
==
401
:
return
{
'errors'
:
[
'This library requires authentication'
]
}
elif
scode
==
403
:
return
{
'errors'
:
[
'Permission denied while scanning library'
]
}
elif
scode
>=
400
:
return
{
'errors'
:
[
'Error {} while fetching the library'
.
format
(
scode
)]
}
serializer
=
serializers
.
PaginatedCollectionSerializer
(
data
=
response
.
json
(),
)
serializer
.
is_valid
(
raise_exception
=
True
)
return
serializer
.
validated_data
api/funkwhale_api/federation/serializers.py
View file @
097707de
...
...
@@ -27,8 +27,10 @@ class ActorSerializer(serializers.ModelSerializer):
id
=
serializers
.
URLField
(
source
=
'url'
)
outbox
=
serializers
.
URLField
(
source
=
'outbox_url'
)
inbox
=
serializers
.
URLField
(
source
=
'inbox_url'
)
following
=
serializers
.
URLField
(
source
=
'following_url'
,
required
=
False
)
followers
=
serializers
.
URLField
(
source
=
'followers_url'
,
required
=
False
)
following
=
serializers
.
URLField
(
source
=
'following_url'
,
required
=
False
,
allow_null
=
True
)
followers
=
serializers
.
URLField
(
source
=
'followers_url'
,
required
=
False
,
allow_null
=
True
)
preferredUsername
=
serializers
.
CharField
(
source
=
'preferred_username'
,
required
=
False
)
publicKey
=
serializers
.
JSONField
(
source
=
'public_key'
,
required
=
False
)
...
...
@@ -94,6 +96,31 @@ class ActorSerializer(serializers.ModelSerializer):
return
value
[:
500
]
class
LibraryActorSerializer
(
ActorSerializer
):
url
=
serializers
.
ListField
(
child
=
serializers
.
JSONField
())
class
Meta
(
ActorSerializer
.
Meta
):
fields
=
ActorSerializer
.
Meta
.
fields
+
[
'url'
]
def
validate
(
self
,
validated_data
):
try
:
urls
=
validated_data
[
'url'
]
except
KeyError
:
raise
serializers
.
ValidationError
(
'Missing URL field'
)
for
u
in
urls
:
try
:
if
u
[
'name'
]
!=
'library'
:
continue
validated_data
[
'library_url'
]
=
u
[
'href'
]
break
except
KeyError
:
continue
return
validated_data
class
FollowSerializer
(
serializers
.
ModelSerializer
):
# left maps to activitypub fields, right to our internal models
id
=
serializers
.
URLField
(
source
=
'get_federation_url'
)
...
...
@@ -226,7 +253,6 @@ OBJECT_SERIALIZERS = {
class
PaginatedCollectionSerializer
(
serializers
.
Serializer
):
type
=
serializers
.
ChoiceField
(
choices
=
[
'Collection'
])
totalItems
=
serializers
.
IntegerField
(
min_value
=
0
)
items
=
serializers
.
ListField
()
actor
=
serializers
.
URLField
()
id
=
serializers
.
URLField
()
...
...
api/funkwhale_api/federation/views.py
View file @
097707de
...
...
@@ -4,15 +4,18 @@ from django.core import paginator
from
django.http
import
HttpResponse
from
django.urls
import
reverse
from
rest_framework
import
viewsets
from
rest_framework
import
views
from
rest_framework
import
permissions
as
rest_permissions
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
funkwhale_api.music.models
import
TrackFile
from
.
import
actors
from
.
import
authentication
from
.
import
library
from
.
import
models
from
.
import
permissions
from
.
import
renderers
from
.
import
serializers
...
...
@@ -154,3 +157,18 @@ class MusicFilesViewSet(FederationMixin, viewsets.GenericViewSet):
return
response
.
Response
(
status
=
404
)
return
response
.
Response
(
data
)
class
LibraryViewSet
(
viewsets
.
GenericViewSet
):
permission_classes
=
[
rest_permissions
.
DjangoModelPermissions
]
queryset
=
models
.
Library
.
objects
.
all
()
@
list_route
(
methods
=
[
'get'
])
def
scan
(
self
,
request
,
*
args
,
**
kwargs
):
account
=
request
.
GET
.
get
(
'account'
)
if
not
account
:
return
response
.
Response
(
{
'account'
:
'This field is mandatory'
},
status
=
400
)
data
=
library
.
scan_from_account_name
(
account
)
return
response
.
Response
(
data
)
api/funkwhale_api/federation/webfinger.py
View file @
097707de
...
...
@@ -36,7 +36,7 @@ def clean_acct(acct_string, ensure_local=True):
raise
forms
.
ValidationError
(
'Invalid hostname {}'
.
format
(
hostname
))
if
username
not
in
actors
.
SYSTEM_ACTORS
:
if
ensure_local
and
username
not
in
actors
.
SYSTEM_ACTORS
:
raise
forms
.
ValidationError
(
'Invalid username'
)
return
username
,
hostname
...
...
api/tests/federation/test_library.py
0 → 100644
View file @
097707de
from
funkwhale_api.federation
import
library
from
funkwhale_api.federation
import
serializers
def
test_library_scan_from_account_name
(
mocker
,
factories
):
actor
=
factories
[
'federation.Actor'
](
preferred_username
=
'library'
,
domain
=
'test.library'
)
get_resource_result
=
{
'actor_url'
:
actor
.
url
}
get_resource
=
mocker
.
patch
(
'funkwhale_api.federation.webfinger.get_resource'
,
return_value
=
get_resource_result
)
actor_data
=
serializers
.
ActorSerializer
(
actor
).
data
actor_data
[
'manuallyApprovesFollowers'
]
=
False
actor_data
[
'url'
]
=
[{
'type'
:
'Link'
,
'name'
:
'library'
,
'mediaType'
:
'application/activity+json'
,
'href'
:
'https://test.library'
}]
get_actor_data
=
mocker
.
patch
(
'funkwhale_api.federation.actors.get_actor_data'
,
return_value
=
actor_data
)
get_library_data_result
=
{
'test'
:
'test'
}
get_library_data
=
mocker
.
patch
(
'funkwhale_api.federation.library.get_library_data'
,
return_value
=
get_library_data_result
)
result
=
library
.
scan_from_account_name
(
'library@test.actor'
)
get_resource
.
assert_called_once_with
(
'acct:library@test.actor'
)
get_actor_data
.
assert_called_once_with
(
actor
.
url
)
get_library_data
.
assert_called_once_with
(
actor_data
[
'url'
][
0
][
'href'
])
assert
result
==
{
'webfinger'
:
get_resource_result
,
'actor'
:
actor_data
,
'library'
:
get_library_data_result
,
}
def
test_get_library_data
(
r_mock
,
factories
):
actor
=
factories
[
'federation.Actor'
]()
url
=
'https://test.library'
conf
=
{
'id'
:
url
,
'items'
:
[],
'actor'
:
actor
,
'page_size'
:
5
,
}
data
=
serializers
.
PaginatedCollectionSerializer
(
conf
).
data
r_mock
.
get
(
url
,
json
=
data
)
result
=
library
.
get_library_data
(
url
)
for
f
in
[
'totalItems'
,
'actor'
,
'id'
,
'type'
]:
assert
result
[
f
]
==
data
[
f
]
def
test_get_library_data_requires_authentication
(
r_mock
,
factories
):
url
=
'https://test.library'
r_mock
.
get
(
url
,
status_code
=
403
)
result
=
library
.
get_library_data
(
url
)
assert
result
[
'errors'
]
==
[
'This library requires authentication'
]
api/tests/federation/test_views.py
View file @
097707de
...
...
@@ -164,3 +164,18 @@ def test_library_actor_includes_library_link(db, settings, api_client):
]
assert
response
.
status_code
==
200
assert
response
.
data
[
'url'
]
==
expected_links
def
test_can_scan_library
(
superuser_api_client
,
mocker
):
result
=
{
'test'
:
'test'
}
scan
=
mocker
.
patch
(
'funkwhale_api.federation.library.scan_from_account_name'
,
return_value
=
result
)
url
=
reverse
(
'api:v1:federation:libraries-scan'
)
response
=
superuser_api_client
.
get
(
url
,
data
=
{
'account'
:
'test@test.library'
})
assert
response
.
status_code
==
200
assert
response
.
data
==
result
scan
.
assert_called_once_with
(
'test@test.library'
)
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment