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
|
||||
|
||||
### Backwards incompatible changes
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import warnings
|
||||
|
||||
from base64 import b64encode
|
||||
import json
|
||||
import os
|
||||
from string import Template
|
||||
from jsonschema import validate
|
||||
from jsonschema.exceptions import ValidationError
|
||||
from xrd import XRD, Link, Element
|
||||
|
||||
|
||||
|
@ -214,3 +218,47 @@ class SocialRelayWellKnown(object):
|
|||
with open(schema_path) as f:
|
||||
schema = json.load(f)
|
||||
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
|
||||
|
||||
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"?>
|
||||
<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")
|
||||
with pytest.raises(ValidationError):
|
||||
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