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 | |
62d14bf5 | 21 | from werkzeug.exceptions import Forbidden |
cc195d5b | 22 | from werkzeug.wrappers import Response |
fd19da34 BP |
23 | |
24 | from six.moves.urllib.parse import urljoin | |
25 | ||
42c83752 | 26 | from mediagoblin import mg_globals |
a062149e | 27 | from mediagoblin.tools.pluginapi import PluginManager |
42c83752 | 28 | from mediagoblin.storage.filestorage import BasicFileStorage |
a062149e JW |
29 | |
30 | _log = logging.getLogger(__name__) | |
31 | ||
32 | ||
42c83752 JW |
33 | class 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 |
56 | def 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 |
84 | def 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 | 106 | def 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 |