Commit | Line | Data |
---|---|---|
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 | ||
17 | import logging | |
42c83752 | 18 | import json |
a062149e JW |
19 | |
20 | from functools import wraps | |
42c83752 | 21 | from webob import exc, Response |
c92aa0d0 | 22 | from urlparse import urljoin |
a062149e | 23 | |
42c83752 | 24 | from mediagoblin import mg_globals |
a062149e | 25 | from mediagoblin.tools.pluginapi import PluginManager |
42c83752 | 26 | from mediagoblin.storage.filestorage import BasicFileStorage |
a062149e JW |
27 | |
28 | _log = logging.getLogger(__name__) | |
29 | ||
30 | ||
42c83752 JW |
31 | class Auth(object): |
32 | ''' | |
33 | An object with two significant methods, 'trigger' and 'run'. | |
34 | ||
35 | Using a similar object to this, plugins can register specific | |
36 | authentication logic, for example the GET param 'access_token' for OAuth. | |
37 | ||
38 | - trigger: Analyze the 'request' argument, return True if you think you | |
39 | can handle the request, otherwise return False | |
40 | - run: The authentication logic, set the request.user object to the user | |
41 | you intend to authenticate and return True, otherwise return False. | |
42 | ||
43 | If run() returns False, an HTTP 403 Forbidden error will be shown. | |
44 | ||
45 | You may also display custom errors, just raise them within the run() | |
46 | method. | |
47 | ''' | |
48 | def trigger(self, request): | |
49 | raise NotImplemented() | |
50 | ||
51 | def __call__(self, request, *args, **kw): | |
52 | raise NotImplemented() | |
53 | ||
54 | ||
85726f73 JW |
55 | def json_response(serializable, *args, **kw): |
56 | ''' | |
57 | Serializes a json objects and returns a webob.Response object with the | |
58 | serialized value as the response body and Content-Type: application/json. | |
59 | ||
60 | :param serializable: A json-serializable object | |
61 | ||
62 | Any extra arguments and keyword arguments are passed to the | |
63 | webob.Response.__init__ method. | |
64 | ''' | |
65 | response = Response(json.dumps(serializable), *args, **kw) | |
42c83752 | 66 | response.headers['Content-Type'] = 'application/json' |
965b39a8 JW |
67 | cors_headers = { |
68 | 'Access-Control-Allow-Origin': '*', | |
69 | 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', | |
70 | 'Access-Control-Allow-Headers': 'Content-Type, X-Requested-With'} | |
71 | response.headers.update(cors_headers) | |
42c83752 JW |
72 | return response |
73 | ||
74 | ||
85726f73 JW |
75 | def get_entry_serializable(entry, urlgen): |
76 | ''' | |
77 | Returns a serializable dict() of a MediaEntry instance. | |
78 | ||
79 | :param entry: A MediaEntry instance | |
80 | :param urlgen: An urlgen instance, can be found on the request object passed | |
81 | to views. | |
82 | ''' | |
42c83752 JW |
83 | return { |
84 | 'user': entry.get_uploader.username, | |
85 | 'user_id': entry.get_uploader.id, | |
85726f73 JW |
86 | 'user_bio': entry.get_uploader.bio, |
87 | 'user_bio_html': entry.get_uploader.bio_html, | |
88 | 'user_permalink': urlgen('mediagoblin.user_pages.user_home', | |
89 | user=entry.get_uploader.username, | |
90 | qualified=True), | |
42c83752 JW |
91 | 'id': entry.id, |
92 | 'created': entry.created.isoformat(), | |
93 | 'title': entry.title, | |
94 | 'license': entry.license, | |
95 | 'description': entry.description, | |
96 | 'description_html': entry.description_html, | |
97 | 'media_type': entry.media_type, | |
85726f73 JW |
98 | 'permalink': entry.url_for_self(urlgen, qualified=True), |
99 | 'media_files': get_media_file_paths(entry.media_files, urlgen)} | |
100 | ||
42c83752 | 101 | |
85726f73 JW |
102 | def get_media_file_paths(media_files, urlgen): |
103 | ''' | |
104 | Returns a dictionary of media files with `file_handle` => `qualified URL` | |
42c83752 | 105 | |
85726f73 JW |
106 | :param media_files: dict-like object consisting of `file_handle => `listy |
107 | filepath` pairs. | |
108 | :param urlgen: An urlgen object, usually found on request.urlgen. | |
109 | ''' | |
42c83752 JW |
110 | media_urls = {} |
111 | ||
112 | for key, val in media_files.items(): | |
c92aa0d0 JW |
113 | if isinstance(mg_globals.public_store, BasicFileStorage): |
114 | # BasicFileStorage does not provide a qualified URI | |
115 | media_urls[key] = urljoin( | |
116 | urlgen('index', qualified=True), | |
117 | mg_globals.public_store.file_url(val)) | |
118 | else: | |
119 | media_urls[key] = mg_globals.public_store.file_url(val) | |
42c83752 JW |
120 | |
121 | return media_urls | |
122 | ||
123 | ||
a062149e | 124 | def api_auth(controller): |
85726f73 JW |
125 | ''' |
126 | Decorator, allows plugins to register auth methods that will then be | |
127 | evaluated against the request, finally a worthy authenticator object is | |
128 | chosen and used to decide whether to grant or deny access. | |
129 | ''' | |
a062149e JW |
130 | @wraps(controller) |
131 | def wrapper(request, *args, **kw): | |
132 | auth_candidates = [] | |
133 | ||
134 | for auth in PluginManager().get_hook_callables('auth'): | |
135 | _log.debug('Plugin auth: {0}'.format(auth)) | |
136 | if auth.trigger(request): | |
137 | auth_candidates.append(auth) | |
138 | ||
139 | # If we can't find any authentication methods, we should not let them | |
140 | # pass. | |
141 | if not auth_candidates: | |
142 | return exc.HTTPForbidden() | |
143 | ||
144 | # For now, just select the first one in the list | |
145 | auth = auth_candidates[0] | |
146 | ||
147 | _log.debug('Using {0} to authorize request {1}'.format( | |
148 | auth, request.url)) | |
149 | ||
150 | if not auth(request, *args, **kw): | |
151 | return exc.HTTPForbidden() | |
152 | ||
153 | return controller(request, *args, **kw) | |
154 | ||
155 | return wrapper |