Merge branch 'stable'
[mediagoblin.git] / mediagoblin / plugins / api / tools.py
CommitLineData
a062149e
JW
1# GNU MediaGoblin -- federated, autonomous media hosting
2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Affero General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Affero General Public License for more details.
13#
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import logging
42c83752 18import json
a062149e
JW
19
20from functools import wraps
62d14bf5 21from werkzeug.exceptions import Forbidden
cc195d5b 22from werkzeug.wrappers import Response
fd19da34
BP
23
24from six.moves.urllib.parse import urljoin
25
42c83752 26from mediagoblin import mg_globals
a062149e 27from mediagoblin.tools.pluginapi import PluginManager
42c83752 28from mediagoblin.storage.filestorage import BasicFileStorage
a062149e
JW
29
30_log = logging.getLogger(__name__)
31
32
42c83752
JW
33class Auth(object):
34 '''
35 An object with two significant methods, 'trigger' and 'run'.
36
37 Using a similar object to this, plugins can register specific
38 authentication logic, for example the GET param 'access_token' for OAuth.
39
40 - trigger: Analyze the 'request' argument, return True if you think you
41 can handle the request, otherwise return False
42 - run: The authentication logic, set the request.user object to the user
43 you intend to authenticate and return True, otherwise return False.
44
45 If run() returns False, an HTTP 403 Forbidden error will be shown.
46
47 You may also display custom errors, just raise them within the run()
48 method.
49 '''
50 def trigger(self, request):
51 raise NotImplemented()
52
53 def __call__(self, request, *args, **kw):
54 raise NotImplemented()
55
85726f73
JW
56def get_entry_serializable(entry, urlgen):
57 '''
58 Returns a serializable dict() of a MediaEntry instance.
59
60 :param entry: A MediaEntry instance
61 :param urlgen: An urlgen instance, can be found on the request object passed
62 to views.
63 '''
42c83752 64 return {
0f3bf8d4
JT
65 'user': entry.get_actor.username,
66 'user_id': entry.get_actor.id,
67 'user_bio': entry.get_actor.bio,
68 'user_bio_html': entry.get_actor.bio_html,
85726f73 69 'user_permalink': urlgen('mediagoblin.user_pages.user_home',
0f3bf8d4 70 user=entry.get_actor.username,
85726f73 71 qualified=True),
42c83752
JW
72 'id': entry.id,
73 'created': entry.created.isoformat(),
74 'title': entry.title,
75 'license': entry.license,
76 'description': entry.description,
77 'description_html': entry.description_html,
78 'media_type': entry.media_type,
09e528ac 79 'state': entry.state,
85726f73
JW
80 'permalink': entry.url_for_self(urlgen, qualified=True),
81 'media_files': get_media_file_paths(entry.media_files, urlgen)}
82
42c83752 83
85726f73
JW
84def get_media_file_paths(media_files, urlgen):
85 '''
86 Returns a dictionary of media files with `file_handle` => `qualified URL`
42c83752 87
85726f73
JW
88 :param media_files: dict-like object consisting of `file_handle => `listy
89 filepath` pairs.
90 :param urlgen: An urlgen object, usually found on request.urlgen.
91 '''
42c83752
JW
92 media_urls = {}
93
94 for key, val in media_files.items():
c92aa0d0
JW
95 if isinstance(mg_globals.public_store, BasicFileStorage):
96 # BasicFileStorage does not provide a qualified URI
97 media_urls[key] = urljoin(
98 urlgen('index', qualified=True),
99 mg_globals.public_store.file_url(val))
100 else:
101 media_urls[key] = mg_globals.public_store.file_url(val)
42c83752
JW
102
103 return media_urls
104
105
a062149e 106def api_auth(controller):
85726f73
JW
107 '''
108 Decorator, allows plugins to register auth methods that will then be
109 evaluated against the request, finally a worthy authenticator object is
110 chosen and used to decide whether to grant or deny access.
111 '''
a062149e
JW
112 @wraps(controller)
113 def wrapper(request, *args, **kw):
114 auth_candidates = []
115
116 for auth in PluginManager().get_hook_callables('auth'):
a062149e 117 if auth.trigger(request):
57c6473a 118 _log.debug('{0} believes it is capable of authenticating this request.'.format(auth))
a062149e
JW
119 auth_candidates.append(auth)
120
121 # If we can't find any authentication methods, we should not let them
122 # pass.
123 if not auth_candidates:
cfa92229 124 raise Forbidden()
a062149e
JW
125
126 # For now, just select the first one in the list
127 auth = auth_candidates[0]
128
129 _log.debug('Using {0} to authorize request {1}'.format(
130 auth, request.url))
131
132 if not auth(request, *args, **kw):
88a9662b
JW
133 if getattr(auth, 'errors', []):
134 return json_response({
135 'status': 403,
136 'errors': auth.errors})
137
cfa92229 138 raise Forbidden()
a062149e
JW
139
140 return controller(request, *args, **kw)
141
142 return wrapper