Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
jovuit
funkwhale
Commits
d9afed50
Commit
d9afed50
authored
Mar 11, 2020
by
Eliot Berriot
Browse files
Fix #1038: Federated reports
parent
40720328
Changes
34
Hide whitespace changes
Inline
Side-by-side
api/config/spa_urls.py
View file @
d9afed50
from
django
import
urls
from
funkwhale_api.audio
import
spa_views
as
audio_spa_views
from
funkwhale_api.federation
import
spa_views
as
federation_spa_views
from
funkwhale_api.music
import
spa_views
...
...
@@ -36,4 +37,9 @@ urlpatterns = [
audio_spa_views
.
channel_detail_username
,
name
=
"channel_detail"
,
),
urls
.
re_path
(
r
"^@(?P<username>[^/]+)/?$"
,
federation_spa_views
.
actor_detail_username
,
name
=
"actor_detail"
,
),
]
api/funkwhale_api/audio/models.py
View file @
d9afed50
...
...
@@ -64,6 +64,10 @@ class Channel(models.Model):
)
)
@
property
def
fid
(
self
):
return
self
.
actor
.
fid
def
generate_actor
(
username
,
**
kwargs
):
actor_data
=
user_models
.
get_actor_data
(
username
,
**
kwargs
)
...
...
api/funkwhale_api/audio/spa_views.py
View file @
d9afed50
...
...
@@ -7,6 +7,7 @@ from django.urls import reverse
from
rest_framework
import
serializers
from
funkwhale_api.common
import
preferences
from
funkwhale_api.common
import
middleware
from
funkwhale_api.common
import
utils
from
funkwhale_api.federation
import
utils
as
federation_utils
from
funkwhale_api.music
import
spa_views
...
...
@@ -14,7 +15,7 @@ from funkwhale_api.music import spa_views
from
.
import
models
def
channel_detail
(
query
):
def
channel_detail
(
query
,
redirect_to_ap
):
queryset
=
models
.
Channel
.
objects
.
filter
(
query
).
select_related
(
"artist__attachment_cover"
,
"actor"
,
"library"
)
...
...
@@ -23,6 +24,9 @@ def channel_detail(query):
except
models
.
Channel
.
DoesNotExist
:
return
[]
if
redirect_to_ap
:
raise
middleware
.
ApiRedirect
(
obj
.
actor
.
fid
)
obj_url
=
utils
.
join_url
(
settings
.
FUNKWHALE_URL
,
utils
.
spa_reverse
(
...
...
@@ -81,16 +85,16 @@ def channel_detail(query):
return
metas
def
channel_detail_uuid
(
request
,
uuid
):
def
channel_detail_uuid
(
request
,
uuid
,
redirect_to_ap
):
validator
=
serializers
.
UUIDField
().
to_internal_value
try
:
uuid
=
validator
(
uuid
)
except
serializers
.
ValidationError
:
return
[]
return
channel_detail
(
Q
(
uuid
=
uuid
))
return
channel_detail
(
Q
(
uuid
=
uuid
)
,
redirect_to_ap
)
def
channel_detail_username
(
request
,
username
):
def
channel_detail_username
(
request
,
username
,
redirect_to_ap
):
validator
=
federation_utils
.
get_actor_data_from_username
try
:
username_data
=
validator
(
username
)
...
...
@@ -100,4 +104,4 @@ def channel_detail_username(request, username):
actor__domain
=
username_data
[
"domain"
],
actor__preferred_username__iexact
=
username_data
[
"username"
],
)
return
channel_detail
(
query
)
return
channel_detail
(
query
,
redirect_to_ap
)
api/funkwhale_api/common/middleware.py
View file @
d9afed50
...
...
@@ -4,6 +4,7 @@ import io
import
os
import
re
import
time
import
urllib.parse
import
xml.sax.saxutils
from
django
import
http
...
...
@@ -163,8 +164,16 @@ def render_tags(tags):
def
get_request_head_tags
(
request
):
accept_header
=
request
.
headers
.
get
(
"Accept"
)
or
None
redirect_to_ap
=
(
False
if
not
accept_header
else
not
federation_utils
.
should_redirect_ap_to_html
(
accept_header
)
)
match
=
urls
.
resolve
(
request
.
path
,
urlconf
=
settings
.
SPA_URLCONF
)
return
match
.
func
(
request
,
*
match
.
args
,
**
match
.
kwargs
)
return
match
.
func
(
request
,
*
match
.
args
,
redirect_to_ap
=
redirect_to_ap
,
**
match
.
kwargs
)
def
get_custom_css
():
...
...
@@ -175,6 +184,30 @@ def get_custom_css():
return
xml
.
sax
.
saxutils
.
escape
(
css
)
class
ApiRedirect
(
Exception
):
def
__init__
(
self
,
url
):
self
.
url
=
url
def
get_api_response
(
request
,
url
):
"""
Quite ugly but we have no choice. When Accept header is set to application/activity+json
some clients expect to get a JSON payload (instead of the HTML we return). Since
redirecting to the URL does not work (because it makes the signature verification fail),
we grab the internal view corresponding to the URL, call it and return this as the
response
"""
path
=
urllib
.
parse
.
urlparse
(
url
).
path
try
:
match
=
urls
.
resolve
(
path
)
except
urls
.
exceptions
.
Resolver404
:
return
http
.
HttpResponseNotFound
()
response
=
match
.
func
(
request
,
*
match
.
args
,
**
match
.
kwargs
)
response
.
render
()
return
response
class
SPAFallbackMiddleware
:
def
__init__
(
self
,
get_response
):
self
.
get_response
=
get_response
...
...
@@ -183,7 +216,10 @@ class SPAFallbackMiddleware:
response
=
self
.
get_response
(
request
)
if
response
.
status_code
==
404
and
should_fallback_to_spa
(
request
.
path
):
return
serve_spa
(
request
)
try
:
return
serve_spa
(
request
)
except
ApiRedirect
as
e
:
return
get_api_response
(
request
,
e
.
url
)
return
response
...
...
api/funkwhale_api/federation/activity.py
View file @
d9afed50
...
...
@@ -165,14 +165,14 @@ def receive(activity, on_behalf_of, inbox_actor=None):
return
local_to_recipients
=
get_actors_from_audience
(
activity
.
get
(
"to"
,
[]))
local_to_recipients
=
local_to_recipients
.
exclude
(
user
=
None
)
local_to_recipients
=
local_to_recipients
.
local
(
)
local_to_recipients
=
local_to_recipients
.
values_list
(
"pk"
,
flat
=
True
)
local_to_recipients
=
list
(
local_to_recipients
)
if
inbox_actor
:
local_to_recipients
.
append
(
inbox_actor
.
pk
)
local_cc_recipients
=
get_actors_from_audience
(
activity
.
get
(
"cc"
,
[]))
local_cc_recipients
=
local_cc_recipients
.
exclude
(
user
=
None
)
local_cc_recipients
=
local_cc_recipients
.
local
(
)
local_cc_recipients
=
local_cc_recipients
.
values_list
(
"pk"
,
flat
=
True
)
inbox_items
=
[]
...
...
@@ -457,6 +457,13 @@ def prepare_deliveries_and_inbox_items(recipient_list, type, allowed_domains=Non
else
:
remote_inbox_urls
.
add
(
actor
.
shared_inbox_url
or
actor
.
inbox_url
)
urls
.
append
(
r
[
"target"
].
followers_url
)
elif
isinstance
(
r
,
dict
)
and
r
[
"type"
]
==
"actor_inbox"
:
actor
=
r
[
"actor"
]
urls
.
append
(
actor
.
fid
)
if
actor
.
is_local
:
local_recipients
.
add
(
actor
)
else
:
remote_inbox_urls
.
add
(
actor
.
inbox_url
)
elif
isinstance
(
r
,
dict
)
and
r
[
"type"
]
==
"instances_with_followers"
:
# we want to broadcast the activity to other instances service actors
...
...
api/funkwhale_api/federation/contexts.py
View file @
d9afed50
...
...
@@ -301,6 +301,38 @@ CONTEXTS = [
}
},
},
{
"shortId"
:
"LITEPUB"
,
"contextUrl"
:
None
,
"documentUrl"
:
"http://litepub.social/ns"
,
"document"
:
{
# from https://ap.thequietplace.social/schemas/litepub-0.1.jsonld
"@context"
:
{
"Emoji"
:
"toot:Emoji"
,
"Hashtag"
:
"as:Hashtag"
,
"PropertyValue"
:
"schema:PropertyValue"
,
"atomUri"
:
"ostatus:atomUri"
,
"conversation"
:
{
"@id"
:
"ostatus:conversation"
,
"@type"
:
"@id"
},
"discoverable"
:
"toot:discoverable"
,
"manuallyApprovesFollowers"
:
"as:manuallyApprovesFollowers"
,
"ostatus"
:
"http://ostatus.org#"
,
"schema"
:
"http://schema.org#"
,
"toot"
:
"http://joinmastodon.org/ns#"
,
"value"
:
"schema:value"
,
"sensitive"
:
"as:sensitive"
,
"litepub"
:
"http://litepub.social/ns#"
,
"invisible"
:
"litepub:invisible"
,
"directMessage"
:
"litepub:directMessage"
,
"listMessage"
:
{
"@id"
:
"litepub:listMessage"
,
"@type"
:
"@id"
},
"oauthRegistrationEndpoint"
:
{
"@id"
:
"litepub:oauthRegistrationEndpoint"
,
"@type"
:
"@id"
,
},
"EmojiReact"
:
"litepub:EmojiReact"
,
"alsoKnownAs"
:
{
"@id"
:
"as:alsoKnownAs"
,
"@type"
:
"@id"
},
}
},
},
]
CONTEXTS_BY_ID
=
{
c
[
"shortId"
]:
c
for
c
in
CONTEXTS
}
...
...
@@ -332,3 +364,4 @@ AS = NS(CONTEXTS_BY_ID["AS"])
LDP
=
NS
(
CONTEXTS_BY_ID
[
"LDP"
])
SEC
=
NS
(
CONTEXTS_BY_ID
[
"SEC"
])
FW
=
NS
(
CONTEXTS_BY_ID
[
"FW"
])
LITEPUB
=
NS
(
CONTEXTS_BY_ID
[
"LITEPUB"
])
api/funkwhale_api/federation/factories.py
View file @
d9afed50
...
...
@@ -125,7 +125,8 @@ class ActorFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
self
.
domain
=
models
.
Domain
.
objects
.
get_or_create
(
name
=
settings
.
FEDERATION_HOSTNAME
)[
0
]
self
.
save
(
update_fields
=
[
"domain"
])
self
.
fid
=
"https://{}/actors/{}"
.
format
(
self
.
domain
,
self
.
preferred_username
)
self
.
save
(
update_fields
=
[
"domain"
,
"fid"
])
if
not
create
:
if
extracted
and
hasattr
(
extracted
,
"pk"
):
extracted
.
actor
=
self
...
...
@@ -166,7 +167,9 @@ class MusicLibraryFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
model
=
"music.Library"
class
Params
:
local
=
factory
.
Trait
(
actor
=
factory
.
SubFactory
(
ActorFactory
,
local
=
True
))
local
=
factory
.
Trait
(
fid
=
None
,
actor
=
factory
.
SubFactory
(
ActorFactory
,
local
=
True
)
)
@
registry
.
register
...
...
api/funkwhale_api/federation/jsonld.py
View file @
d9afed50
...
...
@@ -17,6 +17,10 @@ def cached_contexts(loader):
for
cached
in
contexts
.
CONTEXTS
:
if
url
==
cached
[
"documentUrl"
]:
return
cached
if
cached
[
"shortId"
]
==
"LITEPUB"
and
"/schemas/litepub-"
in
url
:
# XXX UGLY fix for pleroma because they host their schema
# under each instance domain, which makes caching harder
return
cached
return
loader
(
url
,
*
args
,
**
kwargs
)
return
load
...
...
@@ -29,18 +33,19 @@ def get_document_loader():
return
cached_contexts
(
loader
)
def
expand
(
doc
,
options
=
None
,
insert_fw_context
=
True
):
def
expand
(
doc
,
options
=
None
,
default_contexts
=
[
"AS"
,
"FW"
,
"SEC"
]
):
options
=
options
or
{}
options
.
setdefault
(
"documentLoader"
,
get_document_loader
())
if
isinstance
(
doc
,
str
):
doc
=
options
[
"documentLoader"
](
doc
)[
"document"
]
if
insert_fw
_context
:
fw
=
contexts
.
CONTEXTS_BY_ID
[
"FW"
][
"documentUrl"
]
for
context_name
in
default
_context
s
:
ctx
=
contexts
.
CONTEXTS_BY_ID
[
context_name
][
"documentUrl"
]
try
:
insert_context
(
fw
,
doc
)
insert_context
(
ctx
,
doc
)
except
KeyError
:
# probably an already expanded document
pass
result
=
pyld
.
jsonld
.
expand
(
doc
,
options
=
options
)
try
:
# jsonld.expand returns a list, which is useless for us
...
...
api/funkwhale_api/federation/models.py
View file @
d9afed50
...
...
@@ -443,26 +443,29 @@ class Activity(models.Model):
type
=
models
.
CharField
(
db_index
=
True
,
null
=
True
,
max_length
=
100
)
# generic relations
object_id
=
models
.
IntegerField
(
null
=
True
)
object_id
=
models
.
IntegerField
(
null
=
True
,
blank
=
True
)
object_content_type
=
models
.
ForeignKey
(
ContentType
,
null
=
True
,
blank
=
True
,
on_delete
=
models
.
SET_NULL
,
related_name
=
"objecting_activities"
,
)
object
=
GenericForeignKey
(
"object_content_type"
,
"object_id"
)
target_id
=
models
.
IntegerField
(
null
=
True
)
target_id
=
models
.
IntegerField
(
null
=
True
,
blank
=
True
)
target_content_type
=
models
.
ForeignKey
(
ContentType
,
null
=
True
,
blank
=
True
,
on_delete
=
models
.
SET_NULL
,
related_name
=
"targeting_activities"
,
)
target
=
GenericForeignKey
(
"target_content_type"
,
"target_id"
)
related_object_id
=
models
.
IntegerField
(
null
=
True
)
related_object_id
=
models
.
IntegerField
(
null
=
True
,
blank
=
True
)
related_object_content_type
=
models
.
ForeignKey
(
ContentType
,
null
=
True
,
blank
=
True
,
on_delete
=
models
.
SET_NULL
,
related_name
=
"related_objecting_activities"
,
)
...
...
api/funkwhale_api/federation/routes.py
View file @
d9afed50
...
...
@@ -451,3 +451,35 @@ def inbox_delete_actor(payload, context):
logger
.
warn
(
"Cannot delete actor %s, no matching object found"
,
actor
.
fid
)
return
actor
.
delete
()
@
inbox
.
register
({
"type"
:
"Flag"
})
def
inbox_flag
(
payload
,
context
):
serializer
=
serializers
.
FlagSerializer
(
data
=
payload
,
context
=
context
)
if
not
serializer
.
is_valid
(
raise_exception
=
context
.
get
(
"raise_exception"
,
False
)):
logger
.
debug
(
"Discarding invalid report from {}: %s"
,
context
[
"actor"
].
fid
,
serializer
.
errors
,
)
return
report
=
serializer
.
save
()
return
{
"object"
:
report
.
target
,
"related_object"
:
report
}
@
outbox
.
register
({
"type"
:
"Flag"
})
def
outbox_flag
(
context
):
report
=
context
[
"report"
]
actor
=
actors
.
get_service_actor
()
serializer
=
serializers
.
FlagSerializer
(
report
)
yield
{
"type"
:
"Flag"
,
"actor"
:
actor
,
"payload"
:
with_recipients
(
serializer
.
data
,
# Mastodon requires the report to be sent to the reported actor inbox
# (and not the shared inbox)
to
=
[{
"type"
:
"actor_inbox"
,
"actor"
:
report
.
target_owner
}],
),
}
api/funkwhale_api/federation/serializers.py
View file @
d9afed50
...
...
@@ -2,6 +2,7 @@ import logging
import
urllib.parse
import
uuid
from
django.core.exceptions
import
ObjectDoesNotExist
from
django.core.paginator
import
Paginator
from
django.db
import
transaction
...
...
@@ -9,6 +10,9 @@ from rest_framework import serializers
from
funkwhale_api.common
import
utils
as
common_utils
from
funkwhale_api.common
import
models
as
common_models
from
funkwhale_api.moderation
import
models
as
moderation_models
from
funkwhale_api.moderation
import
serializers
as
moderation_serializers
from
funkwhale_api.moderation
import
signals
as
moderation_signals
from
funkwhale_api.music
import
licenses
from
funkwhale_api.music
import
models
as
music_models
from
funkwhale_api.music
import
tasks
as
music_tasks
...
...
@@ -36,13 +40,20 @@ class MediaSerializer(jsonld.JsonLdSerializer):
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
allowed_mimetypes
=
kwargs
.
pop
(
"allowed_mimetypes"
,
[])
self
.
allow_empty_mimetype
=
kwargs
.
pop
(
"allow_empty_mimetype"
,
False
)
super
().
__init__
(
*
args
,
**
kwargs
)
self
.
fields
[
"mediaType"
].
required
=
not
self
.
allow_empty_mimetype
self
.
fields
[
"mediaType"
].
allow_null
=
self
.
allow_empty_mimetype
def
validate_mediaType
(
self
,
v
):
if
not
self
.
allowed_mimetypes
:
# no restrictions
return
v
if
self
.
allow_empty_mimetype
and
not
v
:
return
None
for
mt
in
self
.
allowed_mimetypes
:
if
mt
.
endswith
(
"/*"
):
if
v
.
startswith
(
mt
.
replace
(
"*"
,
""
)):
return
v
...
...
@@ -147,7 +158,10 @@ class ActorSerializer(jsonld.JsonLdSerializer):
publicKey
=
PublicKeySerializer
(
required
=
False
)
endpoints
=
EndpointsSerializer
(
required
=
False
)
icon
=
ImageSerializer
(
allowed_mimetypes
=
[
"image/*"
],
allow_null
=
True
,
required
=
False
allowed_mimetypes
=
[
"image/*"
],
allow_null
=
True
,
required
=
False
,
allow_empty_mimetype
=
True
,
)
class
Meta
:
...
...
@@ -294,7 +308,7 @@ class ActorSerializer(jsonld.JsonLdSerializer):
common_utils
.
attach_file
(
actor
,
"attachment_icon"
,
{
"url"
:
new_value
[
"url"
],
"mimetype"
:
new_value
[
"mediaType"
]
}
{
"url"
:
new_value
[
"url"
],
"mimetype"
:
new_value
.
get
(
"mediaType"
)
}
if
new_value
else
None
,
)
...
...
@@ -1030,7 +1044,7 @@ class MusicEntitySerializer(jsonld.JsonLdSerializer):
return
validated_data
# create the attachment by hand so it can be attached as the cover
validated_data
[
"attachment_cover"
]
=
common_models
.
Attachment
.
objects
.
create
(
mimetype
=
attachment_cover
[
"mediaType"
]
,
mimetype
=
attachment_cover
.
get
(
"mediaType"
)
,
url
=
attachment_cover
[
"url"
],
actor
=
instance
.
attributed_to
,
)
...
...
@@ -1048,7 +1062,10 @@ class MusicEntitySerializer(jsonld.JsonLdSerializer):
class
ArtistSerializer
(
MusicEntitySerializer
):
image
=
ImageSerializer
(
allowed_mimetypes
=
[
"image/*"
],
allow_null
=
True
,
required
=
False
allowed_mimetypes
=
[
"image/*"
],
allow_null
=
True
,
required
=
False
,
allow_empty_mimetype
=
True
,
)
updateable_fields
=
[
(
"name"
,
"name"
),
...
...
@@ -1094,7 +1111,10 @@ class AlbumSerializer(MusicEntitySerializer):
artists
=
serializers
.
ListField
(
child
=
ArtistSerializer
(),
min_length
=
1
)
# XXX: 1.0 rename to image
cover
=
ImageSerializer
(
allowed_mimetypes
=
[
"image/*"
],
allow_null
=
True
,
required
=
False
allowed_mimetypes
=
[
"image/*"
],
allow_null
=
True
,
required
=
False
,
allow_empty_mimetype
=
True
,
)
updateable_fields
=
[
(
"name"
,
"title"
),
...
...
@@ -1172,7 +1192,10 @@ class TrackSerializer(MusicEntitySerializer):
license
=
serializers
.
URLField
(
allow_null
=
True
,
required
=
False
)
copyright
=
serializers
.
CharField
(
allow_null
=
True
,
required
=
False
)
image
=
ImageSerializer
(
allowed_mimetypes
=
[
"image/*"
],
allow_null
=
True
,
required
=
False
allowed_mimetypes
=
[
"image/*"
],
allow_null
=
True
,
required
=
False
,
allow_empty_mimetype
=
True
,
)
updateable_fields
=
[
...
...
@@ -1437,6 +1460,85 @@ class ActorDeleteSerializer(jsonld.JsonLdSerializer):
jsonld_mapping
=
{
"fid"
:
jsonld
.
first_id
(
contexts
.
AS
.
object
)}
class
FlagSerializer
(
jsonld
.
JsonLdSerializer
):
type
=
serializers
.
ChoiceField
(
choices
=
[
contexts
.
AS
.
Flag
])
id
=
serializers
.
URLField
(
max_length
=
500
)
object
=
serializers
.
URLField
(
max_length
=
500
)
content
=
serializers
.
CharField
(
required
=
False
,
allow_null
=
True
,
allow_blank
=
True
)
actor
=
serializers
.
URLField
(
max_length
=
500
)
type
=
serializers
.
ListField
(
child
=
TagSerializer
(),
min_length
=
0
,
required
=
False
,
allow_null
=
True
)
class
Meta
:
jsonld_mapping
=
{
"object"
:
jsonld
.
first_id
(
contexts
.
AS
.
object
),
"content"
:
jsonld
.
first_val
(
contexts
.
AS
.
content
),
"actor"
:
jsonld
.
first_id
(
contexts
.
AS
.
actor
),
"type"
:
jsonld
.
raw
(
contexts
.
AS
.
tag
),
}
def
validate_object
(
self
,
v
):
try
:
return
utils
.
get_object_by_fid
(
v
,
local
=
True
)
except
ObjectDoesNotExist
:
raise
serializers
.
ValidationError
(
"Unknown id {} for reported object"
.
format
(
v
)
)
def
validate_type
(
self
,
tags
):
if
tags
:
for
tag
in
tags
:
if
tag
[
"name"
]
in
dict
(
moderation_models
.
REPORT_TYPES
):
return
tag
[
"name"
]
return
"other"
def
validate_actor
(
self
,
v
):
try
:
return
models
.
Actor
.
objects
.
get
(
fid
=
v
,
domain
=
self
.
context
[
"actor"
].
domain
)
except
models
.
Actor
.
DoesNotExist
:
raise
serializers
.
ValidationError
(
"Invalid actor"
)
def
validate
(
self
,
data
):
validated_data
=
super
().
validate
(
data
)
return
validated_data
def
create
(
self
,
validated_data
):
kwargs
=
{
"target"
:
validated_data
[
"object"
],
"target_owner"
:
moderation_serializers
.
get_target_owner
(
validated_data
[
"object"
]
),
"target_state"
:
moderation_serializers
.
get_target_state
(
validated_data
[
"object"
]
),
"type"
:
validated_data
.
get
(
"type"
,
"other"
),
"summary"
:
validated_data
.
get
(
"content"
),
"submitter"
:
validated_data
[
"actor"
],
}
report
,
created
=
moderation_models
.
Report
.
objects
.
update_or_create
(
fid
=
validated_data
[
"id"
],
defaults
=
kwargs
,
)
moderation_signals
.
report_created
.
send
(
sender
=
None
,
report
=
report
)
return
report
def
to_representation
(
self
,
instance
):
d
=
{
"type"
:
"Flag"
,
"id"
:
instance
.
get_federation_id
(),
"actor"
:
actors
.
get_service_actor
().
fid
,
"object"
:
[
instance
.
target
.
fid
],
"content"
:
instance
.
summary
,
"tag"
:
[
repr_tag
(
instance
.
type
)],
}
if
self
.
context
.
get
(
"include_ap_context"
,
self
.
parent
is
None
):
d
[
"@context"
]
=
jsonld
.
get_default_context
()
return
d
class
NodeInfoLinkSerializer
(
serializers
.
Serializer
):
href
=
serializers
.
URLField
()
rel
=
serializers
.
URLField
()
...
...
api/funkwhale_api/federation/signing.py
View file @
d9afed50
import
cryptography.exceptions
import
datetime
import
logging
import
pytz
...
...
@@ -31,18 +32,29 @@ def verify_date(raw_date):
now
=
timezone
.
now
()
if
dt
<
now
-
delta
or
dt
>
now
+
delta
:
raise
forms
.
ValidationError
(
"Request Date is too far in the future or in the past"
"Request Date
{}
is too far in the future or in the past"
.
format
(
raw_date
)
)
return
dt
def
verify
(
request
,
public_key
):
verify_date
(
request
.
headers
.
get
(
"Date"
))
return
requests_http_signature
.
HTTPSignatureAuth
.
verify
(
request
,
key_resolver
=
lambda
**
kwargs
:
public_key
,
use_auth_header
=
False
date
=
request
.
headers
.
get
(
"Date"
)
logger
.
debug
(
"Verifying request with date %s and headers %s"
,
date
,
str
(
request
.
headers
)
)
verify_date
(
date
)
try
:
return
requests_http_signature
.
HTTPSignatureAuth
.
verify
(
request
,
key_resolver
=
lambda
**
kwargs
:
public_key
,
use_auth_header
=
False
)
except
cryptography
.
exceptions
.
InvalidSignature
:
logger
.
warning
(
"Could not verify request with date %s and headers %s"
,
date
,
str
(
request
.
headers
),
)
raise
def
verify_django
(
django_request
,
public_key
):
...
...
api/funkwhale_api/federation/spa_views.py
0 → 100644
View file @
d9afed50
from
django.conf
import
settings
from
rest_framework
import
serializers
from
funkwhale_api.common
import
preferences
from
funkwhale_api.common
import
middleware
from
funkwhale_api.common
import
utils
from
funkwhale_api.federation
import
utils
as
federation_utils
from
.
import
models
def
actor_detail_username
(
request
,
username
,
redirect_to_ap
):
validator
=
federation_utils
.
get_actor_data_from_username
try
: