kopia lustrzana https://gitlab.com/jaywink/federation
Merge pull request #13 from jaywink/nodeinfo
Add support for NodeInfo document generationmerge-requests/130/head
commit
fd1f5385d3
|
@ -1,3 +1,8 @@
|
||||||
|
## [unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Support for generating [NodeInfo](http://nodeinfo.diaspora.software) documents using the generator `federation.hostmeta.generators.NodeInfo`. Strict validation is skipped by default, but can be enabled by passing in `raise_on_validate` to the `NodeInfo` class. By default a warning will be generated on documents that don't conform with the strict NodeInfo values. This can be disabled by passing in `skip_validate` to the class.
|
||||||
|
|
||||||
## [0.2.0] - 2016-04-09
|
## [0.2.0] - 2016-04-09
|
||||||
|
|
||||||
### Backwards incompatible changes
|
### Backwards incompatible changes
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import warnings
|
||||||
|
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from string import Template
|
from string import Template
|
||||||
from jsonschema import validate
|
from jsonschema import validate
|
||||||
|
from jsonschema.exceptions import ValidationError
|
||||||
from xrd import XRD, Link, Element
|
from xrd import XRD, Link, Element
|
||||||
|
|
||||||
|
|
||||||
|
@ -214,3 +218,47 @@ class SocialRelayWellKnown(object):
|
||||||
with open(schema_path) as f:
|
with open(schema_path) as f:
|
||||||
schema = json.load(f)
|
schema = json.load(f)
|
||||||
validate(self.doc, schema)
|
validate(self.doc, schema)
|
||||||
|
|
||||||
|
|
||||||
|
class NodeInfo(object):
|
||||||
|
"""Generate a NodeInfo document.
|
||||||
|
|
||||||
|
See spec: http://nodeinfo.diaspora.software
|
||||||
|
|
||||||
|
NodeInfo is unnecessarely restrictive in field values. We wont be supporting such strictness, though
|
||||||
|
we will raise a warning unless validation is skipped with `skip_validate=True`.
|
||||||
|
|
||||||
|
For strictness, `raise_on_validate=True` will cause a `ValidationError` to be raised.
|
||||||
|
|
||||||
|
See schema document `federation/hostmeta/schemas/nodeinfo-1.0.json` for how to instantiate this class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, software, protocols, services, open_registrations, usage, metadata, skip_validate=False,
|
||||||
|
raise_on_validate=False):
|
||||||
|
self.doc = {
|
||||||
|
"version": "1.0",
|
||||||
|
"software": software,
|
||||||
|
"protocols": protocols,
|
||||||
|
"services": services,
|
||||||
|
"openRegistrations": open_registrations,
|
||||||
|
"usage": usage,
|
||||||
|
"metadata": metadata,
|
||||||
|
}
|
||||||
|
self.skip_validate = skip_validate
|
||||||
|
self.raise_on_validate = raise_on_validate
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
if not self.skip_validate:
|
||||||
|
self.validate_doc()
|
||||||
|
return json.dumps(self.doc)
|
||||||
|
|
||||||
|
def validate_doc(self):
|
||||||
|
schema_path = os.path.join(os.path.dirname(__file__), "schemas", "nodeinfo-1.0.json")
|
||||||
|
with open(schema_path) as f:
|
||||||
|
schema = json.load(f)
|
||||||
|
try:
|
||||||
|
validate(self.doc, schema)
|
||||||
|
except ValidationError:
|
||||||
|
if self.raise_on_validate:
|
||||||
|
raise
|
||||||
|
warnings.warn("NodeInfo document generated does not validate against NodeInfo 1.0 specification.")
|
||||||
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"id": "http://nodeinfo.diaspora.software/ns/schema/1.0#",
|
||||||
|
"description": "NodeInfo schema version 1.0.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"version",
|
||||||
|
"software",
|
||||||
|
"protocols",
|
||||||
|
"services",
|
||||||
|
"openRegistrations",
|
||||||
|
"usage",
|
||||||
|
"metadata"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"version": {
|
||||||
|
"description": "The schema version, must be 1.0.",
|
||||||
|
"enum": [
|
||||||
|
"1.0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"software": {
|
||||||
|
"description": "Metadata about server software in use.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"version"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "The canonical name of this server software.",
|
||||||
|
"enum": [
|
||||||
|
"diaspora",
|
||||||
|
"friendica",
|
||||||
|
"redmatrix"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"description": "The version of this server software.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"protocols": {
|
||||||
|
"description": "The protocols supported on this server.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"inbound",
|
||||||
|
"outbound"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"inbound": {
|
||||||
|
"description": "The protocols this server can receive traffic for.",
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"enum": [
|
||||||
|
"buddycloud",
|
||||||
|
"diaspora",
|
||||||
|
"friendica",
|
||||||
|
"gnusocial",
|
||||||
|
"libertree",
|
||||||
|
"mediagoblin",
|
||||||
|
"pumpio",
|
||||||
|
"redmatrix",
|
||||||
|
"smtp",
|
||||||
|
"tent"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outbound": {
|
||||||
|
"description": "The protocols this server can generate traffic for.",
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"enum": [
|
||||||
|
"buddycloud",
|
||||||
|
"diaspora",
|
||||||
|
"friendica",
|
||||||
|
"gnusocial",
|
||||||
|
"libertree",
|
||||||
|
"mediagoblin",
|
||||||
|
"pumpio",
|
||||||
|
"redmatrix",
|
||||||
|
"smtp",
|
||||||
|
"tent"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"description": "The third party sites this server can connect to via their application API.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"inbound",
|
||||||
|
"outbound"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"inbound": {
|
||||||
|
"description": "The third party sites this server can retrieve messages from for combined display with regular traffic.",
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 0,
|
||||||
|
"items": {
|
||||||
|
"enum": [
|
||||||
|
"appnet",
|
||||||
|
"gnusocial",
|
||||||
|
"pumpio"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outbound": {
|
||||||
|
"description": "The third party sites this server can publish messages to on the behalf of a user.",
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 0,
|
||||||
|
"items": {
|
||||||
|
"enum": [
|
||||||
|
"appnet",
|
||||||
|
"blogger",
|
||||||
|
"buddycloud",
|
||||||
|
"diaspora",
|
||||||
|
"dreamwidth",
|
||||||
|
"drupal",
|
||||||
|
"facebook",
|
||||||
|
"friendica",
|
||||||
|
"gnusocial",
|
||||||
|
"google",
|
||||||
|
"insanejournal",
|
||||||
|
"libertree",
|
||||||
|
"linkedin",
|
||||||
|
"livejournal",
|
||||||
|
"mediagoblin",
|
||||||
|
"myspace",
|
||||||
|
"pinterest",
|
||||||
|
"posterous",
|
||||||
|
"pumpio",
|
||||||
|
"redmatrix",
|
||||||
|
"smtp",
|
||||||
|
"tent",
|
||||||
|
"tumblr",
|
||||||
|
"twitter",
|
||||||
|
"wordpress",
|
||||||
|
"xmpp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"openRegistrations": {
|
||||||
|
"description": "Whether this server allows open self-registration.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"usage": {
|
||||||
|
"description": "Usage statistics for this server.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"users"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"users": {
|
||||||
|
"description": "statistics about the users of this server.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"total": {
|
||||||
|
"description": "The total amount of on this server registered users.",
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"activeHalfyear": {
|
||||||
|
"description": "The amount of users that signed in at least once in the last 180 days.",
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"activeMonth": {
|
||||||
|
"description": "The amount of users that signed in at least once in the last 30 days.",
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"localPosts": {
|
||||||
|
"description": "The amount of posts that were made by users that are registered on this server.",
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"localComments": {
|
||||||
|
"description": "The amount of comments that were made by users that are registered on this server.",
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"description": "Free form key value pairs for software specific values. Clients should not rely on any specific key present.",
|
||||||
|
"type": "object",
|
||||||
|
"minProperties": 0,
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ from jsonschema import validate, ValidationError
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from federation.hostmeta.generators import generate_host_meta, generate_legacy_webfinger, generate_hcard, \
|
from federation.hostmeta.generators import generate_host_meta, generate_legacy_webfinger, generate_hcard, \
|
||||||
SocialRelayWellKnown
|
SocialRelayWellKnown, NodeInfo
|
||||||
|
|
||||||
DIASPORA_HOSTMETA = """<?xml version="1.0" encoding="UTF-8"?>
|
DIASPORA_HOSTMETA = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
||||||
|
@ -138,3 +138,54 @@ class TestSocialRelayWellKnownGenerator(object):
|
||||||
well_known = SocialRelayWellKnown(subscribe=True, tags=("foo", "bar"), scope="cities")
|
well_known = SocialRelayWellKnown(subscribe=True, tags=("foo", "bar"), scope="cities")
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
well_known.render()
|
well_known.render()
|
||||||
|
|
||||||
|
|
||||||
|
class TestNodeInfoGenerator(object):
|
||||||
|
def _valid_nodeinfo(self, raise_on_validate=False):
|
||||||
|
return NodeInfo(
|
||||||
|
software={"name": "diaspora", "version": "0.5.4.3"},
|
||||||
|
protocols={"inbound": ["diaspora"], "outbound": ["diaspora"]},
|
||||||
|
services={"inbound": ["pumpio"], "outbound": ["twitter"]},
|
||||||
|
open_registrations=True,
|
||||||
|
usage={"users": {}},
|
||||||
|
metadata={},
|
||||||
|
raise_on_validate=raise_on_validate
|
||||||
|
)
|
||||||
|
|
||||||
|
def _invalid_nodeinfo(self, raise_on_validate=False):
|
||||||
|
return NodeInfo(
|
||||||
|
software={"name": "diaspora", "version": "0.5.4.3", "what_is_this_evil_key_here": True},
|
||||||
|
protocols={"inbound": ["diaspora"], "outbound": ["diaspora"]},
|
||||||
|
services={"inbound": ["pumpio"], "outbound": ["twitter"]},
|
||||||
|
open_registrations=True,
|
||||||
|
usage={"users": {}},
|
||||||
|
metadata={},
|
||||||
|
raise_on_validate=raise_on_validate
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_nodeinfo_generator(self):
|
||||||
|
nodeinfo = self._valid_nodeinfo()
|
||||||
|
assert nodeinfo.doc["version"] == "1.0"
|
||||||
|
assert nodeinfo.doc["software"] == {"name": "diaspora", "version": "0.5.4.3"}
|
||||||
|
assert nodeinfo.doc["protocols"] == {"inbound": ["diaspora"], "outbound": ["diaspora"]}
|
||||||
|
assert nodeinfo.doc["services"] == {"inbound": ["pumpio"], "outbound": ["twitter"]}
|
||||||
|
assert nodeinfo.doc["openRegistrations"] == True
|
||||||
|
assert nodeinfo.doc["usage"] == {"users": {}}
|
||||||
|
assert nodeinfo.doc["metadata"] == {}
|
||||||
|
|
||||||
|
def test_nodeinfo_generator_raises_on_invalid_nodeinfo_and_raise_on_validate(self):
|
||||||
|
nodeinfo = self._invalid_nodeinfo(raise_on_validate=True)
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
nodeinfo.render()
|
||||||
|
|
||||||
|
def test_nodeinfo_generator_does_not_raise_on_invalid_nodeinfo(self):
|
||||||
|
nodeinfo = self._invalid_nodeinfo()
|
||||||
|
nodeinfo.render()
|
||||||
|
|
||||||
|
def test_nodeinfo_generator_does_not_raise_on_valid_nodeinfo_and_raise_on_validate(self):
|
||||||
|
nodeinfo = self._valid_nodeinfo(raise_on_validate=True)
|
||||||
|
nodeinfo.render()
|
||||||
|
|
||||||
|
def test_nodeinfo_generator_render_returns_a_document(self):
|
||||||
|
nodeinfo = self._valid_nodeinfo()
|
||||||
|
assert isinstance(nodeinfo.render(), str)
|
||||||
|
|
Ładowanie…
Reference in New Issue