diff options
Diffstat (limited to 'mediagoblin/tools')
-rw-r--r-- | mediagoblin/tools/crypto.py | 6 | ||||
-rw-r--r-- | mediagoblin/tools/mail.py | 11 | ||||
-rw-r--r-- | mediagoblin/tools/metadata.py | 222 | ||||
-rw-r--r-- | mediagoblin/tools/request.py | 20 | ||||
-rw-r--r-- | mediagoblin/tools/response.py | 8 | ||||
-rw-r--r-- | mediagoblin/tools/translate.py | 2 |
6 files changed, 262 insertions, 7 deletions
diff --git a/mediagoblin/tools/crypto.py b/mediagoblin/tools/crypto.py index cf6d31f6..c85ecd4a 100644 --- a/mediagoblin/tools/crypto.py +++ b/mediagoblin/tools/crypto.py @@ -27,8 +27,7 @@ from mediagoblin import mg_globals _log = logging.getLogger(__name__) # produces base64 alphabet -alphabet = string.ascii_letters + "-_" -base = len(alphabet) +ALPHABET = string.ascii_letters + "-_" # Use the system (hardware-based) random number generator if it exists. # -- this optimization is lifted from Django @@ -117,8 +116,9 @@ def get_timed_signer_url(namespace): return itsdangerous.URLSafeTimedSerializer(__itsda_secret, salt=namespace) -def random_string(length): +def random_string(length, alphabet=ALPHABET): """ Returns a URL safe base64 encoded crypographically strong string """ + base = len(alphabet) rstring = "" for i in range(length): n = getrandbits(6) # 6 bytes = 2^6 = 64 diff --git a/mediagoblin/tools/mail.py b/mediagoblin/tools/mail.py index ad2e5a19..ab3e0eaa 100644 --- a/mediagoblin/tools/mail.py +++ b/mediagoblin/tools/mail.py @@ -16,7 +16,9 @@ from __future__ import print_function, unicode_literals +import six import smtplib +import sys from mediagoblin import mg_globals, messages from mediagoblin._compat import MIMEText from mediagoblin.tools import common @@ -66,6 +68,8 @@ class FakeMhost(object): 'to': to_addrs, 'message': message}) + def starttls(self): + raise smtplib.SMTPException("No STARTTLS here") def _clear_test_inboxes(): global EMAIL_TEST_INBOX @@ -105,6 +109,13 @@ def send_email(from_addr, to_addrs, subject, message_body): if not mg_globals.app_config['email_smtp_host']: # e.g. host = '' mhost.connect() # We SMTP.connect explicitly + try: + mhost.starttls() + except smtplib.SMTPException: + # Only raise an exception if we're forced to + if mg_globals.app_config['email_smtp_force_starttls']: + six.reraise(*sys.exc_info()) + if ((not common.TESTS_ENABLED) and (mg_globals.app_config['email_smtp_user'] or mg_globals.app_config['email_smtp_pass'])): diff --git a/mediagoblin/tools/metadata.py b/mediagoblin/tools/metadata.py new file mode 100644 index 00000000..bfefcac9 --- /dev/null +++ b/mediagoblin/tools/metadata.py @@ -0,0 +1,222 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import os +import copy +import json +import re +from pkg_resources import resource_filename + +import dateutil.parser +from pyld import jsonld +from jsonschema import validate, FormatChecker, draft4_format_checker +from jsonschema.compat import str_types + +from mediagoblin.tools.pluginapi import hook_handle + + + +######################################################## +## Set up the MediaGoblin format checker for json-schema +######################################################## + +URL_REGEX = re.compile( + r'^[a-z]+://([^/:]+|([0-9]{1,3}\.){3}[0-9]{1,3})(:[0-9]+)?(\/.*)?$', + re.IGNORECASE) + +def is_uri(instance): + """ + jsonschema uri validator + """ + if not isinstance(instance, str_types): + return True + + return URL_REGEX.match(instance) + +def is_datetime(instance): + """ + Is a date or datetime readable string. + """ + if not isinstance(instance, str_types): + return True + + return dateutil.parser.parse(instance) + + +class DefaultChecker(FormatChecker): + """ + Default MediaGoblin format checker... extended to include a few extra things + """ + checkers = copy.deepcopy(draft4_format_checker.checkers) + + +DefaultChecker.checkers[u"uri"] = (is_uri, ()) +DefaultChecker.checkers[u"date-time"] = (is_datetime, (ValueError, TypeError)) +DEFAULT_CHECKER = DefaultChecker() + +# Crappy default schema, checks for things we deem important + +DEFAULT_SCHEMA = { + "$schema": "http://json-schema.org/schema#", + + "type": "object", + "properties": { + "license": { + "format": "uri", + "type": "string", + }, + "dcterms:created": { + "format": "date-time", + "type": "string", + }, + "dc:created": { + "format": "date-time", + "type": "string", + } + }, +} + + +def load_resource(package, resource_path): + """ + Load a resource, return it as a string. + + Args: + - package: package or module name. Eg "mediagoblin.media_types.audio" + - resource_path: path to get to this resource, a list of + directories and finally a filename. Will be joined with + os.path.sep. + """ + filename = resource_filename(package, os.path.sep.join(resource_path)) + return file(filename).read() + +def load_resource_json(package, resource_path): + """ + Load a resource json file, return a dictionary. + + Args: + - package: package or module name. Eg "mediagoblin.media_types.audio" + - resource_path: path to get to this resource, a list of + directories and finally a filename. Will be joined with + os.path.sep. + """ + return json.loads(load_resource(package, resource_path)) + + +################################## +## Load the MediaGoblin core files +################################## + + +BUILTIN_CONTEXTS = { + "http://www.w3.org/2013/json-ld-context/rdfa11": load_resource( + "mediagoblin", ["static", "metadata", "rdfa11.jsonld"])} + + +_CONTEXT_CACHE = {} + +def load_context(url): + """ + A self-aware document loader. For those contexts MediaGoblin + stores internally, load them from disk. + """ + if url in _CONTEXT_CACHE: + return _CONTEXT_CACHE[url] + + # See if it's one of our basic ones + document = BUILTIN_CONTEXTS.get(url, None) + + # No? See if we have an internal schema for this + if document is None: + document = hook_handle(("context_url_data", url)) + + # Okay, if we've gotten a document by now... let's package it up + if document is not None: + document = {'contextUrl': None, + 'documentUrl': url, + 'document': document} + + # Otherwise, use jsonld.load_document + else: + document = jsonld.load_document(url) + + # cache + _CONTEXT_CACHE[url] = document + return document + + +DEFAULT_CONTEXT = "http://www.w3.org/2013/json-ld-context/rdfa11" + +def compact_json(metadata, context=DEFAULT_CONTEXT): + """ + Compact json with supplied context. + + Note: Free floating" nodes are removed (eg a key just named + "bazzzzzz" which isn't specified in the context... something like + bazzzzzz:blerp will stay though. This is jsonld.compact behavior. + """ + compacted = jsonld.compact( + metadata, context, + options={ + "documentLoader": load_context, + # This allows for things like "license" and etc to be preserved + "expandContext": context, + "keepFreeFloatingNodes": False}) + + return compacted + + +def compact_and_validate(metadata, context=DEFAULT_CONTEXT, + schema=DEFAULT_SCHEMA): + """ + compact json with supplied context, check against schema for errors + + raises an exception (jsonschema.exceptions.ValidationError) if + there's an error. + + Note: Free floating" nodes are removed (eg a key just named + "bazzzzzz" which isn't specified in the context... something like + bazzzzzz:blerp will stay though. This is jsonld.compact behavior. + + You may wish to do this validation yourself... this is just for convenience. + """ + compacted = compact_json(metadata, context) + validate(metadata, schema, format_checker=DEFAULT_CHECKER) + + return compacted + + +def expand_json(metadata, context=DEFAULT_CONTEXT): + """ + Expand json, but be sure to use our documentLoader. + + By default this expands with DEFAULT_CONTEXT, but if you do not need this, + you can safely set this to None. + + # @@: Is the above a good idea? Maybe it should be set to None by + # default. + """ + options = { + "documentLoader": load_context} + if context is not None: + options["expandContext"] = context + return jsonld.expand(metadata, options=options) + + +def rdfa_to_readable(rdfa_predicate): + readable = rdfa_predicate.split(u":")[1].capitalize() + return readable diff --git a/mediagoblin/tools/request.py b/mediagoblin/tools/request.py index d4739039..d2cb0f6a 100644 --- a/mediagoblin/tools/request.py +++ b/mediagoblin/tools/request.py @@ -16,7 +16,9 @@ import json import logging -from mediagoblin.db.models import User + +from mediagoblin.db.models import User, AccessToken +from mediagoblin.oauth.tools.request import decode_authorization_header _log = logging.getLogger(__name__) @@ -31,6 +33,18 @@ def setup_user_in_request(request): Examine a request and tack on a request.user parameter if that's appropriate. """ + # If API request the user will be associated with the access token + authorization = decode_authorization_header(request.headers) + + if authorization.get(u"access_token"): + # Check authorization header. + token = authorization[u"oauth_token"] + token = AccessToken.query.filter_by(token=token).first() + if token is not None: + request.user = token.user + return + + if 'user_id' not in request.session: request.user = None return @@ -45,8 +59,8 @@ def setup_user_in_request(request): def decode_request(request): """ Decodes a request based on MIME-Type """ - data = request.get_data() - + data = request.data + if request.content_type == json_encoded: data = json.loads(data) elif request.content_type == form_encoded or request.content_type == "": diff --git a/mediagoblin/tools/response.py b/mediagoblin/tools/response.py index 1380633f..e1c59e32 100644 --- a/mediagoblin/tools/response.py +++ b/mediagoblin/tools/response.py @@ -158,6 +158,14 @@ def json_response(serializable, _disable_cors=False, *args, **kw): return response +def json_error(error_str, status=400, *args, **kwargs): + """ + This is like json_response but takes an error message in and formats + it in {"error": error_str}. This also sets the default HTTP status + code to 400. + """ + return json_response({"error": error_str}, status=status, *args, **kwargs) + def form_response(data, *args, **kwargs): """ Responds using application/x-www-form-urlencoded and returns a werkzeug diff --git a/mediagoblin/tools/translate.py b/mediagoblin/tools/translate.py index 5ea73b71..a5e56cfe 100644 --- a/mediagoblin/tools/translate.py +++ b/mediagoblin/tools/translate.py @@ -33,7 +33,7 @@ TRANSLATIONS_PATH = pkg_resources.resource_filename( 'mediagoblin', 'i18n') # Known RTL languages -KNOWN_RTL = set(["ar", "fa", "zh","he","iw","ja","ur","yi","ji"]) +KNOWN_RTL = set(["ar", "fa", "he", "iw", "ur", "yi", "ji"]) def is_rtl(lang): """Returns true when the local language is right to left""" |